iter(g134): 新增 ua sync-counts 自动同步组件计数

This commit is contained in:
HIsheR
2026-10-26 15:25:52 +08:00
parent 6701c226fb
commit 2a9193ce19
4 changed files with 51 additions and 12 deletions
+39 -9
View File
@@ -627,7 +627,10 @@ function styleCheckFile(file, src) {
let j = i - 1;
while (j >= 0 && lines[j].trim() === '') j--; // 找上一个非空行
if (j < 0 || !lines[j].includes('/***')) {
issues.push(`${file}:${i + 1} 导出 ${decl[1]} 缺少 /*** 作用 ***/ 注释`);
// 上一行是注释续行(以 ***/ 结尾却没有 /***)说明写成了多行,给出可直接照做的修法
const cont = j >= 0 && lines[j].includes('***/') && !lines[j].includes('/***');
const hint = cont ? '(注释被拆成多行:压成单行 /*** 作用 ***/ 并紧贴导出上一行)' : '';
issues.push(`${file}:${i + 1} 导出 ${decl[1]} 缺少 /*** 作用 ***/ 注释${hint}`);
}
}
@@ -749,22 +752,46 @@ function checkReadme(pkgs) {
}
/*** 组件数漂移校验:AGENTS.md / ARCHITECTURE.md 中的「N 个组件」必须与 skin-dom 清单实际总数一致 ***/
// 组件计数的三种写法:「N 个组件」「N 个 DOM 组件」「共 N 个」
const COUNT_RE = /(\d+)\s*个\s*(?:DOM\s*)?组件|共\s*(\d+)\s*个/g;
/*** 逐行扫文档里的组件计数,返回 { line, value, text } 便于校验与改写共用 ***/
function scanCountLines(text) {
const hits = [];
text.split(/\r?\n/).forEach((line, i) => {
for (const m of line.matchAll(COUNT_RE)) hits.push({ line: i + 1, value: Number(m[1] ?? m[2]), text: line });
});
return hits;
}
function checkCountDocs(actual) {
const issues = [];
const re = /(\d+)\s*个(?:\s*DOM\s*)?组件/;
for (const name of ['AGENTS.md', 'ARCHITECTURE.md', 'README.md']) {
const file = path.join(ROOT, name);
if (!fs.existsSync(file)) continue; // 卫语句:文档缺失不校验
fs.readFileSync(file, 'utf8').split(/\r?\n/).forEach((line, i) => {
const m = line.match(re);
if (m && Number(m[1]) !== actual) {
issues.push(`${name}:${i + 1} 组件数「${m[1]}」与清单实际 ${actual} 不一致,请同步`);
}
});
for (const hit of scanCountLines(fs.readFileSync(file, 'utf8'))) {
if (hit.value !== actual) issues.push(`${name}:${hit.line} 组件数「${hit.value}」与清单实际 ${actual} 不一致,跑 ua sync-counts 自动同步`);
}
}
return issues;
}
/*** 组件计数同步:把三份文档里的「N 个组件 / 共 N 个」一律改成清单实际数量(幂等,无变化不写盘) ***/
function cmdSyncCounts() {
const actual = collectComponents(collectManifests()).length;
let changed = 0;
for (const name of ['AGENTS.md', 'ARCHITECTURE.md', 'README.md']) {
const file = path.join(ROOT, name);
if (!fs.existsSync(file)) continue; // 卫语句:文档缺失跳过
const before = fs.readFileSync(file, 'utf8');
const after = before.replace(COUNT_RE, (m, a, b) => (a ? m.replace(a, String(actual)) : m.replace(b, String(actual))));
if (after === before) continue;
fs.writeFileSync(file, after, 'utf8');
changed += 1;
}
console.log(`sync-counts: 清单实际 ${actual} 个组件,改写 ${changed} 个文件`);
}
/*** llms-full.txt 同步校验:内联重渲染 Agent 文档(时间戳归一化)与落盘产物比对,防 manifest 改后忘跑 docs ***/
function checkLlmsFull(pkgs, issues) {
if (!fs.existsSync(LLMS_FULL)) return; // 卫语句:产物缺失不算漂移(可能是新仓库)
@@ -1101,6 +1128,9 @@ switch (cmd) {
case 'style':
cmdStyle();
break;
case 'sync-counts':
cmdSyncCounts();
break;
case 'verify':
cmdVerify();
break;
@@ -1120,6 +1150,6 @@ switch (cmd) {
cmdMcp();
break;
default:
console.log('usage: ua <index|list|info <name>|docs|tokens [--json]|style|verify|commit-lint|lint-iterations|changelog|mcp>');
console.log('usage: ua <index|list|info <name>|docs|tokens [--json]|style|sync-counts|verify|commit-lint|lint-iterations|changelog|mcp>');
process.exit(cmd ? 1 : 0);
}