iter(g249): doctor 增加命令面与门禁链自洽自检
This commit is contained in:
@@ -1675,6 +1675,17 @@ function cmdLintIterations() {
|
||||
}
|
||||
|
||||
/*** 环境自检:新 Agent 接手前一条命令确认 node / 依赖 / git 钩子 / .ua 元数据就绪,任一失败退出码 1 ***/
|
||||
/*** 作用:解析本文件的命令面——dispatch 的 case 集合 vs usage 串里列的命令,两者必须相互覆盖
|
||||
* 新增命令时只加 case 不改 usage(或反之)会让 Agent 查不到入口:usage 是 Agent 唯一能看到的命令清单。 ***/
|
||||
function commandSurface() {
|
||||
const self = fs.readFileSync(fileURLToPath(import.meta.url), 'utf8');
|
||||
const cases = new Set([...self.matchAll(/^ {2}case '([^']+)':/gm)].map((m) => m[1]));
|
||||
const usage = /console\.log\('usage: ua <(.+)>'\)/.exec(self); // 贪婪到行尾括号:usage 里本身含 <kw> 这类尖括号
|
||||
// 先剔掉 [--json|--baseline] 这类选项组再按 | 切:否则选项里的竖线会被当成命令分隔符
|
||||
const listed = usage ? usage[1].replace(/\[[^\]]*\]/g, '').split('|').map((part) => part.trim().split(/\s+/)[0]).filter(Boolean) : [];
|
||||
return { cases, listed };
|
||||
}
|
||||
|
||||
function cmdDoctor() {
|
||||
const checks = []; // 逐项结果:名称 + 是否通过 + 可选失败详情
|
||||
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
||||
@@ -1701,6 +1712,30 @@ function cmdDoctor() {
|
||||
const badLines = fs.readFileSync(logFile, 'utf8').split(/\r?\n/).filter((l) => l.trim()).filter((l) => { try { JSON.parse(l); return false; } catch { return true; } });
|
||||
checks.push({ name: '.ua/iterations.log 每行合法 JSON', pass: !badLines.length, detail: badLines.length ? `${badLines.length} 行非法(首行:${badLines[0].slice(0, 40)}…)` : '' });
|
||||
} else checks.push({ name: '.ua/iterations.log 存在', pass: false, detail: '尚未生成,跑一轮迭代或 ua index' });
|
||||
// 命令面自检:usage ↔ dispatch ↔ CLI 清单三方一致,防止新命令只落一处
|
||||
const { cases, listed } = commandSurface();
|
||||
const missingInUsage = [...cases].filter((c) => !listed.includes(c));
|
||||
const missingInCode = listed.filter((c) => !cases.has(c));
|
||||
checks.push({
|
||||
name: 'usage 串 ↔ dispatch 一致',
|
||||
pass: !missingInUsage.length && !missingInCode.length,
|
||||
detail: [missingInUsage.length ? `usage 缺 ${missingInUsage.join('/')}` : '', missingInCode.length ? `实现缺 ${missingInCode.join('/')}` : ''].filter(Boolean).join(';'),
|
||||
});
|
||||
// 门禁链自检:package.json 的 check 必须跑齐所有门禁步骤(新增门禁忘了接进链等于没加)
|
||||
const needSteps = ['build', 'demo', 'style', 'typecheck', 'smoke', 'index', 'verify', 'lint-iterations', 'audit:visual', 'audit:contrast'];
|
||||
const chain = String(JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')).scripts?.check ?? '');
|
||||
const lacking = needSteps.filter((step) => !chain.includes(`npm run ${step}`));
|
||||
checks.push({ name: 'check 链覆盖全部门禁步骤', pass: !lacking.length, detail: lacking.length ? `缺 ${lacking.join(' / ')}` : '' });
|
||||
// 清单自检:ua.manifest.json 里写进 api.exports 的命令必须真的存在(文档承诺 ≠ 实现)
|
||||
const cliManifestPath = path.join(ROOT, 'packages', 'tools', 'cli', 'ua.manifest.json');
|
||||
const declared = fs.existsSync(cliManifestPath)
|
||||
? (JSON.parse(fs.readFileSync(cliManifestPath, 'utf8')).api?.exports ?? [])
|
||||
.filter((line) => /^ua \S/.test(line))
|
||||
.map((line) => line.replace(/^ua\s+/, '').split(/[\s:]/)[0]) // 说明用全角冒号接在后面,按空白与冒号一起切
|
||||
: [];
|
||||
const ghost = declared.filter((name) => !cases.has(name));
|
||||
checks.push({ name: 'CLI 清单声明的命令都有实现', pass: !ghost.length, detail: ghost.length ? `清单有实现无:${ghost.join('/')}` : '' });
|
||||
|
||||
for (const c of checks) console.log(`${c.pass ? 'pass' : 'FAIL'} ${c.name}${c.detail ? ':' + c.detail : ''}`);
|
||||
const fails = checks.filter((c) => !c.pass).length;
|
||||
if (fails) { console.error(`doctor: ${fails} 项未通过,先修复再跑 npm run check`); process.exit(1); }
|
||||
@@ -1767,6 +1802,6 @@ switch (cmd) {
|
||||
cmdMcp();
|
||||
break;
|
||||
default:
|
||||
console.log('usage: ua <index|list|search <kw>|info <name>|docs|tokens [--json]|style|sync-counts|verify|doctor|commit-lint|lint-iterations|audit:visual [--json|--baseline]|audit:contrast [--json|--list]|changelog|mcp>');
|
||||
console.log('usage: ua <index|list|search <kw>|info <name> [--json|--md]|recipes [编号] [--json]|docs|tokens [--json]|style|sync-counts|verify|doctor|commit-lint|lint-iterations|audit:visual [--json|--baseline]|audit:contrast [--json|--list]|changelog|mcp>');
|
||||
process.exit(cmd ? 1 : 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user