agentic-flow-kit 1.2.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +10 -4
  2. package/modules/hosts/claude/commands/wf-test.md +1 -1
  3. package/modules/hosts/codex/commands/wf-test.md +1 -1
  4. package/modules/hosts/cursor/commands/wf-test.md +1 -1
  5. package/modules/hosts/opencode/commands/wf-test.md +1 -1
  6. package/modules/hosts/trae/commands/wf-test.md +1 -1
  7. package/package.json +1 -1
  8. package/src/fresh-init.test.mjs +10 -0
  9. package/src/init.mjs +1 -1
  10. package/src/profiles.mjs +12 -6
  11. package/src/sync.mjs +30 -26
  12. package/src/sync.test.mjs +82 -0
  13. package/templates/AGENTS.md +1 -0
  14. package/templates/_agents/commands/test.md +1 -1
  15. package/templates/_agents/rule-budgets.txt +3 -1
  16. package/templates/_agents/scripts/agg-delegations.cjs +56 -17
  17. package/templates/_agents/scripts/agg-delegations.test.mjs +76 -1
  18. package/templates/_agents/scripts/agg-gate-stats.mjs +113 -0
  19. package/templates/_agents/scripts/agg-gate-stats.test.mjs +95 -0
  20. package/templates/_agents/scripts/check-hygiene.mjs +167 -0
  21. package/templates/_agents/scripts/check-hygiene.test.mjs +182 -0
  22. package/templates/_agents/scripts/check-ledger.mjs +60 -16
  23. package/templates/_agents/scripts/check-ledger.test.mjs +70 -0
  24. package/templates/_agents/scripts/check-loop-unit.test.mjs +95 -0
  25. package/templates/_agents/scripts/check-loop.mjs +1119 -1097
  26. package/templates/_agents/scripts/check-loop.test.mjs +200 -1
  27. package/templates/_agents/scripts/confirm-doc.mjs +27 -6
  28. package/templates/_agents/scripts/confirm-doc.test.mjs +35 -0
  29. package/templates/_agents/scripts/gate-seg.mjs +54 -0
  30. package/templates/_agents/scripts/gate-seg.test.mjs +112 -0
  31. package/templates/_agents/scripts/gen-workflow-dashboard.mjs +249 -0
  32. package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +189 -0
  33. package/templates/_agents/scripts/policy.mjs +31 -3
  34. package/templates/_agents/scripts/verify.mjs +27 -2
  35. package/templates/_agents/scripts/verify.test.mjs +53 -0
  36. package/templates/_github/workflows/kit-ci.yml +28 -0
package/README.md CHANGED
@@ -32,6 +32,8 @@ node .agents/scripts/confirm-doc.mjs workflow/intents/<文件>.md # 终端里
32
32
 
33
33
  可选红线用 `add-gate`:`dotnet-ca`、`node-layer`、`py-import`、`generated-readonly`。约定见 `modules/gates/README.md`。
34
34
 
35
+ **关于「确认门」的诚实边界**:确认门是**留痕机制,不是防伪机制**。`confirm-doc.mjs` 把确认事件写进 `.agents/confirmations.jsonl`(append-only,入 git),check-loop 据此对账——但台账是本地可写文件,**刻意手改台账仍可伪造**。门禁的实际作用是把「顺手绕过」抬到「主动伪造」(两者性质不同、可事后对质),而非关闭通道。仓库自身的引擎注释对此有完整声明(见 `.agents/scripts/stage-gates.mjs` 头部信任边界段)。委托代录(`--delegated`)的台账行如实记 `source` 与用户原话供事后对质,永不伪装 TTY 确认。
36
+
35
37
  ## 快速开始
36
38
 
