iter(g228): 新增 ua doctor 环境自检命令

This commit is contained in:
HIsheR
2026-10-26 20:29:52 +08:00
parent b19ea5d2b9
commit a692cab80f
5 changed files with 43 additions and 10 deletions
+34 -1
View File
@@ -1576,10 +1576,43 @@ function cmdLintIterations() {
console.log(`lint-iterations OK (${lines.length} entries)`);
}
/*** 环境自检:新 Agent 接手前一条命令确认 node / 依赖 / git 钩子 / .ua 元数据就绪,任一失败退出码 1 ***/
function cmdDoctor() {
const checks = []; // 逐项结果:名称 + 是否通过 + 可选失败详情
const nodeMajor = Number(process.versions.node.split('.')[0]);
checks.push({ name: `node >= 18(当前 ${process.versions.node})`, pass: nodeMajor >= 18 });
checks.push({ name: 'pnpm-lock.yaml 存在', pass: fs.existsSync(path.join(ROOT, 'pnpm-lock.yaml')) });
checks.push({ name: '依赖已安装(node_modules/)', pass: fs.existsSync(path.join(ROOT, 'node_modules')) });
const hooksPath = spawnSync('git', ['config', 'core.hooksPath'], { cwd: ROOT }).stdout?.toString().trim(); // 显式 hooksPath 优先,默认取 .git/hooks
const hookDir = hooksPath ? path.resolve(ROOT, hooksPath) : path.join(ROOT, '.git', 'hooks');
checks.push({ name: 'commit-msg 钩子在位', pass: fs.existsSync(path.join(hookDir, 'commit-msg')) });
const tryJson = (rel) => { // 存在且能 JSON.parse 才算过
const p = path.join(ROOT, rel);
if (!fs.existsSync(p)) return `文件不存在`;
try { JSON.parse(fs.readFileSync(p, 'utf8')); return ''; } catch (e) { return `解析失败:${e.message}`; }
};
for (const rel of ['.ua/backlog.json', '.ua/index.json']) {
const bad = tryJson(rel);
checks.push({ name: `${rel} 可解析`, pass: !bad, detail: bad });
}
const logFile = path.join(INDEX_DIR, 'iterations.log');
if (fs.existsSync(logFile)) {
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' });
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); }
console.log(`doctor OK(${checks.length} 项全过)`);
}
// ---- 入口 ----
const [cmd, ...args] = process.argv.slice(2);
switch (cmd) {
case 'doctor':
cmdDoctor();
break;
case 'index':
cmdIndex();
break;
@@ -1630,6 +1663,6 @@ switch (cmd) {
cmdMcp();
break;
default:
console.log('usage: ua <index|list|search <kw>|info <name>|docs|tokens [--json]|style|sync-counts|verify|commit-lint|lint-iterations|audit:visual [--json|--baseline]|changelog|mcp>');
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]|changelog|mcp>');
process.exit(cmd ? 1 : 0);
}