base-skeleton-complete.test.js 2.85 KB
/**
 * Unit Test — `_base` 骨架必须齐全(28 号设计 验收 6、二轮修复 问题 2)
 *
 * 锁的是一条**只有真跑一次招聘才会显形**的静默失效:
 * `hire()` 雇一个仓库里没有的角色时,从 `_base` 复制骨架毕业出一份新本体,随即用
 * `missingBodyDefinition()`(agent-locator)判它齐不齐全 —— 缺一份就停在 status='draft',
 * 不许被任何公司雇走。而 `_base` 一度只带 agent.md + config.json,于是**每一个**新本体
 * 出生即 draft,补齐只能靠 agent-creator 那次 fire-and-forget 的自调用(已知缺陷:单会话锁,
 * 连招时只有第一个被填充)。症状是「招聘成功了,人却永远上不了岗」,一处报错都没有。
 *
 * 两条断言分工:
 *   ① 逐份查字节数 —— 三份定义都得真有内容,缺一份新本体就少一条提示词到达路径;
 *      200 字节是「写了点东西」的下限,不是质量标准;
 *   ② 拿**闸门本人**(missingBodyDefinition)判 `_base`:它必须判**三份全缺**。
 *
 * ⚠️ ② 的方向是二轮修复反过来的(原来断言 `[]`)。补齐 `_base` 与「非空即齐全」的旧判据
 * 凑在一起会把闸门整个抵消:骨架一拷完就转正,新角色拿着通用 Base Agent 文本直接上岗,
 * 第二家公司还会把它当可复用本体雇走。所以 `_base` 的三份定义里都埋了
 * `BASE_SKELETON_SENTINEL`,「齐全」的判据变成「有内容 **且** 不再是骨架」——
 * `_base` 自己**永远**是骨架,也就永远不该被判成齐全。详见 base-graduation-gate.test.js。
 */
import { describe, it, expect } from 'vitest';
import fs from 'fs';
import path from 'path';
import { missingBodyDefinition, BASE_SKELETON_SENTINEL } from '../../services/agent-locator.js';

const BASE_DIR = path.resolve(__dirname, '../../../system/agents/_base');

const DEFINITION_FILES = [
  'agent.md',
  'soul.md',
  path.join('.claude', 'skills', 'main_skill', 'SKILL.md'),
];

describe('_base 元 agent 骨架(新本体的出生起点)', () => {
  it.each(DEFINITION_FILES)('_base/%s 存在且不是占位(去空白后 > 200 字节)', (rel) => {
    const p = path.join(BASE_DIR, rel);
    expect(fs.existsSync(p), `${p} 不存在`).toBe(true);
    expect(fs.readFileSync(p, 'utf-8').trim().length).toBeGreaterThan(200);
  });

  it.each(DEFINITION_FILES)('_base/%s 带着骨架哨兵(人一眼看得见「这份还没填」)', (rel) => {
    expect(fs.readFileSync(path.join(BASE_DIR, rel), 'utf-8')).toContain(BASE_SKELETON_SENTINEL);
  });

  it('闸门直接判 _base:三份**全部**算缺 —— 骨架不是定义', () => {
    // 文件全在、内容也不少(上一条断言过),旧的「非空即齐全」判据会把它们全部放行。
    expect(missingBodyDefinition('_base')).toEqual(DEFINITION_FILES);
  });
});