37
39
  ```bash
@@ -84,10 +86,14 @@ modules/gates/ 可选门禁模块(dotnet-ca:Clean Architecture 参考
84
86
  - 抽取自真实项目长期运转的引擎(某真实项目),dogfooding 是后续路线(sync 升级 / add-host / add-gate / npm 发布)的一部分。
85
87
  - **威胁模型(2026-09-27 init-p1-batch)**:勿在不可信仓库运行 `init` / `doctor` / `sync`——三命令在执行目标侧 `.agents/scripts/` 脚本(生成器 / 校验器)前有供应链防线:脚本内容与包源渲染值(LF 归一 sha)一致才执行,失配即显式跳过提示,不执行不下结论。**防线不覆盖 git 钩子面**:预植的 `.githooks/`、`.agents/hooks/` 仍会被保留挂载,并在你自己的首次 git 操作时执行——不可信仓库请先审查/清除预置件再 init。
86
88
 
87
- ## 当前能力(已发布 0.8.0)
89
+ ## 当前能力
90
+
91
+ **能力清单的单一真相源是 `CHANGELOG.md`**——每个已发布版本带什么、为什么这么带,都在那里。本节只讲当前语义,不重复枚举版本特性(此前列举导致 README 与实际发布内容长期漂移,2026-10-08 已收敛到单源)。
88
92
 
89
- 版本沿革写在 `CHANGELOG.md`。0.8.0 已包含:init / doctor / sync / add-host / add-gate、四套技术栈门禁初值、宿主薄适配、确认门、check-loop 16 项、看板端口上探、opencode 命令 `wf-` 前缀、pre-commit managed 台账快检。
93
+ 当前语义三点:
90
94
 
91
- 仓库里还没打进版本号的增量:`audit` 档(新装默认 false)、`policyVersion`、检查 17(发版树上的未收口文档)、检查 18(委派台账对账)、检查 19(逐阶段审计)、逐阶段前置门(起草门 / 确认门 / done 前置门)、claude / cursor / codex 薄适配、`node-layer` / `py-import` / `generated-readonly` 三个可选门禁、fresh init 冒烟。效果数字等有外部仓库跑过再记,不在这次。
95
+ - **门禁面**:check-loop 20 项检查 + `.githooks/` 五个钩子(pre-commit / pre-push / commit-msg / post-commit / pre-merge-commit),门禁逻辑不依赖 agent 客户端自觉。
96
+ - **闭环面**:intents → specs → plans → incidents 四态文档链 + 确认台账(`.agents/confirmations.jsonl`)+ 逐阶段前置门(起草门 / 确认门 / done 前置门)。
97
+ - **观测面**:`workflow/INDEX.md`(检索索引)、`workflow/DASHBOARD.md`(红绿灯仪表盘)、`workflow/metrics.md`(月度快照)——**观测面非门禁**,红灯是「看」的不是「拦」的。
92
98
 
93
- 活跃层里 2026-09-23 至 09-28 的 approved / open 文档没有在这次改成 done 或 closed。那些文件的确认指纹绑着当时的正文,关单要逐份 `confirm-doc`。
99
+ 版本号与已发布能力看 `package.json` 的 `version` 与 `CHANGELOG.md`;仓库当前工作流实况看 `workflow/INDEX.md`。
@@ -56,7 +56,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
56
56
  - 输出测试报告(通过项 / 失败项)
57
57
  - 失败项已修复并复测通过
58
58
  - **关单**(日常闭环点,不依赖是否上 prod):
59
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
59
+ - 关单前一键过门:`node .agents/scripts/verify.mjs --doc workflow/intents/<本单>.md`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**`--doc` 把测试绿凭证绑到本单**——不带它落下的绿行不给任何单背书,confirm-doc 前置与检查 8 对账只认带本单 `--doc` 的绿行(仅 v6+ 生效);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
60
60
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
61
61
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
62
62
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
@@ -56,7 +56,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
56
56
  - 输出测试报告(通过项 / 失败项)
57
57
  - 失败项已修复并复测通过
58
58
  - **关单**(日常闭环点,不依赖是否上 prod):
59
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
59
+ - 关单前一键过门:`node .agents/scripts/verify.mjs --doc workflow/intents/<本单>.md`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**`--doc` 把测试绿凭证绑到本单**——不带它落下的绿行不给任何单背书,confirm-doc 前置与检查 8 对账只认带本单 `--doc` 的绿行(仅 v6+ 生效);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
60
60
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
61
61
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
62
62
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
@@ -56,7 +56,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
56
56
  - 输出测试报告(通过项 / 失败项)
57
57
  - 失败项已修复并复测通过
58
58
  - **关单**(日常闭环点,不依赖是否上 prod):
59
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
59
+ - 关单前一键过门:`node .agents/scripts/verify.mjs --doc workflow/intents/<本单>.md`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**`--doc` 把测试绿凭证绑到本单**——不带它落下的绿行不给任何单背书,confirm-doc 前置与检查 8 对账只认带本单 `--doc` 的绿行(仅 v6+ 生效);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
60
60
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
61
61
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
62
62
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
@@ -56,7 +56,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
56
56
  - 输出测试报告(通过项 / 失败项)
57
57
  - 失败项已修复并复测通过
58
58
  - **关单**(日常闭环点,不依赖是否上 prod):
59
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
59
+ - 关单前一键过门:`node .agents/scripts/verify.mjs --doc workflow/intents/<本单>.md`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**`--doc` 把测试绿凭证绑到本单**——不带它落下的绿行不给任何单背书,confirm-doc 前置与检查 8 对账只认带本单 `--doc` 的绿行(仅 v6+ 生效);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
60
60
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
61
61
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
62
62
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
@@ -57,7 +57,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
57
57
  - 输出测试报告(通过项 / 失败项)
58
58
  - 失败项已修复并复测通过
59
59
  - **关单**(日常闭环点,不依赖是否上 prod):
60
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
60
+ - 关单前一键过门:`node .agents/scripts/verify.mjs --doc workflow/intents/<本单>.md`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**`--doc` 把测试绿凭证绑到本单**——不带它落下的绿行不给任何单背书,confirm-doc 前置与检查 8 对账只认带本单 `--doc` 的绿行(仅 v6+ 生效);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
61
61
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
62
62
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
63
63
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-flow-kit",
3
- "version": "1.2.2",
3
+ "version": "1.4.0",
4
4
  "description": "AI-Native 闭环工作流 + wiki 知识层脚手架——flow-kit init 一条命令为新项目装好 .agents 工作流引擎(阶段命令/角色契约/门禁钩子/kb 检索/看板/规则面预算)+ workflow 文档闭环 + wiki 知识库,装完自包含、零运行时依赖",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -42,6 +42,16 @@ check('新装 audit 为 false 且 policyVersion 为 2', kit.audit === false && k
42
42
  check('新装无包源模板对应条目无锚(生成类配置不参与感知)', cfg !== undefined && cfg.srcSha256 === undefined);
43
43
  const rb = tplAnchor('.agents/rule-budgets.txt');
44
44
  check('新装 _agents 前缀翻译参与感知(rule-budgets 带锚)', rb !== undefined && typeof rb.srcSha256 === 'string');
45
+ // CI 远端门模板(2026-10-07 adopter-ci-github):.github → _github 通用点前缀翻译 + owned 落盘带锚
46
+ const ci = tplAnchor('.github/workflows/kit-ci.yml');
47
+ const ciSrc = fs.readFileSync(path.join(ROOT, 'templates', '_github', 'workflows', 'kit-ci.yml'), 'utf8').replace(/\r\n/g, '\n');
48
+ check('新装 CI 远端门模板落盘且 kit.json owned 带锚(srcSha256 == 包源 LF 归一 sha)',
49
+ fs.existsSync(path.join(dir, '.github', 'workflows', 'kit-ci.yml'))
50
+ && ci !== undefined && ci.srcSha256 === shaOf(Buffer.from(ciSrc, 'utf8')),
51
+ JSON.stringify(ci || null));
52
+ const ciText = fs.readFileSync(path.join(dir, '.github', 'workflows', 'kit-ci.yml'), 'utf8');
53
+ check('新装 CI 模板引用统一入口 verify.mjs、含分支策略人工边界提示且无 Tab 缩进',
54
+ ciText.includes('node .agents/scripts/verify.mjs') && ciText.includes('branch protection') && !ciText.includes('\t'));
45
55
  }
46
56
  check('claude 命令与角色薄适配落盘',
47
57
  fs.existsSync(path.join(dir, '.claude', 'commands', 'wf-plan.md'))
package/src/init.mjs CHANGED
@@ -97,7 +97,7 @@ async function interactive() {
97
97
  const menu = (names) => ' ' + names.map((n, i) => dim(`${menuMark(i)} ${stackLabel(n)}`)).join(dim(' '));
98
98
 
99
99
  console.log(bold(cyan('▶ flow-kit init')) + dim(' · AI 闭环工作流 + wiki 知识层脚手架'));
100
- console.log(dim(' 安装到当前目录:.agents/ · .githooks/ · workflow/ · wiki/ · AGENTS.md(已存在的文件保守跳过;AGENTS.md 无工作流骨架时文末追加补齐)'));
100
+ console.log(dim(' 安装到当前目录:.agents/ · .githooks/ · workflow/ · wiki/ · AGENTS.md · .github/workflows/kit-ci.yml(CI 远端门,非 GitHub 装户可删)(已存在的文件保守跳过;AGENTS.md 无工作流骨架时文末追加补齐)'));
101
101
  try {
102
102
  let hosts;
103
103
  for (;;) {
package/src/profiles.mjs CHANGED
@@ -106,13 +106,17 @@ export function pickStackVars(stackKey) {
106
106
  // Node 18.20 / 20.18 / 22.11 全部 ERR_REQUIRE_ESM(只有 ≥20.19/≥22.12 可用),而本仓 engines
107
107
  // 写 >=18.0.0、CI 跑 18/22 → 装户在 CI 目标版本上根本用不了。改 .cjs 后 createRequire 同步载入
108
108
  // 全线可用。旧 .mjs 路径同样登记为 owned(免装户残留旧文件时落 managed 重演缺陷)。
109
+ // .github/ 是装户 CI 远端门(2026-10-07 adopter-ci-github):kit 只下发 workflows/kit-ci.yml 薄模板,
110
+ // 触发分支 / node 版本 / 测试命令均项目特定——归 owned,sync 永不覆盖;感知锚经 srcTemplatePath
111
+ // 通用点前缀翻译命中 templates/_github/。
109
112
  export function isOwned(rel) {
110
113
  return rel === 'AGENTS.md' || rel === '.gitattributes' || rel.startsWith('workflow/') || rel.startsWith('wiki/')
111
114
  || rel.startsWith('.agents/notes/')
112
115
  || rel === '.agents/workflow-modules.txt' || rel === '.agents/rule-budgets.txt'
113
116
  || rel === '.agents/metric-claims.txt' || rel === '.agents/metric-derivers.cjs'
114
117
  || rel === '.agents/metric-derivers.mjs'
115
- || rel === '.agents/hooks/local-pre-commit';
118
+ || rel === '.agents/hooks/local-pre-commit'
119
+ || rel.startsWith('.github/');
116
120
  }
117
121
 
118
122
  // ---- 模板下发感知(2026-10-06 template-downstream)----
@@ -124,12 +128,14 @@ export function isOwned(rel) {
124
128
  // 装户不期望跟随包源模板——模板感知排除出比对面(防「拉取了也被生成器重写」的无谓出账)。新增生成器目标须同步本清单。
125
129
  export const TEMPLATE_DRIFT_EXCLUDE = new Set(['workflow/INDEX.md', 'wiki/INDEX.md', 'wiki/知识沉淀总览.html']);
126
130
 
127
- // 映射规则单源:renderTree 的「srcRoot/templates ↔ targetRoot/项目根」关系 + 点目录前缀翻译
128
- // (render.mjs:`_agents` → `.agents`)——装户 rel 以 .agents/ 开头时探 templates/_agents/<余径>。
129
- // 复核 P2-1(2026-10-06 template-downstream 独立复核):漏翻译会让 rule-budgets.txt /
130
- // metric-claims.txt / local-pre-commit 等 5 条有包源起步模板的 owned 件永不参与感知。
131
+ // 映射规则单源:renderTree 的「srcRoot/templates ↔ targetRoot/项目根」关系 + 点目录前缀翻译。
132
+ // 通用点前缀翻译(2026-10-07 adopter-ci-github,由 .agents/ 特例泛化):装户 rel 首段以 `.` 开头时探
133
+ // templates/_<去点首段>/<余径>(.agents/notes/x → _agents/notes/x、.github/workflows/kit-ci.yml →
134
+ // _github/…)——与 render.mjs relOf 的顶层 `_`→`.` 同一规则的逆向;无点前缀 rel(AGENTS.md / workflow/…)
135
+ // 原样探 templates/<rel>。无对应模板返回 null(不参与感知)。
131
136
  export function srcTemplatePath(pkgRoot, rel) {
132
- const tplRel = rel.startsWith('.agents/') ? `_agents/${rel.slice('.agents/'.length)}` : rel;
137
+ const first = rel.split('/')[0];
138
+ const tplRel = first.startsWith('.') ? `_${first.slice(1)}${rel.slice(first.length)}` : rel;
133
139
  const p = path.join(pkgRoot, 'templates', tplRel);
134
140
  return fs.existsSync(p) ? p : null;
135
141
  }
package/src/sync.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  // flow-kit sync:按 .agents/kit.json 台账升级 managed 文件。
2
2
  // 三态:未改动(磁盘 sha==台账)→ 覆盖新版;本地已改 → 跳过并报告(--force 覆盖;台账保持包侧基线,
3
3
  // 每次持续报告直至 --force 或本地对齐新版——防跳过一次后下次升级被静默覆盖,2026-09-24 语义修正);
4
- // 生成器目标(INDEX.md / wiki 看板)→ 不比对,收尾重跑生成器走锚点重写。
4
+ // 生成器目标(INDEX.md / wiki 看板)→ 不比对,生成器先于台账记账执行(2026-10-08 修 1:一次 sync
5
+ // 自洽——生成器重写盘面后按终态记账),锚点内重写。
5
6
  // 附带:包内新增 managed 文件 → 安装;包内已删 → 仅报告不删盘、出台账;managed 缺失 → 恢复。
6
7
  // 台账外文件收养(2026-09-26 managed-ledger-adopt):盘上存在但台账无该 rel 的 managed 类文件,
7
8
  // 若内容恰好等于新版渲染 sha(diskSha === fresh.sha)则收养登记——判据与台账内「改动恰好等于新版」
@@ -149,9 +150,35 @@ export function sync(args, pkgRoot) {
149
150
  console.log(` 无变化 ${unchanged} 份`);
150
151
  }
151
152
 
153
+ // 生成器目标走锚点重写(不参与 sha 比对)——执行前过供应链防线(init-p1-batch P1-3):
154
+ // 目标侧脚本与包源渲染值 sha 一致才执行(预置/被改动的脚本不可信,跳过并显式提示)。
155
+ // 先于台账记账执行(2026-10-08 papercuts-cleanup-batch 修 1):生成器会重写 owned 生成目标
156
+ // (INDEX/看板),记账段按生成后盘面收口——一次 sync 自洽,不再出现「wiki 变更后需二次 sync 自愈」。
157
+ const genGuard = (rel) => {
158
+ const g = scriptTrusted({ pkgRoot, target, rel, vars });
159
+ if (!g.ok) {
160
+ console.log(` ⚠️ 跳过执行 ${rel}——${g.note}(供应链防线:只执行与包源渲染值一致的目标侧脚本)`);
161
+ return false;
162
+ }
163
+ return true;
164
+ };
165
+ if (genGuard('.agents/scripts/gen-workflow-index.mjs')) {
166
+ const genIndex = runNode(target, '.agents/scripts/gen-workflow-index.mjs');
167
+ if (genIndex.ok || fs.existsSync(path.join(target, '.agents/scripts/gen-workflow-index.mjs'))) {
168
+ console.log(genIndex.ok ? ' 已重生成 workflow/INDEX.md(锚点内重写)' : ` ⚠️ gen-workflow-index 失败:${genIndex.out.split('\n')[0]}`);
169
+ }
170
+ }
171
+ if (genGuard('.agents/scripts/gen-wiki-board.mjs')) {
172
+ const genBoard = runNode(target, '.agents/scripts/gen-wiki-board.mjs');
173
+ if (genBoard.ok || fs.existsSync(path.join(target, '.agents/scripts/gen-wiki-board.mjs'))) {
174
+ console.log(genBoard.ok ? ' 已重生成 wiki 速览与看板 DATA(锚点内重写)' : ` ⚠️ gen-wiki-board 失败:${genBoard.out.split('\n')[0]}`);
175
+ }
176
+ }
177
+
152
178
  // owned 台账按盘面自愈:owned 归项目所有,哈希只是记账不是约束——手改后无须手工刷 kit.json
153
179
  // (Shipyard 回流策略,2026-09-24:曾在消费仓被迫手工刷新 owned 哈希,根因收敛到此处)。
154
- // sha 按 LF 归一记(与 doctor checkOwnedDrift 同口径,2026-09-27 gate-coverage:跨 checkout 字节稳定)
180
+ // sha 按 LF 归一记(与 doctor checkOwnedDrift 同口径,2026-09-27 gate-coverage:跨 checkout 字节稳定)。
181
+ // 位于生成器之后(2026-10-08 papercuts-cleanup-batch 修 1):生成器重写盘面后按终态记账。
155
182
  const ownedSha = (p) => sha256(Buffer.from(fs.readFileSync(p, 'utf8').replace(/\r\n/g, '\n'), 'utf8'));
156
183
  let ownedRefreshed = 0;
157
184
  if (Array.isArray(kit.owned)) {
@@ -188,33 +215,10 @@ export function sync(args, pkgRoot) {
188
215
  console.log(' owned 归项目所有,不自动覆盖;如需跟随:从包源 templates/ 拷贝对应文件;有意定制可忽略(下次 sync 起静默)');
189
216
  }
190
217
 
191
- // 台账重写:版本对齐当前包;removed 出册;skipped 保持包侧基线持续报告
218
+ // 台账重写(生成器之后):版本对齐当前包;removed 出册;skipped 保持包侧基线持续报告
192
219
  kit.managed = managedNew.sort((a, b) => a.rel.localeCompare(b.rel));
193
220
  kit.version = pkg.version;
194
221
  fs.writeFileSync(kitPath, `${JSON.stringify(kit, null, 2)}\n`);
195
-
196
- // 生成器目标走锚点重写(不参与 sha 比对)——执行前过供应链防线(init-p1-batch P1-3):
197
- // 目标侧脚本与包源渲染值 sha 一致才执行(预置/被改动的脚本不可信,跳过并显式提示)
198
- const genGuard = (rel) => {
199
- const g = scriptTrusted({ pkgRoot, target, rel, vars });
200
- if (!g.ok) {
201
- console.log(` ⚠️ 跳过执行 ${rel}——${g.note}(供应链防线:只执行与包源渲染值一致的目标侧脚本)`);
202
- return false;
203
- }
204
- return true;
205
- };
206
- if (genGuard('.agents/scripts/gen-workflow-index.mjs')) {
207
- const genIndex = runNode(target, '.agents/scripts/gen-workflow-index.mjs');
208
- if (genIndex.ok || fs.existsSync(path.join(target, '.agents/scripts/gen-workflow-index.mjs'))) {
209
- console.log(genIndex.ok ? ' 已重生成 workflow/INDEX.md(锚点内重写)' : ` ⚠️ gen-workflow-index 失败:${genIndex.out.split('\n')[0]}`);
210
- }
211
- }
212
- if (genGuard('.agents/scripts/gen-wiki-board.mjs')) {
213
- const genBoard = runNode(target, '.agents/scripts/gen-wiki-board.mjs');
214
- if (genBoard.ok || fs.existsSync(path.join(target, '.agents/scripts/gen-wiki-board.mjs'))) {
215
- console.log(genBoard.ok ? ' 已重生成 wiki 速览与看板 DATA(锚点内重写)' : ` ⚠️ gen-wiki-board 失败:${genBoard.out.split('\n')[0]}`);
216
- }
217
- }
218
222
  } finally {
219
223
  fs.rmSync(tmp, { recursive: true, force: true });
220
224
  }
package/src/sync.test.mjs CHANGED
@@ -366,5 +366,87 @@ else if (cmd === 'add-gate') addGate(rest, root);
366
366
  check('S15⑥ .agents/ 前缀翻译 → _agents 模板参与感知出账', (r7.stdout + r7.stderr).includes('有演进且盘面未跟随') && (r7.stdout + r7.stderr).includes('.agents/rule-budgets.txt'));
367
367
  }
368
368
 
369
+ // ============ 场景 16:装户 GitHub CI 远端门模板(2026-10-07 adopter-ci-github)============
370
+ // 包源 templates/_github/workflows/kit-ci.yml → 装户 .github/workflows/kit-ci.yml(owned):
371
+ // ① isOwned / srcTemplatePath 契约(含 .agents 存量不回归、无模板反例);
372
+ // ② 存量装户盘上无该件 → newOwned 提示且不自动落盘;③ 已装 + 源演进 → 感知出账 + 锚刷新 + 再跑静默
373
+ {
374
+ const sha = (s) => sha256(Buffer.from(s, 'utf8'));
375
+ const CI_REL = '.github/workflows/kit-ci.yml';
376
+ const CI_TPL = 'ci v2\n';
377
+ const { isOwned, srcTemplatePath } = await import(pathToFileURL(path.join(SRC_ROOT, 'profiles.mjs')).href);
378
+
379
+ // ① 契约断言:.github/ 归 owned;点前缀翻译 .github ↔ _github 命中、.agents 严格不回归、无模板 null
380
+ check('S16① isOwned:.github/ 前缀归 owned + 存量正例不回归(spec 验收 1)',
381
+ isOwned(CI_REL) === true && isOwned('.github/other.yml') === true
382
+ && isOwned('AGENTS.md') === true && isOwned('.agents/notes/x.md') === true && isOwned('workflow/plans/a.md') === true
383
+ && isOwned('.agents/scripts/check-loop.mjs') === false);
384
+ const fxC = mkFixture();
385
+ W(path.join(fxC, 'templates/_github/workflows/kit-ci.yml'), CI_TPL);
386
+ const norm = (p) => p.split(path.sep).join('/');
387
+ check('S16① srcTemplatePath 通用点前缀翻译:.github → _github 命中 / .agents 严格不回归',
388
+ norm(srcTemplatePath(fxC, CI_REL)).endsWith('templates/_github/workflows/kit-ci.yml')
389
+ && norm(srcTemplatePath(fxC, '.agents/scripts/keep.txt')).endsWith('templates/_agents/scripts/keep.txt'),
390
+ JSON.stringify({ ci: srcTemplatePath(fxC, CI_REL), agents: srcTemplatePath(fxC, '.agents/scripts/keep.txt') }));
391
+ check('S16① srcTemplatePath 无对应模板 → null(.gitattributes 反例,与旧实现等价)',
392
+ srcTemplatePath(fxC, '.gitattributes') === null);
393
+
394
+ // ② 存量装户(盘上无该件)→ newOwned 提示且不自动落盘
395
+ const fx1 = mkFixture();
396
+ W(path.join(fx1, 'templates/_github/workflows/kit-ci.yml'), CI_TPL);
397
+ const t1 = mkTarget(fx1);
398
+ const r1 = runCmd('sync', fx1, t1);
399
+ const out1 = r1.stdout + r1.stderr;
400
+ check('S16② 存量装户无该件 → newOwned 提示含文件名且不自动落盘',
401
+ out1.includes('新增 owned 起步文档') && out1.includes(CI_REL) && !fs.existsSync(path.join(t1, CI_REL)), out1);
402
+
403
+ // ③ 装户已装(带旧锚)+ 源演进 → 感知出账;锚刷新后(custom-synced)再跑静默
404
+ const fx2 = mkFixture();
405
+ W(path.join(fx2, 'templates/_github/workflows/kit-ci.yml'), CI_TPL);
406
+ const t2 = mkTarget(fx2);
407
+ W(path.join(t2, CI_REL), 'ci CUSTOM\n');
408
+ W(path.join(t2, '.agents/kit.json'), `${JSON.stringify({
409
+ kit: 'agentic-flow-kit', version: '1.0.0', createdAt: '2026-09-23T00:00:00Z',
410
+ options: { hosts: ['zcode'], stack: 'none', boardPort: '777' },
411
+ managed: [{ rel: '.agents/scripts/keep.txt', sha256: sha('keep v2\n') }],
412
+ owned: [{ rel: CI_REL, sha256: sha('ci CUSTOM\n'), srcSha256: sha('ci v1\n') }],
413
+ }, null, 2)}\n`);
414
+ const r2 = runCmd('sync', fx2, t2);
415
+ const out2 = r2.stdout + r2.stderr;
416
+ check('S16③ 源演进未跟随 → 感知出账含 .github rel', out2.includes('有演进且盘面未跟随') && out2.includes(CI_REL), out2.slice(0, 300));
417
+ check('S16③ 装户定制不被覆盖(盘面仍 CUSTOM)', R(path.join(t2, CI_REL)) === 'ci CUSTOM\n', out2);
418
+ const kit2 = JSON.parse(R(path.join(t2, '.agents/kit.json')));
419
+ check('S16③ 锚刷新为当前包源 sha', kit2.owned.find((f) => f.rel === CI_REL).srcSha256 === sha(CI_TPL), JSON.stringify(kit2.owned));
420
+ const r2b = runCmd('sync', fx2, t2);
421
+ check('S16③ 锚已刷(custom-synced)→ 再跑静默', !(r2b.stdout + r2b.stderr).includes('有演进且盘面未跟随'));
422
+ }
423
+
424
+ // ============ 场景 17:sync 一次自洽——生成器重写 owned 目标后按终态记账(2026-10-08 papercuts-cleanup-batch 修 1)============
425
+ // 修前:记账段(owned 自愈/感知/台账重写)先于生成器执行 → 生成器重写 wiki 类 owned 目标后台账停旧值
426
+ // → doctor §6.6 owned 漂移 FAIL 一次,需二次 sync 自愈。修后:生成器先跑,记账按生成后盘面收口。
427
+ {
428
+ const sha = (s) => sha256(Buffer.from(s, 'utf8'));
429
+ const GEN = "import fs from 'node:fs';\nfs.writeFileSync('wiki/INDEX.md', 'generated v2\\n');\n";
430
+ const fx = mkFixture();
431
+ W(path.join(fx, 'templates/_agents/scripts/gen-wiki-board.mjs'), GEN);
432
+ const t = mkTarget(fx);
433
+ W(path.join(t, '.agents/scripts/gen-wiki-board.mjs'), GEN); // 目标侧与包源渲染一致(genGuard/scriptTrusted 放行)
434
+ W(path.join(t, 'wiki/INDEX.md'), 'generated v1\n'); // 盘面停旧版——生成器将重写它
435
+ const k0 = JSON.parse(R(path.join(t, '.agents/kit.json')));
436
+ k0.managed.push({ rel: '.agents/scripts/gen-wiki-board.mjs', sha256: sha(GEN) });
437
+ k0.owned.push({ rel: 'wiki/INDEX.md', sha256: sha('generated v1\n') });
438
+ W(path.join(t, '.agents/kit.json'), `${JSON.stringify(k0, null, 2)}\n`);
439
+ const r = runCmd('sync', fx, t);
440
+ const out = r.stdout + r.stderr;
441
+ check('S17 一次 sync:生成器重写后 owned 台账按终态记账(wiki/INDEX.md → v2 sha)',
442
+ JSON.parse(R(path.join(t, '.agents/kit.json'))).owned.find((f) => f.rel === 'wiki/INDEX.md').sha256 === sha('generated v2\n'),
443
+ JSON.stringify(JSON.parse(R(path.join(t, '.agents/kit.json'))).owned) + out.slice(0, 300));
444
+ const { checkOwnedDrift } = await import(pathToFileURL(path.join(SRC_ROOT, 'doctor.mjs')).href);
445
+ const d = checkOwnedDrift(t);
446
+ check('S17 一次 sync 后 doctor owned 零漂移(修前必漂移需二次 sync)', d.drift === 0 && d.gone.length === 0, JSON.stringify(d));
447
+ const r2 = runCmd('sync', fx, t);
448
+ check('S17 二次 sync 幂等(无 owned 刷新报告)', !(r2.stdout + r2.stderr).includes('owned 台账哈希按盘面刷新'), r2.stdout + r2.stderr);
449
+ }
450
+
369
451
  console.log(`\n合计: PASS ${pass} / FAIL ${failCount}`);
370
452
  process.exit(failCount ? 1 : 0);
@@ -24,6 +24,7 @@
24
24
  ### 门禁与提交
25
25
 
26
26
  - `.githooks/`(经 `core.hooksPath` 挂载,本地 clone 后执行 `git config core.hooksPath .githooks`):pre-commit(闭环配对 / 泳道完整性 / wiki 台账 / 规则面预算 / 双源一致性 / managed 台账快检 / 敏感信息扫描与条件构建)、pre-push(闭环断档扫描)、commit-msg、post-commit、pre-merge-commit。
27
+ - **CI 远端门(2026-10-07 起)**:`.github/workflows/kit-ci.yml`(owned)在 GitHub runner 复跑 `node .agents/scripts/verify.mjs`(云端兜底本地钩子);非 GitHub 装户可删,等价门 = 同入口命令 + 服务端分支策略(人工一次性配置,机器不校验)。
27
28
  - **`git commit` / `git merge` / `git push` 三处一律禁 `--no-verify`**——被拦说明产出不合规,按提示修完原路重试。
28
29
  - 项目专属门禁挂 `.agents/hooks/local-pre-commit`。装户五条硬规则(配对、验收、确认留痕、敏感信息、双源台账)与 `audit` 档见 `workflow/README.md`「硬规则」。
29
30
  - 提交遵循 Conventional Commits 中文(feat / fix / docs / style / refactor / perf)。L1+ 三段式闭环链:`docs(*)` approved 留痕 → 代码 → 关单 `docs(*)`,相邻提交即同一闭环(2026-10-04 修订)。
@@ -75,7 +75,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
75
75
  - 输出测试报告(通过项 / 失败项)
76
76
  - 失败项已修复并复测通过
77
77
  - **关单**(日常闭环点,不依赖是否上 prod):
78
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
78
+ - 关单前一键过门:`node .agents/scripts/verify.mjs --doc workflow/intents/<本单>.md`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**`--doc` 把测试绿凭证绑到本单**——不带它落下的绿行不给任何单背书,confirm-doc 前置与检查 8 对账只认带本单 `--doc` 的绿行(仅 v6+ 生效);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
79
79
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
80
80
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
81
81
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
@@ -5,6 +5,8 @@
5
5
  # 口径:预算比较用「索引 / HEAD 字节」(git cat-file,LF 归一);禁用工作区 wc -c 核对(CRLF 膨胀)。
6
6
  # 2026-10-06 backflow-loop-audit-remediation:6 份命令文档顶部加「装户边界」标注(build.md 实测 8386B
7
7
  # 超 8192 被拦)——单篇上限 8192→8704(与装户 Shipyard.Material 同值,512B 步进);目录合计未超不动。
8
- AGENTS.md 7680
8
+ # 2026-10-07 adopter-ci-github:AGENTS.md 门禁节补 CI 远端门一行(299B,实测 7969B 超 7680 被拦)——
9
+ # 新门入册属合法增长,同 512B 步进上调 7680→8192。
10
+ AGENTS.md 8192
9
11
  .agents/commands/*.md 8704
10
12
  .agents/commands/ 65536
@@ -2,7 +2,10 @@
2
2
  /* agg-delegations: 量化证据聚合
3
3
  * 读 workflow/delegations.md 两张结果表(按表头签名识别:委派表含「被委派方」列、自做表=日期+任务一句话;不依赖 ## 节标题)+ 扫 workflow/incidents/ 按月计数,
4
4
  * 输出各月指标(一次通过率 / 平均返工次数 / 主兜底占比)、并发扩容门判定、可粘贴的月度快照行。
5
- * 结果取值:一次通过 | 返工×N | 主兜底 | 返工待修(未闭环,不计入率)。
5
+ * 结果取值:一次通过 | 返工×N | 返工×N(门禁噪声)| 主兜底 | 返工待修(未闭环,不计入率)。
6
+ * 门禁噪声档(2026-10-08 selfmeasure-and-modularize):剥离「返工由门禁面而非设计面触发」的形态——
7
+ * 扩容门第 3 项只对设计返工判 =0,门禁噪声返工单列观测。存量行零回填(旧四档解析逐条不变)。
8
+ * 取值定义与边界(何为门禁噪声 / 不得用于给设计返工贴标签)以 workflow/delegations.md 头部记法为准。
6
9
  * 用法:node .agents/scripts/agg-delegations.cjs [--month=YYYY-MM](默认输出全部月份 + 全量累计)
7
10
  * 门槛定义见 workflow/delegations.md §并发扩容门槛(doc 为权威,脚本向文档对齐——2026-09-27 board-kb-p1)。
8
11
  */
@@ -29,18 +32,38 @@ function parseTableRows(sectionText) {
29
32
  .filter((cols) => !/^\s*$/.test(cols[0]) && /^\d{4}-\d{2}-\d{2}$/.test(cols[0])); // 只留数据行(首列是日期;表头/分隔行随之滤除)
30
33
  }
31
34
 
35
+ // parseResult:结果列解析。返回值统一为 { kind, rework, reworkNoise } 两个分量。
36
+ // 三个返工格式(2026-10-08 rework-attribution-split 补第三种):
37
+ // 返工×N → 纯设计返工 { kind:'rework', rework:N, reworkNoise:0 }
38
+ // 返工×N(门禁噪声) → 纯门禁面噪声 { kind:'rework-noise', rework:0, reworkNoise:N }
39
+ // 返工×N + 门禁噪声×M → 行内双值(混合) { kind:'rework-mixed', rework:N, reworkNoise:M }
40
+ // 动机:存量逐行审计(29 行含返工)证实前两种单值格式**无法表达混合归因**——一行只记一个返工
41
+ // 总数时,「1 次设计 + 1 次噪声」只能整行倒向某一侧。行内双值是作者写入时确定已知的归因事实,
42
+ // 不从返工点数量反推(batch-ledger-audit 结论:判据读写入时事实,不猜)。
43
+ // ⚠️ 顺序约束:新分支须排在两个单值分支之前(`$` 锚定使三者互斥,此处按语义就近声明)。
32
44
  function parseResult(raw) {
33
45
  const r = (raw || '').trim();
34
- if (/^一次通过$/.test(r)) return { kind: 'pass', rework: 0 };
46
+ // 行内双值(混合归因):宽容空格(`返工×1+门禁噪声×1` 亦可解析)
47
+ const mx = r.match(/^返工×(\d+)\s*\+\s*门禁噪声×(\d+)$/);
48
+ if (mx) return { kind: 'rework-mixed', rework: parseInt(mx[1], 10), reworkNoise: parseInt(mx[2], 10) };
49
+ if (/^一次通过$/.test(r)) return { kind: 'pass', rework: 0, reworkNoise: 0 };
50
+ // 门禁噪声档(2026-10-08 selfmeasure-and-modularize):剥离「返工由门禁面(预算超限 / 双源漏刷 /
51
+ // 节名不一致 / 门禁自身误报)触发、而非设计面」的形态。动机:本仓 DASHBOARD 自报质量门连续两月红
52
+ // (一次通过率 55% / 61%,门槛 ≥90%),根因是两档混列——指标永远红且不指示该改什么。
53
+ const mn = r.match(/^返工×(\d+)(门禁噪声)$/);
54
+ if (mn) return { kind: 'rework-noise', rework: 0, reworkNoise: parseInt(mn[1], 10) };
35
55
  const m = r.match(/^返工×(\d+)$/);
36
- if (m) return { kind: 'rework', rework: parseInt(m[1], 10) };
37
- if (/^主兜底$/.test(r)) return { kind: 'fallback', rework: 1 };
38
- if (/^返工待修$/.test(r)) return { kind: 'pending', rework: 0 };
39
- return { kind: 'unknown', rework: 0 };
56
+ if (m) return { kind: 'rework', rework: parseInt(m[1], 10), reworkNoise: 0 };
57
+ if (/^主兜底$/.test(r)) return { kind: 'fallback', rework: 1, reworkNoise: 0 };
58
+ if (/^返工待修$/.test(r)) return { kind: 'pending', rework: 0, reworkNoise: 0 };
59
+ return { kind: 'unknown', rework: 0, reworkNoise: 0 };
40
60
  }
41
61
 
42
62
  // splitTables:按表头签名识别两张结果表——节标题只服务人类阅读,表头才是数据边界
43
63
  // (2026-09-24 修复:曾按 `## 委派结果` 节标题定位,节结构漂移时已有记录被静默读成「台账为空」)
