diff --git a/.ua/backlog.json b/.ua/backlog.json index cc27b9a..0dc4ea7 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -1428,6 +1428,14 @@ "acceptance": "treeSelect 点击开弹层、支持搜索过滤、单选/多选、父子联动、清空,弹层沿用 overlay 的 Esc 栈与外部点击收起;样式只消费 --ua-*;文档与演示同步;npm run check 全绿", "status": "done", "notes": "自补:组件补充方向" + }, + { + "id": "g134", + "title": "组件计数自动同步 ua sync-counts", + "motivation": "新增组件要手改 README 标题 / README 包说明 / AGENTS / ARCHITECTURE 四处数字,本轮四次新增每次都靠人工替换;且 README 标题「共 N 个」此前不在 verify 正则里,漏改不报错", + "acceptance": "新增 ua sync-counts(幂等,无变化不写盘)把三份文档的「N 个组件 / N 个 DOM 组件 / 共 N 个」统一改写为清单实际数量;verify 的计数正则覆盖 README 标题形态并提示修法;npm run check 全绿", + "notes": "自补:构建/门禁工具链改进(实测把 README 标题改成 61 后 verify 能报行号,sync-counts 一键修回)", + "status": "open" } ] } diff --git a/AGENTS.md b/AGENTS.md index 899cd01..834410d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,8 +76,8 @@ | 5 | `packages/skin/ui/ua.manifest.json` → `api.exports` | 一行 `xxx(props):一句话说明` | | 6 | 同上 → `docs.components` | `{ id, title, demo, props[], example, steps[] }`:props 里每个字段名必须在实现源码里真实出现,且 `<组件>Props` 的字段一个都不能漏(verify 双向校验) | | 7 | `apps/demo/main.ts` | 新增 `section('', ...)` 并**真实调用**该组件(verify 会查) | -| 8 | `README.md` | `` 区块补 id、「分组:」行补归组、标题「共 N 个」与包说明里的数字 | -| 9 | `AGENTS.md` / `ARCHITECTURE.md` | 两处的「N 个组件」计数同步 | +| 8 | `README.md` | `` 区块补 id、「分组:」行补归组(数字交给下一步自动同步) | +| 9 | `npm run sync-counts` | 自动把三份文档里的「N 个组件 / 共 N 个」改成清单实际数量(幂等;漏改时 verify 报行号) | | 10 | `npm run docs` | 重新生成 `llms-full.txt` 与 `apps/docs/index.html`(不跑 verify 会报 llms-full 漂移) | 改**既有**组件的入参同样要走 5、6、10 三步;只加一个可选参数也要补文档行,否则双向校验失败。 diff --git a/package.json b/package.json index 6d232cd..e69e0fd 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "verify": "node packages/tools/cli/bin/ua.js verify", "lint-iterations": "node packages/tools/cli/bin/ua.js lint-iterations", "changelog": "node packages/tools/cli/bin/ua.js changelog", - "changelog:check": "node packages/tools/cli/bin/ua.js changelog --check" + "changelog:check": "node packages/tools/cli/bin/ua.js changelog --check", + "sync-counts": "node packages/tools/cli/bin/ua.js sync-counts" }, "devDependencies": { "typescript": "^5.6.0" diff --git a/packages/tools/cli/bin/ua.js b/packages/tools/cli/bin/ua.js index 2c1f1e2..dc6d4e1 100644 --- a/packages/tools/cli/bin/ua.js +++ b/packages/tools/cli/bin/ua.js @@ -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 |docs|tokens [--json]|style|verify|commit-lint|lint-iterations|changelog|mcp>'); + console.log('usage: ua |docs|tokens [--json]|style|sync-counts|verify|commit-lint|lint-iterations|changelog|mcp>'); process.exit(cmd ? 1 : 0); }