iter(g151): verify 校验 MCP 工具清单
MCP_TOOL_NAMES 收敛为单一来源,cmdMcp 启动时自证注册表与常量一致。 verify 再查 README / AGENTS / CLI 清单是否逐个写出工具名,缺失即报错。
This commit is contained in:
@@ -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,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 ***/
|
||||||
|
|||||||
Reference in New Issue
Block a user