64
+ // ⚠️ 互引(2026-10-08 papercuts-cleanup-batch 修 2):check-loop.mjs delegationResultRows 已对齐本签名
65
+ // 口径(委派表头=含「被委派方」列 / 自做表头=首列「日期」且含「任务一句话」列)——一边改另一边须跟;
66
+ // 差异保留:本函数按「连续 | 行」断表(聚合需分行归属),check-loop 按「## 节」终止收集(对账只需并集)
44
67
  function splitTables(text) {
45
68
  const tables = { delegated: [], self: [] };
46
69
  let cur = null;
@@ -54,9 +77,15 @@ function splitTables(text) {
54
77
  return tables;
55
78
  }
56
79
 
57
- function readLedger() {
58
- if (!fs.existsSync(LEDGER)) fail(`找不到 ${LEDGER}`);
59
- const text = fs.readFileSync(LEDGER, 'utf8');
80
+ // readLedger:root 显式参数 + soft 模式(2026-10-08 workflow-dashboard 使能导出,判据单源复用——
81
+ // 仪表盘 require 本函数取行,不重写表解析;缺省 root/无 soft 行为与原签名完全一致,main() 零变化)
82
+ function readLedger(root = ROOT, opts = {}) {
83
+ const ledger = path.join(root, 'workflow', 'delegations.md');
84
+ if (!fs.existsSync(ledger)) {
85
+ if (opts.soft) return [];
86
+ fail(`找不到 ${ledger}`);
87
+ }
88
+ const text = fs.readFileSync(ledger, 'utf8');
60
89
  const tables = splitTables(text);
61
90
  const delegated = parseTableRows(tables.delegated.join('\n')).map((c) => ({
62
91
  scope: '委派',
@@ -73,7 +102,8 @@ function readLedger() {
73
102
  const rows = delegated.concat(selfDone);
74
103
  // 零数据行时区分「台账真空」与「结构漂移」:有日期开头的数据行却识别不到表 → fail-loud,不再静默「台账为空」
75
104
  if (!rows.length && /^\s*\|\s*\d{4}-\d{2}-\d{2}\s*\|/m.test(text)) {
76
- fail(`台账结构漂移:${LEDGER} 存在日期开头的数据行但未识别到结果表(委派表表头须含「被委派方」列,自做表表头须为「日期 | 任务一句话 | 结果 | 备注」)`);
105
+ if (opts.soft) return [];
106
+ fail(`台账结构漂移:${ledger} 存在日期开头的数据行但未识别到结果表(委派表表头须含「被委派方」列,自做表表头须为「日期 | 任务一句话 | 结果 | 备注」)`);
77
107
  }
78
108
  return rows;
79
109
  }
@@ -89,7 +119,7 @@ function countIncidents() {
89
119
  }
90
120
 
91
121
  // 门槛(2026-09-27 board-kb-p1 对齐 workflow/delegations.md §并发扩容门槛声明——doc 为权威):
92
- // 月度口径项:一次完成率 ≥90% | 月度返工次数 = 0 | 无主兜底信号(=0)(旧「平均返工≤0.3 / 兜底≤10%」为宽松分叉,退役)
122
+ // 月度口径项:一次完成率 ≥90% | 月度**设计**返工次数 = 0(2026-10-08 起门禁噪声返工剥离单列)| 无主兜底信号(=0)(旧「平均返工≤0.3 / 兜底≤10%」为宽松分叉,退役)
93
123
  // 样本护栏项(脚本侧统计可信度门槛,非口径分叉):月样本量 ≥20 | 委派样本 ≥5
94
124
  // 连续性:扩容需「本月 + 上一自然月」连续两月月度口径全达标(delegations.md「连续两个月」)
95
125
  const GATE = { minSample: 20, minPassRate: 0.9, minDelegated: 5 };
@@ -100,12 +130,17 @@ function metrics(rows) {
100
130
  const valid = rows.filter((_, i) => kinds[i] !== 'pending' && kinds[i] !== 'unknown');
101
131
  const pending = kinds.filter((k) => k === 'pending').length;
102
132
  const unknown = kinds.filter((k) => k === 'unknown').length;
103
- let pass = 0, reworkSum = 0, fallback = 0;
133
+ let pass = 0, reworkSum = 0, reworkNoiseSum = 0, fallback = 0;
104
134
  for (const r of valid) {
105
135
  const p = parseResult(r.result);
106
136
  if (p.kind === 'pass') pass += 1;
107
137
  if (p.kind === 'fallback') fallback += 1;
108
- reworkSum += p.rework;
138
+ // 双列累计:设计返工与门禁噪声返工分列(2026-10-08 selfmeasure-and-modularize 引入分列,
139
+ // 2026-10-08 rework-attribution-split 加行内混合档)。
140
+ // 显式列 kind 而非 `reworkSum += p.rework`——否则后续新增 kind 会被静默并入设计返工,
141
+ // 且混合档的两个分量必须各走各的累加器(只加 rework 会漏掉噪声)。
142
+ if (p.kind === 'rework' || p.kind === 'rework-mixed') reworkSum += p.rework;
143
+ if (p.kind === 'rework-noise' || p.kind === 'rework-mixed') reworkNoiseSum += p.reworkNoise;
109
144
  }
110
145
  const delegatedValid = valid.filter((r) => r.scope === '委派');
111
146
  return {
@@ -115,7 +150,8 @@ function metrics(rows) {
115
150
  pass,
116
151
  passRate: valid.length ? pass / valid.length : null,
117
152
  reworkSum,
118
- avgRework: valid.length ? reworkSum / valid.length : null,
153
+ reworkNoiseSum,
154
+ avgRework: valid.length ? (reworkSum + reworkNoiseSum) / valid.length : null,
119
155
  delegatedValid: delegatedValid.length,
120
156
  fallback,
121
157
  fallbackRate: delegatedValid.length ? fallback / delegatedValid.length : null,
@@ -134,7 +170,10 @@ function gateMonth(m) {
134
170
  const items = [];
135
171
  items.push({ no: 1, ok: m.total >= GATE.minSample, desc: `样本量 ${m.total}/${GATE.minSample}`, kind: '样本护栏' });
136
172
  items.push({ no: 2, ok: m.passRate != null && m.passRate >= GATE.minPassRate, desc: `一次完成率 ${fmtRate(m.passRate)}≥90%`, kind: '口径' });
137
- items.push({ no: 3, ok: m.reworkSum === 0, desc: `月度返工次数 ${m.reworkSum}=0`, kind: '口径' });
173
+ // 第 3 项判据 2026-10-08 语义变更:只对**设计返工**判 =0;门禁噪声返工单列在描述里(数字仍可见不隐藏,
174
+ // 但不参与 ok 判定——两类混判是本仓质量门连续两月红的根因)。⚠️ 不可据此给设计返工贴噪声标签洗白指标:
175
+ // 该取值由作者手写、机器无法判真伪(同 --delegated 信任边界),口径约束见 delegations.md 头部。
176
+ items.push({ no: 3, ok: m.reworkSum === 0, desc: `月度设计返工 ${m.reworkSum}=0(门禁噪声返工 ${m.reworkNoiseSum || 0} 次已剥离单列)`, kind: '口径' });
138
177
  items.push({ no: 4, ok: m.fallback === 0, desc: `主兜底 ${m.fallback}=0(无主兜底信号)`, kind: '口径' });
139
178
  items.push({ no: 5, ok: m.delegatedValid >= GATE.minDelegated, desc: `委派样本 ${m.delegatedValid}≥5`, kind: '样本护栏' });
140
179
  items.push({ no: 6, ok: null, desc: '门禁不失守(人工对照当月 incident 定性)', kind: '人工' });
@@ -173,7 +212,7 @@ function main() {
173
212
  const prevYm = (() => { const [y, mo] = ym.split('-').map(Number); const d = new Date(Date.UTC(y, mo - 2, 1)); return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`; })();
174
213
  const verdict = expansionVerdict(monthOk, monthResults.get(prevYm), !months.includes(prevYm), items);
175
214
  console.log(`## ${ym}`);
176
- console.log(` 有效任务 ${m.total}(待修 ${m.pending}${m.unknown ? `、未知结果 ${m.unknown}` : ''})|一次通过 ${m.pass}(${fmtRate(m.passRate)})|返工总次数 ${m.reworkSum}(平均 ${fmtNum(m.avgRework)})|主兜底 ${m.fallback}/${m.delegatedValid}(${fmtRate(m.fallbackRate)})|incident ${incidents[ym] || 0} 起`);
215
+ console.log(` 有效任务 ${m.total}(待修 ${m.pending}${m.unknown ? `、未知结果 ${m.unknown}` : ''})|一次通过 ${m.pass}(${fmtRate(m.passRate)})|设计返工 ${m.reworkSum} 次 / 门禁噪声返工 ${m.reworkNoiseSum} 次(合计返工 ${m.reworkSum + m.reworkNoiseSum},平均 ${fmtNum(m.avgRework)})|主兜底 ${m.fallback}/${m.delegatedValid}(${fmtRate(m.fallbackRate)})|incident ${incidents[ym] || 0} 起`);
177
216
  for (const i of items) console.log(` 门${i.no}[${i.kind}] ${i.ok === null ? '[人工]' : i.ok ? '✅' : '❌'} ${i.desc}`);
178
217
  console.log(` 扩容门判定:${verdict}\n`);
179
218
  snapshotRows.push(`| ${ym} | ${m.total} | ${fmtRate(m.passRate)} | ${fmtNum(m.avgRework)} | ${fmtRate(m.fallbackRate)} | ${incidents[ym] || 0} | ${verdict} | 样本含待修${m.pending}${m.unknown ? `、未知${m.unknown}` : ''} |`);
@@ -197,5 +236,5 @@ function main() {
197
236
  if (require.main === module) {
198
237
  main();
199
238
  } else {
200
- module.exports = { parseResult, metrics, gateMonth, expansionVerdict, GATE };
239
+ module.exports = { parseResult, metrics, gateMonth, expansionVerdict, GATE, readLedger };
201
240
  }