iter(g151): verify 校验 MCP 工具清单

MCP_TOOL_NAMES 收敛为单一来源,cmdMcp 启动时自证注册表与常量一致。
verify 再查 README / AGENTS / CLI 清单是否逐个写出工具名,缺失即报错。
This commit is contained in:
HIsheR
2026-10-26 15:51:48 +08:00
parent 7418b7970b
commit f59e9b70e3
2 changed files with 34 additions and 1 deletions
+8
View File
@@ -1564,6 +1564,14 @@
"acceptance": "ua info <组件id> 末尾列出包含该组件的配方 id 与标题(无命中不追加空行);--json 输出增加 recipes 字段;MCP get_component 组件级返回同步带 recipes。", "acceptance": "ua info <组件id> 末尾列出包含该组件的配方 id 与标题(无命中不追加空行);--json 输出增加 recipes 字段;MCP get_component 组件级返回同步带 recipes。",
"notes": "", "notes": "",
"status": "done" "status": "done"
},
{
"id": "g151",
"title": "verify 校验 MCP 工具清单三处一致",
"motivation": "MCP 工具从 4 个扩到 6 个时,README/AGENTS/CLI 清单靠手改;漏一处就会让 Agent 读到过期的工具名,而 verify 当时不管这块。",
"acceptance": "MCP_TOOL_NAMES 为单一来源,cmdMcp 启动时自证注册表与之一致;verify 检查 README/AGENTS/CLI 清单逐个写出工具名,缺失即报错并指出漏了哪个(已负向实测)。",
"notes": "",
"status": "open"
} }
] ]
} }
+26 -1
View File
@@ -26,6 +26,7 @@ const INDEX_FILE = path.join(INDEX_DIR, 'index.json');
const LLMS_FILE = path.join(ROOT, 'llms.txt'); const LLMS_FILE = path.join(ROOT, 'llms.txt');
const LLMS_FULL = path.join(ROOT, 'llms-full.txt'); const LLMS_FULL = path.join(ROOT, 'llms-full.txt');
const RECIPES_FILE = path.join(ROOT, 'packages', 'tools', 'cli', 'recipes.md'); // 组合配方单一来源(人类版与 MCP 共用) const RECIPES_FILE = path.join(ROOT, 'packages', 'tools', 'cli', 'recipes.md'); // 组合配方单一来源(人类版与 MCP 共用)
const MCP_TOOL_NAMES = ['list_components', 'get_component', 'search_components', 'get_tokens', 'list_recipes', 'get_recipe']; // MCP 工具单一来源:注册表与三处文档都对它校验
/*** 汇总各包 ua.manifest.json 与 package.json 版本(扫描 packages/ 两层目录) ***/ /*** 汇总各包 ua.manifest.json 与 package.json 版本(扫描 packages/ 两层目录) ***/
function collectManifests() { function collectManifests() {
@@ -580,6 +581,9 @@ function cmdMcp() {
const recipes = parseRecipes(); // 配方清单:MCP 侧只读一次 const recipes = parseRecipes(); // 配方清单:MCP 侧只读一次
const compIds = collectComponents(packages).map((x) => x.comp.id); // 组件全集:把配方里的组件名回指到注册表 const compIds = collectComponents(packages).map((x) => x.comp.id); // 组件全集:把配方里的组件名回指到注册表
if (tools.map((t) => t.name).join() !== MCP_TOOL_NAMES.join()) {
throw new Error(`MCP 工具注册表与 MCP_TOOL_NAMES 不一致:${tools.map((t) => t.name).join(',')}`); // 常量是文档校验的基准,先自证
}
/*** 从配方正文里认出用到的组件 id(按词边界匹配,避免 toast 命中 toastError) ***/ /*** 从配方正文里认出用到的组件 id(按词边界匹配,避免 toast 命中 toastError) ***/
const recipeComponents = (r) => const recipeComponents = (r) =>
@@ -987,6 +991,26 @@ function checkIterations() {
return issues; return issues;
} }
/*** MCP 工具清单校验:README / AGENTS / CLI 清单必须逐个写出实际注册的工具名(新增工具漏改文档会命中) ***/
function checkMcpDocs() {
const issues = [];
const files = [
['README.md', path.join(ROOT, 'README.md')],
['AGENTS.md', path.join(ROOT, 'AGENTS.md')],
['packages/tools/cli/ua.manifest.json', path.join(ROOT, 'packages', 'tools', 'cli', 'ua.manifest.json')],
];
for (const [label, file] of files) {
if (!fs.existsSync(file)) {
issues.push(`${label} 不存在:MCP 工具清单无处登记`);
continue;
}
const text = fs.readFileSync(file, 'utf8');
const miss = MCP_TOOL_NAMES.filter((n) => !text.includes(n));
if (miss.length) issues.push(`${label} 的 MCP 工具清单缺少:${miss.join(' / ')}(改了 ua mcp 的工具就要同步这三处)`);
}
return issues;
}
/*** 配方结构校验:编号连续不重复、每条都有最小代码块与要点、通用约定段存在(MCP 与 llms-full 都按这个结构取用) ***/ /*** 配方结构校验:编号连续不重复、每条都有最小代码块与要点、通用约定段存在(MCP 与 llms-full 都按这个结构取用) ***/
function checkRecipes() { function checkRecipes() {
const issues = []; const issues = [];
@@ -1093,6 +1117,7 @@ function cmdVerify() {
if (demoSource) issues.push(...checkDemoBundle(anchors)); // 产物与源码同步校验 if (demoSource) issues.push(...checkDemoBundle(anchors)); // 产物与源码同步校验
if (!demoSource) console.log('verify: 未找到 apps/demo/main.ts,跳过 demo 锚点校验'); if (!demoSource) console.log('verify: 未找到 apps/demo/main.ts,跳过 demo 锚点校验');
issues.push(...checkRecipes()); // 配方结构:编号连续 + 骨架 + 要点,保证 MCP 取得到 issues.push(...checkRecipes()); // 配方结构:编号连续 + 骨架 + 要点,保证 MCP 取得到
issues.push(...checkMcpDocs()); // MCP 工具清单:改注册表必须同步 README / AGENTS / CLI 清单
for (const pkg of pkgs) { for (const pkg of pkgs) {
const comps = pkg.docs?.components ?? []; // 清单已被 collectManifests 摊平 const comps = pkg.docs?.components ?? []; // 清单已被 collectManifests 摊平
@@ -1141,7 +1166,7 @@ function cmdVerify() {
console.error(`${issues.length} violation(s)`); console.error(`${issues.length} violation(s)`);
process.exit(1); process.exit(1);
} }
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌,README 组件清单与分组同步,backlog ↔ 留痕一致,组合配方结构完整)'); console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌,README 组件清单与分组同步,backlog ↔ 留痕一致,组合配方结构完整,MCP 工具清单三处一致)');
} }
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/ /*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/