agentic-flow-kit 1.4.0 → 1.6.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 (140) hide show
  1. package/modules/hosts/claude/agents/implementer.md +5 -6
  2. package/modules/hosts/claude/agents/independent-reviewer.md +6 -6
  3. package/modules/hosts/claude/agents/ui-verifier.md +5 -5
  4. package/modules/hosts/claude/commands/wf-build.md +28 -21
  5. package/modules/hosts/claude/commands/wf-deploy.md +2 -2
  6. package/modules/hosts/claude/commands/wf-design.md +14 -8
  7. package/modules/hosts/claude/commands/wf-gate-checklist.md +8 -8
  8. package/modules/hosts/claude/commands/wf-maintain.md +12 -12
  9. package/modules/hosts/claude/commands/wf-new-task.md +11 -9
  10. package/modules/hosts/claude/commands/wf-pipeline-run.md +3 -3
  11. package/modules/hosts/claude/commands/wf-plan.md +10 -11
  12. package/modules/hosts/claude/commands/wf-review.md +4 -4
  13. package/modules/hosts/claude/commands/wf-source-sync-check.md +10 -10
  14. package/modules/hosts/claude/commands/wf-sync-hosts.md +9 -9
  15. package/modules/hosts/claude/commands/wf-test.md +5 -6
  16. package/modules/hosts/codex/agents/implementer.md +5 -6
  17. package/modules/hosts/codex/agents/independent-reviewer.md +6 -6
  18. package/modules/hosts/codex/agents/ui-verifier.md +5 -5
  19. package/modules/hosts/codex/commands/wf-build.md +28 -21
  20. package/modules/hosts/codex/commands/wf-deploy.md +2 -2
  21. package/modules/hosts/codex/commands/wf-design.md +14 -8
  22. package/modules/hosts/codex/commands/wf-gate-checklist.md +8 -8
  23. package/modules/hosts/codex/commands/wf-maintain.md +12 -12
  24. package/modules/hosts/codex/commands/wf-new-task.md +11 -9
  25. package/modules/hosts/codex/commands/wf-pipeline-run.md +3 -3
  26. package/modules/hosts/codex/commands/wf-plan.md +10 -11
  27. package/modules/hosts/codex/commands/wf-review.md +4 -4
  28. package/modules/hosts/codex/commands/wf-source-sync-check.md +10 -10
  29. package/modules/hosts/codex/commands/wf-sync-hosts.md +9 -9
  30. package/modules/hosts/codex/commands/wf-test.md +5 -6
  31. package/modules/hosts/cursor/agents/implementer.md +5 -6
  32. package/modules/hosts/cursor/agents/independent-reviewer.md +6 -6
  33. package/modules/hosts/cursor/agents/ui-verifier.md +5 -5
  34. package/modules/hosts/cursor/commands/wf-build.md +28 -21
  35. package/modules/hosts/cursor/commands/wf-deploy.md +2 -2
  36. package/modules/hosts/cursor/commands/wf-design.md +14 -8
  37. package/modules/hosts/cursor/commands/wf-gate-checklist.md +8 -8
  38. package/modules/hosts/cursor/commands/wf-maintain.md +12 -12
  39. package/modules/hosts/cursor/commands/wf-new-task.md +11 -9
  40. package/modules/hosts/cursor/commands/wf-pipeline-run.md +3 -3
  41. package/modules/hosts/cursor/commands/wf-plan.md +10 -11
  42. package/modules/hosts/cursor/commands/wf-review.md +4 -4
  43. package/modules/hosts/cursor/commands/wf-source-sync-check.md +10 -10
  44. package/modules/hosts/cursor/commands/wf-sync-hosts.md +9 -9
  45. package/modules/hosts/cursor/commands/wf-test.md +5 -6
  46. package/modules/hosts/omp/agents/implementer.md +5 -6
  47. package/modules/hosts/omp/agents/independent-reviewer.md +6 -6
  48. package/modules/hosts/omp/agents/ui-verifier.md +5 -5
  49. package/modules/hosts/opencode/agents/implementer.md +5 -6
  50. package/modules/hosts/opencode/agents/independent-reviewer.md +6 -6
  51. package/modules/hosts/opencode/agents/ui-verifier.md +5 -5
  52. package/modules/hosts/opencode/commands/wf-build.md +28 -21
  53. package/modules/hosts/opencode/commands/wf-deploy.md +2 -2
  54. package/modules/hosts/opencode/commands/wf-design.md +14 -8
  55. package/modules/hosts/opencode/commands/wf-gate-checklist.md +8 -8
  56. package/modules/hosts/opencode/commands/wf-maintain.md +12 -12
  57. package/modules/hosts/opencode/commands/wf-new-task.md +11 -9
  58. package/modules/hosts/opencode/commands/wf-pipeline-run.md +3 -3
  59. package/modules/hosts/opencode/commands/wf-plan.md +10 -11
  60. package/modules/hosts/opencode/commands/wf-review.md +4 -4
  61. package/modules/hosts/opencode/commands/wf-source-sync-check.md +10 -10
  62. package/modules/hosts/opencode/commands/wf-sync-hosts.md +9 -9
  63. package/modules/hosts/opencode/commands/wf-test.md +5 -6
  64. package/modules/hosts/trae/agents/implementer.md +5 -6
  65. package/modules/hosts/trae/agents/independent-reviewer.md +6 -6
  66. package/modules/hosts/trae/agents/ui-verifier.md +5 -5
  67. package/modules/hosts/trae/commands/wf-build.md +28 -21
  68. package/modules/hosts/trae/commands/wf-deploy.md +2 -2
  69. package/modules/hosts/trae/commands/wf-design.md +14 -8
  70. package/modules/hosts/trae/commands/wf-gate-checklist.md +8 -8
  71. package/modules/hosts/trae/commands/wf-maintain.md +12 -12
  72. package/modules/hosts/trae/commands/wf-new-task.md +11 -9
  73. package/modules/hosts/trae/commands/wf-pipeline-run.md +3 -3
  74. package/modules/hosts/trae/commands/wf-plan.md +10 -11
  75. package/modules/hosts/trae/commands/wf-review.md +4 -4
  76. package/modules/hosts/trae/commands/wf-source-sync-check.md +10 -10
  77. package/modules/hosts/trae/commands/wf-sync-hosts.md +9 -9
  78. package/modules/hosts/trae/commands/wf-test.md +5 -6
  79. package/modules/hosts/zcode/agents/implementer.md +5 -6
  80. package/modules/hosts/zcode/agents/independent-reviewer.md +6 -6
  81. package/modules/hosts/zcode/agents/ui-verifier.md +5 -5
  82. package/package.json +7 -2
  83. package/src/doctor.mjs +1 -1
  84. package/src/init.test.mjs +1 -1
  85. package/src/run-tests.mjs +15 -1
  86. package/src/sync-hosts.mjs +100 -62
  87. package/src/sync-hosts.test.mjs +43 -4
  88. package/src/sync.test.mjs +1 -1
  89. package/templates/_agents/commands/build.md +28 -21
  90. package/templates/_agents/commands/deploy.md +2 -2
  91. package/templates/_agents/commands/design.md +14 -8
  92. package/templates/_agents/commands/gate-checklist.md +8 -8
  93. package/templates/_agents/commands/maintain.md +12 -12
  94. package/templates/_agents/commands/new-task.md +11 -9
  95. package/templates/_agents/commands/pipeline-run.md +3 -3
  96. package/templates/_agents/commands/plan.md +10 -11
  97. package/templates/_agents/commands/review.md +4 -4
  98. package/templates/_agents/commands/source-sync-check.md +10 -10
  99. package/templates/_agents/commands/sync-hosts.md +9 -9
  100. package/templates/_agents/commands/test.md +5 -6
  101. package/templates/_agents/roles/implementer.md +5 -6
  102. package/templates/_agents/roles/independent-reviewer.md +6 -6
  103. package/templates/_agents/roles/ui-verifier.md +5 -5
  104. package/templates/_agents/scripts/agg-delegations.cjs +0 -1
  105. package/templates/_agents/scripts/agg-delegations.test.mjs +2 -2
  106. package/templates/_agents/scripts/check-hygiene.mjs +2 -2
  107. package/templates/_agents/scripts/check-ledger-invariant.mjs +0 -1
  108. package/templates/_agents/scripts/check-loop.mjs +115 -38
  109. package/templates/_agents/scripts/check-loop.test.mjs +67 -1
  110. package/templates/_agents/scripts/check-stage-index.mjs +40 -0
  111. package/templates/_agents/scripts/check-stage-index.test.mjs +77 -0
  112. package/templates/_agents/scripts/commit-check-trigger.test.mjs +0 -1
  113. package/templates/_agents/scripts/confirm-doc.mjs +17 -3
  114. package/templates/_agents/scripts/confirm-doc.test.mjs +17 -0
  115. package/templates/_agents/scripts/edit-face-check.mjs +107 -0
  116. package/templates/_agents/scripts/edit-face-check.test.mjs +98 -0
  117. package/templates/_agents/scripts/ensure-board.mjs +2 -2
  118. package/templates/_agents/scripts/fill-intent.mjs +34 -5
  119. package/templates/_agents/scripts/fill-intent.test.mjs +13 -0
  120. package/templates/_agents/scripts/fill-plan.mjs +1 -2
  121. package/templates/_agents/scripts/gen-wiki-board.mjs +1 -1
  122. package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +0 -2
  123. package/templates/_agents/scripts/gen-workflow-index.test.mjs +1 -1
  124. package/templates/_agents/scripts/gen-workflow-metrics.mjs +53 -2
  125. package/templates/_agents/scripts/kb-search.mjs +1 -2
  126. package/templates/_agents/scripts/kb-search.test.mjs +1 -1
  127. package/templates/_agents/scripts/pipeline-run-docs.mjs +76 -0
  128. package/templates/_agents/scripts/pipeline-run-run.mjs +63 -0
  129. package/templates/_agents/scripts/pipeline-run.mjs +8 -128
  130. package/templates/_agents/scripts/policy.mjs +20 -1
  131. package/templates/_agents/scripts/source-sync-check.mjs +2 -2
  132. package/templates/_agents/scripts/source-sync-check.test.mjs +1 -1
  133. package/templates/_agents/scripts/verify-wiki-consistency.mjs +0 -1
  134. package/templates/_agents/scripts/verify-wiki-consistency.test.mjs +81 -0
  135. package/templates/_agents/scripts/verify.test.mjs +2 -2
  136. package/templates/_agents/scripts/wiki-search.test.mjs +58 -0
  137. package/templates/_agents/scripts/workflow-board-server.test.mjs +0 -1
  138. package/templates/_agents/scripts/workflow-enums.test.mjs +0 -1
  139. package/templates/_agents/scripts-test-exempt.txt +5 -3
  140. package/templates/_github/workflows/kit-ci.yml +21 -1
@@ -25,16 +25,16 @@ next: .agents/commands/build.md(漂移属 build 改权威源未同步导致的
25
25
  > 比 `templates/_agents` ↔ `modules/hosts/<宿主键>/`;含 `.agents` → 装户:比 `.agents` ↔ 项目根下 `<宿主点目录>/`),
26
26
  > 目标根取 `--dir <目录>` > 当前目录。
27
27
 
28
- ## 何时跑
28
+ ## 1. 何时跑
29
29
 
30
30
  - 改 `templates/_agents/commands/*.md` 正文后(11 个 commands 文件)
31
31
  - 改 `templates/_agents/roles/*.md` 正文后(3 个 roles 文件)
32
32
  - 装户跑 `flow-kit doctor` 报"跨宿主薄适配正文漂移 N 对" WARN 时
33
33
  - 不跑:仅改 frontmatter;改 trae 钩子/规则;改包源其他文件
34
34
 
35
- ## 怎么跑
35
+ ## 2. 怎么跑
36
36
 
37
- ### 默认 `--diff`(仅报告)
37
+ ### 2.1 默认 `--diff`(仅报告)
38
38
 
39
39
  ```
40
40
  node bin/flow-kit.mjs sync-hosts --diff # 包源仓(在包源根跑)
@@ -48,7 +48,7 @@ flow-kit sync-hosts --diff # 装户仓(在装户根跑;或
48
48
  - **⚠️ 权威源声明但薄适配缺失** — `add-host <h>` 接管后跑 `--apply` 才会装(apply 不自动创建)
49
49
  - **⚠️ 孤儿薄适配(权威源无对应文件)** — 历史残留或宿主特化,apply 不删
50
50
 
51
- ### `--apply` 单向同步
51
+ ### 2.2 `--apply` 单向同步
52
52
 
53
53
  ```
54
54
  node bin/flow-kit.mjs sync-hosts --apply
@@ -56,7 +56,7 @@ node bin/flow-kit.mjs sync-hosts --apply
56
56
 
57
57
  按权威源正文覆盖薄适配正文段;薄适配原 frontmatter 保留(trae name: wf-X、opencode 描述、zcode/omp 中文风格各自不动)。apply 不反向同步——薄适配手改了正文也会被覆盖(这是设计选择,避免漂移方向歧义)。
58
58
 
59
- ### 自验
59
+ ### 2.3 自验
60
60
 
61
61
  ```
62
62
  npm test # 含 sync-hosts.test.mjs 8 场景
@@ -64,7 +64,7 @@ node bin/flow-kit.mjs doctor # 装户跑 §7.x PASS;包源
64
64
  sh .agents/scripts/check-loop.sh # Windows 无 sh 时 doctor 已探测分流
65
65
  ```
66
66
 
67
- ## 边界(不要做)
67
+ ## 3. 边界(不要做)
68
68
 
69
69
  - 不要 `--apply` 反向同步(写权威源):权威源是真相,薄适配手改会被覆盖——这是设计,apply 前先看 `--diff`
70
70
  - 不要删除孤儿薄适配:可能是宿主特化或历史残留,apply 不动
@@ -72,14 +72,14 @@ sh .agents/scripts/check-loop.sh # Windows 无 sh 时 doctor 已
72
72
  - 不要把「避免与权威版本漂移」明文加回去——这是旧契约的指针风格,已被 B-b 正文副本取代
73
73
  - 装了新宿主后跑 `--apply`:新宿主目录空白,apply 不会自动创建薄适配(薄适配须经 `add-host <h>` 接管)
74
74
 
75
- ## 不在本次同步范围
75
+ ## 4. 不在本次同步范围
76
76
 
77
77
  - trae 钩子(`modules/hosts/trae/hooks/*.cjs`)— 独立演进的工程件
78
78
  - trae 规则(`modules/hosts/trae/rules/*.md`)— 同上
79
79
  - 包源 .agents/scripts/*.cjs(pre-commit 等钩子)— 同步由 `flow-kit sync` 走 managed 台账负责
80
80
 
81
- ## 相关留痕
81
+ ## 5. 相关留痕
82
82
 
83
83
  - intent:`workflow/intents/2026-09-25-cross-host-sync.md`
84
84
  - plan:`workflow/plans/2026-09-25-cross-host-sync.md`(含 B-b 方案拍板)
85
- - incident:`workflow/incidents/2026-09-25-wf-runtime.md`(双源纪律教训,规范 §7.x 包源环境跳过)
85
+ - incident:`workflow/incidents/2026-09-25-wf-runtime.md`(双源纪律教训,规范 §7.x 包源环境跳过)
@@ -65,20 +65,19 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
65
65
  - 复核者读入口文档(intent / incident) + spec + plan + diff,判断变更意图与风险
66
66
  - 复核意见交用户定性(采纳 / 驳回);采纳项回 `build.md` 修改
67
67
 
68
- ## 子代理调用约定
68
+ ## 5. 子代理调用约定
69
69
 
70
70
  - 静态门不委派;UI 实测委派 `ui-verifier`(静态门通过后)、独立复核委派 `independent-reviewer`,均要求读取对应 `.agents/roles/*.md`;宿主不支持时按 frontmatter `fallback`(reviewer 必须另开新会话,不得主智能体自查冒充)。
71
71
  - 子智能体只返回证据与结论——失败项修复仍回 `build.md`,不得在 Test 阶段顺手改代码;修复后重跑本阶段,全过后用户确认再关单。
72
72
 
73
- ## 确认后
73
+ ## 6. 确认后(关单)
74
74
 
75
- - 输出测试报告(通过项 / 失败项)
76
- - 失败项已修复并复测通过
75
+ - 输出测试报告(通过项 / 失败项);失败项已修复并复测通过。
77
76
  - **关单**(日常闭环点,不依赖是否上 prod):
78
77
  - 关单前一键过门:`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
- - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
78
+ - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent → done;同名 spec/Quick-Plan(若有)仍 approved → done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
80
79
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
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。
80
+ - **确认落态**:唯一入口 `node .agents/scripts/confirm-doc.mjs <path>`(两形态 / 逐件+逐件原话 / done 内容绑定(关单编辑先于 done 确认、confirm-doc 是最后一次写入)/ 台账对质全口径见根 `AGENTS.md` §1.3 确认门)。
82
81
  - 修复:防复发验证已落地,incident → closed(经确认门 confirm-doc:open→fixed / fixed→closed 两跳、无单跳;2026-09-28 起 check-loop 15 对账——关单编辑先于 closed 确认,口径同 done 内容绑定)
83
82
  - **同族收尾**:入口置终态时,同名 plan 一并置 `done`(spec 见上条),不留 `approved` 孤儿(口径同看板「入口已 done,本 plan 未终态」告警)
84
83
  - 主智能体自做的 L1+ 新需求在 `workflow/delegations.md`「自做任务结果表」记一行(修复类不重复记)
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 公共角色契约(本文件):`.agents/roles/implementer.md`。宿主薄适配(各宿主目录下 `agents/implementer.md`)正文段同源本文件,frontmatter 各自保留宿主特化字段(check-loop 检查项 6 按本引用校验适配层不断线)。
4
4
 
5
- ## 职责
5
+ ## 1. 职责
6
6
 
7
7
  执行已经由主智能体和用户确认、范围明确的实现任务。适用范围仅限:
8
8
 
@@ -11,7 +11,7 @@
11
11
  - 编写或调整任务直接要求的测试
12
12
  - 修复与任务直接相关的编译错误
13
13
 
14
- ## 必需输入
14
+ ## 2. 必需输入
15
15
 
16
16
  开始前必须获得:
17
17
 
@@ -22,7 +22,7 @@
22
22
 
23
23
  任一输入缺失或相互冲突时,停止并向主智能体报告,不自行扩大范围。
24
24
 
25
- ## 行为边界
25
+ ## 3. 行为边界
26
26
 
27
27
  - 只修改明确授权的文件;若必须触及其他文件,先停止并说明原因。
28
28
  - 仓库可能有其他智能体或用户同时工作;保留现有改动,不回退、不覆盖无关修改。
@@ -31,13 +31,13 @@
31
31
  - 不执行提交、推送、发布、数据迁移或破坏性操作。
32
32
  - 不顺手重构、格式化或清理与任务无关的代码。
33
33
 
34
- ## 验证
34
+ ## 4. 验证
35
35
 
36
36
  - 运行任务指定的最小相关检查。
37
37
  - 检查失败时只修复由本次改动引起且仍在授权范围内的问题。
38
38
  - 同一验证连续失败三次,或继续需要猜测需求时,停止并报告阻塞。
39
39
 
40
- ## 输出
40
+ ## 5. 输出
41
41
 
42
42
  返回给主智能体:
43
43
 
@@ -45,4 +45,3 @@
45
45
  2. 每个文件的改动摘要
46
46
  3. 已运行的验证及结果
47
47
  4. 未解决的阻塞或剩余风险
48
-
@@ -2,11 +2,11 @@
2
2
 
3
3
  > 公共角色契约(本文件):`.agents/roles/independent-reviewer.md`。宿主薄适配(各宿主目录下 `agents/independent-reviewer.md`)正文段同源本文件,frontmatter 各自保留宿主特化字段(check-loop 检查项 6 按本引用校验适配层不断线)。
4
4
 
5
- ## 职责
5
+ ## 1. 职责
6
6
 
7
7
  在独立上下文中对 spec、plan 或代码 diff 做只读复核,发现原作者可能遗漏的正确性、契约、架构、数据安全和回归风险。
8
8
 
9
- ## 必需输入
9
+ ## 2. 必需输入
10
10
 
11
11
  开始前必须获得:
12
12
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  输入不足以确定范围时,停止并向主智能体报告。
19
19
 
20
- ## 行为边界
20
+ ## 3. 行为边界
21
21
 
22
22
  - 只读,不修改任何仓库文件。
23
23
  - 不替用户决定问题定性、方案取舍、合入或上线。
@@ -26,7 +26,7 @@
26
26
  - 不把样式偏好、假设性风险或既有无关问题包装成缺陷。
27
27
  - 无法验证的事项明确标为未验证,不声称已经通过。
28
28
 
29
- ## 复核重点
29
+ ## 4. 复核重点
30
30
 
31
31
  1. 实现是否满足入口文档和验收标准
32
32
  2. 是否违反根 `AGENTS.md` 与改动域目录级 `AGENTS.md` 的架构、数据和所在域红线
@@ -34,7 +34,7 @@
34
34
  4. 是否缺少能防止回归的验证
35
35
  5. 是否存在数据丢失、安全、权限或发布风险
36
36
 
37
- ## 输出
37
+ ## 5. 输出
38
38
 
39
39
  按严重度输出问题清单:
40
40
 
@@ -42,4 +42,4 @@
42
42
  - `P1`:功能错误、明确回归或违反项目硬约束
43
43
  - `P2`:非阻断但有证据的维护性问题
44
44
 
45
- 每项必须包含:位置、问题、证据、建议。没有发现时明确输出“未发现阻断问题”,并列出未验证范围。
45
+ 每项必须包含:位置、问题、证据、建议。没有发现时明确输出"未发现阻断问题",并列出未验证范围。
@@ -2,11 +2,11 @@
2
2
 
3
3
  > 公共角色契约(本文件):`.agents/roles/ui-verifier.md`。宿主薄适配(各宿主目录下 `agents/ui-verifier.md`)正文段同源本文件,frontmatter 各自保留宿主特化字段(check-loop 检查项 6 按本引用校验适配层不断线)。
4
4
 
5
- ## 职责
5
+ ## 1. 职责
6
6
 
7
7
  对已经实现的 UI 改动执行浏览器实测并收集可复核证据。只验证,不修复。
8
8
 
9
- ## 必需输入
9
+ ## 2. 必需输入
10
10
 
11
11
  开始前必须获得:
12
12
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  缺少可观察的预期结果时,停止并向主智能体报告。
19
19
 
20
- ## 执行
20
+ ## 3. 执行
21
21
 
22
22
  1. 读取项目根目录 `AGENTS.md`,以及改动域目录级 `AGENTS.md`(如有,前端改动域)。
23
23
  2. 读取并遵循项目 UI 实测技能 `.agents/skills/verify-ui/SKILL.md`(如有)。
@@ -26,14 +26,14 @@
26
26
  5. 收集控制台、网络请求、DOM 数据或截图等证据。
27
27
  6. 结束时关闭本次启动的后台进程。
28
28
 
29
- ## 行为边界
29
+ ## 4. 行为边界
30
30
 
31
31
  - 不修改代码、配置、测试数据或工作流文档。
32
32
  - 不自行修复发现的问题;将失败证据交还主智能体。
33
33
  - 不使用生产环境或真实业务数据。
34
34
  - 无法启动环境或完成关键路径时,报告阻塞,不把部分验证标记为通过。
35
35
 
36
- ## 输出
36
+ ## 5. 输出
37
37
 
38
38
  返回:
39
39
 
@@ -14,7 +14,6 @@ const fs = require('fs');
14
14
  const path = require('path');
15
15
 
16
16
  const ROOT = path.resolve(__dirname, '..', '..');
17
- const LEDGER = path.join(ROOT, 'workflow', 'delegations.md');
18
17
  const INCIDENTS = path.join(ROOT, 'workflow', 'incidents');
19
18
 
20
19
  const argMonth = (process.argv.find((a) => a.startsWith('--month=')) || '').split('=')[1] || null;
@@ -96,8 +96,8 @@ const { metrics, gateMonth, expansionVerdict, parseResult } = requireCjs(path.jo
96
96
  // 场景 4:审查反例——30 任务、2 主兜底、平均返工 0.25(旧口径四门全过,声明口径下必须 ❌)
97
97
  {
98
98
  const rows = [
99
- ...Array.from({ length: 22 }, (_, i) => ({ scope: '委派', result: '一次通过' })),
100
- ...Array.from({ length: 6 }, (_, i) => ({ scope: '委派', result: '返工×1' })),
99
+ ...Array.from({ length: 22 }, () => ({ scope: '委派', result: '一次通过' })),
100
+ ...Array.from({ length: 6 }, () => ({ scope: '委派', result: '返工×1' })),
101
101
  { scope: '委派', result: '主兜底' }, { scope: '委派', result: '主兜底' },
102
102
  ];
103
103
  const m = metrics(rows);
@@ -85,7 +85,7 @@ export function runCheckHygiene(ctx) {
85
85
  }
86
86
  }
87
87
 
88
- // --- 9. 文件名英文 kebab-case [warning] ---
88
+ // --- 9. 文件名英文 kebab-case [warning](2026-09-11 规则)---
89
89
  markGate('9', '文件名 kebab-case');
90
90
  for (const sub of DOC_DIRS) {
91
91
  const dir = path.join(ROOT, WF, sub);
@@ -99,7 +99,7 @@ export function runCheckHygiene(ctx) {
99
99
  }
100
100
  }
101
101
 
102
- // --- 11. 生成物漂移 [warning](调生成器 --check,口径单一不复刻渲染)---
102
+ // --- 11. 生成物漂移 [warning](调生成器 --check,口径单一不复刻渲染;INDEX 2026-09-21 检索层 / DASHBOARD 2026-10-08 扩覆盖面)---
103
103
  markGate('11', '生成物漂移(INDEX+DASHBOARD)');
104
104
  // ⚠️ 位置:本段须排在 12/13 之前——check-loop 输出按 warnings 插入序,行序是稳定输出契约
105
105
  // (pre-push / pre-commit 消费者 + doctor 按 WARN 行计数);原序为 2 → 9 → 11 → 12 → 13,勿调换。
@@ -41,7 +41,6 @@ const gitText = (args) => execFileSync('git', args, { cwd: ROOT, encoding: 'utf8
41
41
  const blobOf = (ref) => git(['show', ref]); // ref 必须为完整对象引用(<sha>:<path> / HEAD:<path> / :<path>)
42
42
 
43
43
  const problems = [];
44
- const fail = (msg) => { console.error(msg); process.exit(1); };
45
44
 
46
45
  // ---- --staged:暂存 blob ⊇ HEAD blob ----
47
46
  if (staged) {
@@ -17,7 +17,6 @@
17
17
  // L0/L1 勾触达红线 → 「红线判低」hard——协作道不受理 STOP 级改动,就高不就低,
18
18
  // 2026-09-30 hybrid-governance-risk-lanes) [hard-block]
19
19
  // 2. 模板字段占位符残留(YYYY-MM-DD / <主题> 等未替换;<主题> 与 .md 同行 = 命名约定描述,豁免) [warning]
20
- // (2026-10-08 selfmeasure-and-modularize:实现已迁 check-hygiene.mjs,判据与文案未变)
21
20
  // 3. incidents 复盘三件套完整性 + 状态严格枚举 + 新 intent 回路(回路断档=hard,其他=warning)
22
21
  // 4. 引用有效性(文档/指令中引用的 .agents/ 路径必须存在;支持 fill-{a,b,c}.mjs 花括号展开与 fill-*.mjs 通配;
23
22
  // workflow 文档仅扫活跃态——终态件的引用是历史叙述,不扫,2026-09-27 audit-gate-hardening) [warning]
@@ -30,15 +29,11 @@
30
29
  // 非 git / 查不到加入记录 → 不可判定 → 走存量口径(不误报 hard)
31
30
  // 证据可写在 [x] 行的续行(仓库通写法「(证据:…)」另起一行;全/半角冒号皆认)
32
31
  // 9. 文件名英文 kebab-case(非 ASCII 文件名=warning,2026-09-11 规则)
33
- // (2026-10-08 selfmeasure-and-modularize:实现已迁 check-hygiene.mjs,判据与文案未变)
34
32
  // 10. 级别 vs 迁移文件一致性(L1/L2 入口文档加入提交触及迁移 SQL/Migrations=疑似判低,warning)
35
33
  // 11. 生成物漂移:workflow/INDEX.md(2026-09-21 检索层) + workflow/DASHBOARD.md(2026-10-08 扩覆盖面)=warning;
36
- // 各自调对应生成器 --check,口径单一不复刻渲染;实现已迁 check-hygiene.mjs
37
34
  // 12. frontmatter「模块:」合法性(枚举非法 / 2026-09-22 起新建缺字段=warning;词表单源 .agents/workflow-modules.txt)
38
- // (2026-10-08 selfmeasure-and-modularize:实现已迁 check-hygiene.mjs,判据与文案未变)
39
35
  // 13. 常驻面体积预算(超限=warning;判定单源 rule-budget.sh——经 sh 调用,
40
36
  // 无 sh 环境静默跳过:advisory 级且 pre-commit 侧在 git 钩子 sh 环境照常硬拦)
41
- // (2026-10-08 selfmeasure-and-modularize:实现已迁 check-hygiene.mjs,判据与文案未变)
42
37
  // 14. 新 done 的 spec/plan 须在 git 历史里出现过 `状态: approved`(确认环节留痕,2026-09-22;恒 advisory 永不升级 hard)
43
38
  // 15. 确认指纹对账(2026-09-27 起:approved/done 须 confirm-doc.mjs 确认指纹+台账配对,缺=hard-block;
44
39
  // 两形态——TTY 亲手 / --delegated 对话委托代录,台账 source 如实区分,配对判据与 source 无关)
@@ -114,11 +109,9 @@ import os from 'node:os';
114
109
  import path from 'node:path';
115
110
  import { spawnSync } from 'node:child_process';
116
111
  import { createHash } from 'node:crypto';
117
- import { createRequire } from 'node:module';
118
- import { fileURLToPath } from 'node:url';
119
112
  import { loadEnums } from './workflow-enums.mjs';
120
113
  import { runCheck16 } from './check-metric-claims.mjs';
121
- import { auditEnabled, loadKitPolicy, hasFreshVerifyLine } from './policy.mjs';
114
+ import { auditEnabled, loadKitPolicy, hasFreshVerifyLine, ledgerChainHash } from './policy.mjs';
122
115
  import { laneOfEntry } from './stage-gates.mjs';
123
116
  // MARK_RE:incident 留痕形态的单源(第七轮复核 N3——此前 check-loop 内联复制一份同口径字面量,
124
117
  // 两处靠注释与人工同步;改为复用 stage-gates.mjs 的导出,从结构上消除漂移可能)
@@ -127,9 +120,10 @@ import { MARK_RE, approvedTraceHit } from './stage-gates.mjs';
127
120
  // 本文件只组装 ctx 并消费返回的文案数组(先例 = check-metric-claims.mjs 承载检查 16)。
128
121
  // 调用点位置即输出顺序位:须留在检查 10 与 12 之间以保持 warnings 行序(稳定输出契约),见该模块内注释。
129
122
  import { runCheckHygiene } from './check-hygiene.mjs';
123
+ // 检查 7 拆模块(2026-10-09 engine-quality-round2 A1):阶段索引同步迁 check-stage-index.mjs
124
+ import { runCheckStageIndex } from './check-stage-index.mjs';
130
125
  import { gateSeg, finishSegs, makeCollector } from './gate-seg.mjs';
131
126
 
132
- const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
133
127
  // CHECK_LOOP_GIT:测试注入钩子(指向不存在/不可执行路径可稳定触发 spawn 异常);未设置时与原行为逐字节一致(2026-10-05-gitout-fail-open)
134
128
  const GIT = process.env.CHECK_LOOP_GIT || (process.platform === 'win32' ? 'git.exe' : 'git');
135
129
 
@@ -600,19 +594,10 @@ export function runCheckLoop(opts = {}) {
600
594
  if (modelHits.length) warnings.push(`- [WARN 宿主耦合] 薄 Adapter 不应固定模型:\n${modelHits.join('\n')}`);
601
595
  }
602
596
 
603
- // --- 6. 阶段索引同步 [warning] ---
604
- markGate('6', '阶段索引同步');
605
- for (const cmd of ['plan', 'design', 'build', 'test', 'deploy', 'maintain', 'review']) {
606
- for (const doc of ['AGENTS.md', '.agents/commands/new-task.md']) {
607
- // existsSync 守卫(2026-10-06-check19-entry-enoent 同类):两文件可合法缺失(下方 text 判空本就
608
- // 预期缺失态),fail-loud fs 读下按空跳过、不再响亮出账
609
- const docPath = path.join(ROOT, doc);
610
- const text = fs.existsSync(docPath) ? linesOf(docPath) : null;
611
- if (text && !text.some((l) => l.includes(`.agents/commands/${cmd}.md`))) {
612
- warnings.push(`- [WARN 阶段索引漂移] ${doc} 缺 ${cmd} 指令索引(两处阶段表须同步维护)`);
613
- }
614
- }
615
- }
597
+ // --- 6. 阶段索引同步 [warning](2026-10-09 engine-quality-round2 A1:实现迁 check-stage-index.mjs——
598
+ // 先例 check-hygiene / check-metric-claims;段 id 沿 sh 版 '6',清单编号 7 与文案逐字不动;
599
+ // 调用点位置即输出顺序契约)---
600
+ warnings.push(...runCheckStageIndex({ root: ROOT, linesOf, gateStats }));
616
601
 
617
602
  // --- 共享:文件「首次加入 git」的日期索引(2026-09-28 check8-git-anchor)---
618
603
  // 用途:检查 8 的生效日锚从**可手填的文件名前缀**(项目命名规范本身即 `YYYY-MM-DD-<主题>`,作者
@@ -626,18 +611,13 @@ export function runCheckLoop(opts = {}) {
626
611
  // 非 git / 命令失败 → null,调用方按既有「非 git 跳过」语义退化(不 fail-loud)。
627
612
  // 注:检查 10 需要的是「文件→加入 commit」+「commit→文件清单」两份映射(判断同提交是否触及迁移 SQL),
628
613
  // 与本索引「文件→日期」不同构,故**不合并**——避免把两处判据耦死。
629
- const addedDates = (() => {
630
- if (gitOut(['rev-parse', '--git-dir']) === null) return null;
631
- const log = gitOut(['log', '--diff-filter=A', '--format=@%aI', '--name-only']);
632
- if (log === null) return null;
633
- const m = new Map();
634
- let cur = null;
635
- for (const line of log.split('\n')) {
636
- if (line.startsWith('@')) { cur = line.slice(1); continue; }
637
- if (line.trim() && cur && !m.has(line.trim())) m.set(line.trim(), cur); // 新→旧序首遇 = 最早
638
- }
639
- return m;
640
- })();
614
+ // addedDates(2026-10-09 engine-quality-round3 W1):全史解析走持久缓存(HEAD 键)——
615
+ // 同 HEAD 多入口复跑(verify/edit-face-check/pre-commit/CI)不再重复全算;git-dir 探测保留在前。
616
+ const addedDates = gitOut(['rev-parse', '--git-dir']) === null ? null : addedDatesWithCache({
617
+ head: gitOut(['rev-parse', 'HEAD']),
618
+ gitLog: () => gitOut(['log', '--diff-filter=A', '--format=@%aI', '--name-only']),
619
+ cachePath: path.join(ROOT, '.agents', 'cache', 'added-dates.json'),
620
+ });
641
621
  // 某文档「首次加入日期」(YYYY-MM-DD);不可判定(非 git / 查不到)返回 ''
642
622
  const addedDateOf = (absDoc) => {
643
623
  if (!addedDates) return '';
@@ -723,7 +703,7 @@ export function runCheckLoop(opts = {}) {
723
703
  if (/^##\s+(改动面|任务拆解|改动方案)/.test(line)) { inDeclaredSec = true; continue; }
724
704
  if (inDeclaredSec && /^##\s+/.test(line)) { inDeclaredSec = false; continue; }
725
705
  if (inDeclaredSec) {
726
- const fileMatch = line.match(/([a-zA-Z0-9._\/-]+\.[a-zA-Z0-9]+)/);
706
+ const fileMatch = line.match(/([a-zA-Z0-9._/-]+\.[a-zA-Z0-9]+)/);
727
707
  if (fileMatch) declaredFiles.push(fileMatch[1]);
728
708
  }
729
709
  }
@@ -773,8 +753,39 @@ export function runCheckLoop(opts = {}) {
773
753
  const verifyFreshGlobal = verifyLines ? hasFreshVerifyLine(verifyLines) : false;
774
754
  // 2026-10-08 verify-doc-binding:v6+(verifyDocSince)按本 doc 精确匹配;缺键维持全局布尔。
775
755
  const verifyDocBound = typeof kitPolicy.verifyDocSince === 'string';
756
+ // 2026-10-09 verifydoc-anchor:v6 精确匹配的**追溯边界**——只对「确认门 done ts ≥ verifyDocSince」的单生效。
757
+ // 背景(用户 2026-10-09 拍板 D):4 单(verify-evidence / workflow-dashboard / selfmeasure / gate-roi-metrics)
758
+ // 的 done ts 均 < v6 上线日(2026-10-08 17:37),它们关单时既无 --doc 机制、也无从补证——被精确匹配追责
759
+ // 会产 9 条不可消退 advisory(补跑=自证,回溯不了关单当时全绿,与门禁本意相悖)。退回全局布尔 = v5 语义
760
+ // (不误报、不伪造),与检查 15/18 生效日锚同构。done ts 台账读一次循环外缓存,零新增子进程。
761
+ const verifyDocSinceTs = verifyDocBound ? kitPolicy.verifyDocSince : null;
762
+ const doneTsOf = (() => {
763
+ const map = new Map();
764
+ try {
765
+ const lp = path.join(ROOT, '.agents', 'confirmations.jsonl');
766
+ if (fs.existsSync(lp)) {
767
+ for (const line of fs.readFileSync(lp, 'utf8').split(/\r?\n/)) {
768
+ if (!line.trim()) continue;
769
+ try {
770
+ const e = JSON.parse(line);
771
+ // append-only 取末次 done 跳转(同一 doc 多次 done 取最后一条 ts)
772
+ if (e && e.doc && ['done', 'closed'].includes(e.stage)) map.set(e.doc, e.ts || '');
773
+ } catch { /* 坏行跳过(同检查 15 口径) */ }
774
+ }
775
+ }
776
+ } catch { /* 台账不可读 → 无锚 → 全走全局布尔(fail-open,不误报 hard) */ }
777
+ return map;
778
+ })();
779
+ // 该 doc 的确认门 done 是否落在 v6 追溯窗口内;无台账行 / 台账不可读 → false(存量豁免,退回全局布尔)
780
+ // 2026-10-09 verifydoc-anchor:按 ISO 时刻比较(不再是日期粒度)——机制上线当日的更早时刻 done 的单
781
+ // 仍在窗口外(日期粒度会把同日的早时刻误纳入,见 policy.mjs verifyDocSince 注释)。
782
+ const verifyDocInWindow = (rel) => {
783
+ if (!verifyDocSinceTs) return false;
784
+ const ts = doneTsOf.get(rel);
785
+ return !!ts && ts >= verifyDocSinceTs;
786
+ };
776
787
  const verifyFreshFor = (rel) => (verifyLines
777
- ? hasFreshVerifyLine(verifyLines, Date.now(), verifyDocBound ? rel : null)
788
+ ? hasFreshVerifyLine(verifyLines, Date.now(), verifyDocInWindow(rel) ? rel : null)
778
789
  : false);
779
790
  for (const intent of docFiles('intents')) {
780
791
  if (fmGet(intent, '状态') !== 'done') continue;
@@ -960,10 +971,39 @@ export function runCheckLoop(opts = {}) {
960
971
  const EFFECTIVE = kitPolicy.confirmDocsEffective;
961
972
  const ledgerPath = path.join(ROOT, '.agents', 'confirmations.jsonl');
962
973
  const ledger = [];
974
+ const ledgerBadLines = []; // 坏行(JSON.parse 失败)的物理行号(复核 P2-1:报文行号须对齐物理行)
963
975
  if (fs.existsSync(ledgerPath)) {
964
- for (const line of linesOf(ledgerPath) || []) {
976
+ for (const [lineNo, line] of (linesOf(ledgerPath) || []).entries()) {
965
977
  if (!line.trim()) continue;
966
- try { ledger.push(JSON.parse(line)); } catch { /* 坏行跳过 */ }
978
+ try { ledger.push(JSON.parse(line)); } catch { ledgerBadLines.push(lineNo + 1); }
979
+ }
980
+ }
981
+ // 台账哈希链校验(2026-10-09 engine-quality-round2 D;写入侧=confirm-doc appendLedger,哈希单源
982
+ // policy.ledgerChainHash——判定零复刻)。判据:连续带 hash 行段内,① prevHash 须等于前一带 hash
983
+ // 行的 hash(无 hash 行后链段重启 '',与写入侧读末行口径一致);② hash 须可重算一致。
984
+ // **坏行口径(复核 P2-1 勘误,此前注释误称「与写入侧一致」)**:坏行被本解析整体剔除、不重置游标
985
+ // ——坏行后写入的链行(写入侧读末行遇坏行 prevHash='')验链必失配出账。这是**预期 fail-loud**
986
+ // (坏行本身即台账完整性事故,应人工查验),非口径一致场景。
987
+ // 断裂 = hard「台账链断裂」(只报首断**物理行号** + 影响行计数,不做逐行刷屏)——就地改/删中间行
988
+ // 必留痕迹;边界:整文件重写可重建链,不可机器防,兜底 = 台账自身 git 历史(check-ledger-invariant)。
989
+ // 历史行(v1.5.0 前,无 hash 字段)零回填零告警——向后兼容。
990
+ {
991
+ let prevHash = '';
992
+ let brokenAt = 0;
993
+ let brokenCount = 0;
994
+ ledger.forEach((row, idx) => {
995
+ if (!row || typeof row.hash !== 'string') { prevHash = ''; return; } // 存量行/无链字段 → 链段重启
996
+ const { hash, prevHash: p, ...rest } = row;
997
+ const ok = (p ?? '') === prevHash && ledgerChainHash(rest, p ?? '') === hash;
998
+ if (!ok) {
999
+ if (!brokenAt) brokenAt = idx + 1;
1000
+ brokenCount++;
1001
+ }
1002
+ prevHash = hash;
1003
+ });
1004
+ if (brokenAt) {
1005
+ const physical = brokenAt + ledgerBadLines.filter((n) => n <= brokenAt).length; // 物理行号(复核 P2-1:剔除坏行会令数组下标漂移)
1006
+ blockers.push(`- [台账链断裂] confirmations.jsonl 自第 ${physical} 行(物理)起哈希链校验失败(共 ${brokenCount} 行不一致——append-only 台账出现就地改/删痕迹;prevHash 接续或内容重算不符。整文件重写不可机器防,对质走台账自身 git 历史)`);
967
1007
  }
968
1008
  }
969
1009
  // incidents 侧覆盖(2026-09-27 gate-coverage):fixed/closed 为已确认态(open = 起草态不加门——
@@ -1414,6 +1454,43 @@ export function fmStatus(text) {
1414
1454
  // lines 已 CRLF 归一(split(/\r?\n/)),frontmatter 区内首个「状态:」行保分隔符换值为 prev(`状态: done`
1415
1455
  // 双空格 → `状态: approved`——与前向按原行字面计算对称,非规范分隔符不误伤;行尾空白等更奇异格式仍会
1416
1456
  // 失配,触发前提本身已违反「确认落态唯一入口」约定,接受),全文剔「确认指纹:」行后 join('\n') 再 sha256。
1457
+ // parseAddedDatesLog:git log(--diff-filter=A --format=@%aI --name-only)文本 → Map(rel → 加入 ISO 时间)。
1458
+ // 新→旧序首遇 = 最新一次加入(复核 P2-1 勘误:旧注释「最早」系误述——行为原样零复刻,检查 8 isNew 取最新加入日期更保守)。
1459
+ export function parseAddedDatesLog(logText) {
1460
+ const m = new Map();
1461
+ let cur = null;
1462
+ for (const line of logText.split('\n')) {
1463
+ if (line.startsWith('@')) { cur = line.slice(1); continue; }
1464
+ if (line.trim() && cur && !m.has(line.trim())) m.set(line.trim(), cur);
1465
+ }
1466
+ return m;
1467
+ }
1468
+
1469
+ // addedDatesWithCache:addedDates 的持久缓存层(2026-10-09 engine-quality-round3 W1)。
1470
+ // 键 = HEAD sha(同 commit 祖先不可变 → log 幂等;rebase 必改 HEAD 必重算;复核 P2-4:shallow→unshallow 同 HEAD 可见性增长有陈旧窗口——后果退化为旧行为 addedDateOf 返回 "",HEAD 变化自愈);
1471
+ // 缓存位于 .agents/cache/(gitignored 运行态);读/写任一失败 fail-open 回退全算(门禁不失效);
1472
+ // head 缺省(detached --rev worktree 等场景)→ 不读写缓存直接全算(语义不变)。
1473
+ export function addedDatesWithCache({ head, gitLog, cachePath }) {
1474
+ if (head) {
1475
+ try {
1476
+ const c = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
1477
+ if (c && c.head === head && c.map && Object.keys(c.map).length) return new Map(Object.entries(c.map));
1478
+ } catch { /* 缓存缺失/损坏 → 全算 */ }
1479
+ }
1480
+ const log = gitLog();
1481
+ if (log === null) return null;
1482
+ const m = parseAddedDatesLog(log);
1483
+ if (head) {
1484
+ try {
1485
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
1486
+ const tmp = cachePath + '.tmp';
1487
+ fs.writeFileSync(tmp, JSON.stringify({ head, map: Object.fromEntries(m), savedAt: new Date().toISOString() }) + '\n');
1488
+ fs.renameSync(tmp, cachePath);
1489
+ } catch { /* 写失败 fail-open:本轮已用全算结果,缓存留待下次 */ }
1490
+ }
1491
+ return m;
1492
+ }
1493
+
1417
1494
  export function bindingSha256(lines, prevStatus) {
1418
1495
  const isDelim = (l) => /^---\s*$/.test(l);
1419
1496
  const out = [];
@@ -12,7 +12,8 @@ import { spawnSync } from 'node:child_process';
12
12
  import { fileURLToPath } from 'node:url';
13
13
  import { computeFingerprint } from './confirm-doc.mjs';
14
14
  import { POLICIES, loadKitPolicy } from './policy.mjs';
15
- import { entryConfirmed, MARK_RE, laneOfEntry, laneOfDoc } from './stage-gates.mjs';
15
+ import { entryConfirmed, laneOfEntry, laneOfDoc } from './stage-gates.mjs';
16
+ import { parseAddedDatesLog, addedDatesWithCache } from './check-loop.mjs';
16
17
 
17
18
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
18
19
  const CHECK_LOOP = path.join(SCRIPT_DIR, 'check-loop.mjs');
@@ -2902,5 +2903,70 @@ w(T, 'workflow/intents/2026-09-12-deep.md', INTENT('deep', '状态: draft\n级
2902
2903
  rmfix(T);
2903
2904
  }
2904
2905
 
2906
+ // ---- 场景 21:台账哈希链(2026-10-09 engine-quality-round2 D)——篡改必抓 / 存量零告警 ----
2907
+ {
2908
+ // 21a:连续带 hash 行中间被篡改(改 quote 不动链字段)→ hard「台账链断裂」
2909
+ const T = mkfix();
2910
+ const { ledgerChainHash } = await import('./policy.mjs');
2911
+ const chainRow = (prevHash, fields) => {
2912
+ const row = { ...fields, prevHash };
2913
+ return { ...row, hash: ledgerChainHash(fields, prevHash) };
2914
+ };
2915
+ const row1 = chainRow('', { ts: '2026-10-09T01:00:00Z', doc: 'workflow/intents/2026-10-09-a.md', stage: 'approved', fingerprint: 'a'.repeat(64), prev: 'draft', source: 'chat-delegated', quote: '真原话一' });
2916
+ const row2 = chainRow(row1.hash, { ts: '2026-10-09T02:00:00Z', doc: 'workflow/intents/2026-10-09-a.md', stage: 'done', fingerprint: 'b'.repeat(64), prev: 'approved', source: 'chat-delegated', quote: '真原话二' });
2917
+ // 篡改第一行 quote(链字段不动 → hash 失配)
2918
+ const tampered = { ...row1, quote: '篡改后的假原话' };
2919
+ writeLedger(T, [tampered, row2]);
2920
+ let r = run(T);
2921
+ let out = outOf(r);
2922
+ check('场景 21a 链断裂:中间行篡改 → hard 台账链断裂(指首断行号)',
2923
+ r.status !== 0 && out.includes('台账链断裂') && /第 1 行/.test(out), `exit=${r.status}\n${out.slice(-400)}`);
2924
+ // 反证还原:篡改行恢复原 quote → 链复通(断言非恒绿)
2925
+ writeLedger(T, [row1, row2]);
2926
+ r = run(T);
2927
+ check('场景 21b 反证还原:篡改修复 → 链校验通过', !outOf(r).includes('台账链断裂'), outOf(r).slice(-300));
2928
+ rmfix(T);
2929
+ }
2930
+ {
2931
+ // 21c:全无 hash 的存量台账(v1.5.0 前形态)→ 零链告警(向后兼容)
2932
+ const T = mkfix();
2933
+ writeLedger(T, [
2934
+ { ts: '2026-09-20T01:00:00Z', doc: 'workflow/intents/2026-09-20-legacy.md', stage: 'approved', fingerprint: 'c'.repeat(64), prev: 'draft' },
2935
+ { ts: '2026-09-20T02:00:00Z', doc: 'workflow/intents/2026-09-20-legacy.md', stage: 'done', fingerprint: 'd'.repeat(64), prev: 'approved' },
2936
+ ]);
2937
+ const r = run(T);
2938
+ check('场景 21c 存量无 hash 行:零链告警(不追溯)', !outOf(r).includes('台账链断裂'), outOf(r).slice(-300));
2939
+ rmfix(T);
2940
+ }
2941
+
2942
+
2943
+ // ---- 场景 22:addedDates 持久缓存(2026-10-09 engine-quality-round3 W1)----
2944
+ {
2945
+ const log = '@2026-10-02T10:00:00+08:00\nworkflow/intents/a.md\nworkflow/plans/a.md\n@2026-10-01T09:00:00+08:00\nworkflow/intents/a.md\n';
2946
+ const m = parseAddedDatesLog(log);
2947
+ check('场景 22a 提取:新→旧序首遇 = 最新一次加入(原 IIFE 语义零复刻;注释「最早」为历史误述)', m.get('workflow/intents/a.md') === '2026-10-02T10:00:00+08:00' && m.get('workflow/plans/a.md') === '2026-10-02T10:00:00+08:00', JSON.stringify([...m]));
2948
+ }
2949
+ {
2950
+ const cacheP = path.join(os.tmpdir(), 'adc-' + Date.now() + '.json');
2951
+ let gitLogCalls = 0;
2952
+ const gitLog = () => { gitLogCalls++; return '@2026-10-01T09:00:00+08:00\nworkflow/intents/a.md\n'; };
2953
+ const head = 'head-sha-1';
2954
+ const m1 = addedDatesWithCache({ head, gitLog, cachePath: cacheP });
2955
+ const m2 = addedDatesWithCache({ head, gitLog, cachePath: cacheP });
2956
+ check('场景 22b 缓存命中:同 HEAD 二次调用 gitLog 仅 1 次且结果一致',
2957
+ gitLogCalls === 1 && m1.get('workflow/intents/a.md') === m2.get('workflow/intents/a.md'), 'calls=' + gitLogCalls);
2958
+ const m3 = addedDatesWithCache({ head: 'head-sha-2', gitLog, cachePath: cacheP });
2959
+ check('场景 22c HEAD 变更:重算(gitLog 计 2 次)', gitLogCalls === 2 && m3.get('workflow/intents/a.md') === '2026-10-01T09:00:00+08:00', 'calls=' + gitLogCalls);
2960
+ fs.writeFileSync(cacheP, '{broken json');
2961
+ const before = gitLogCalls;
2962
+ const m4 = addedDatesWithCache({ head: 'head-sha-2', gitLog, cachePath: cacheP });
2963
+ check('场景 22d 缓存损坏:fail-open 全算(gitLog 再调 1 次)且结果正确',
2964
+ gitLogCalls === before + 1 && m4.get('workflow/intents/a.md') === '2026-10-01T09:00:00+08:00', 'calls=' + gitLogCalls);
2965
+ const before2 = gitLogCalls;
2966
+ addedDatesWithCache({ head: null, gitLog, cachePath: cacheP });
2967
+ check('场景 22e 无 head:不读缓存直接全算(gitLog 再调 1 次)', gitLogCalls === before2 + 1, 'calls=' + gitLogCalls);
2968
+ fs.rmSync(cacheP, { force: true });
2969
+ }
2970
+
2905
2971
  console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
2906
2972
  process.exit(fail ? 1 : 0);
@@ -0,0 +1,40 @@
1
+ // check-stage-index.mjs — check-loop 检查 7 模块(阶段索引同步,2026-10-09 engine-quality-round2 A1)
2
+ // 拆分先例 = check-hygiene.mjs(检查 2/9/11/12/13)/ check-metric-claims.mjs(检查 16):
3
+ // ctx 传入既有 helper(linesOf——读异常响亮出账 + 按空文档降级的语义承载者,零复刻),
4
+ // 模块只返回 warnings 数组,不打印、不 exit、不改 ctx。
5
+ // 输出顺序契约:调用点位置即 warnings 插入序(check-loop 原位调用,段 id 沿 sh 版 '6'——清单编号 7
6
+ // 不动,gate-checklist 按 id 消费面不受影响)。
7
+ // 判据(沿 sh 版逐条):AGENTS.md 与 .agents/commands/new-task.md 须各含全部阶段指令路径串
8
+ // `.agents/commands/<cmd>.md`(双向索引);缺任一 = WARN。两文件可合法缺失(text 判空预期缺失态)。
9
+ // 测试:node templates/_agents/scripts/check-stage-index.test.mjs
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import { gateSeg, finishSegs, makeCollector, appendSegs } from './gate-seg.mjs';
13
+
14
+ const STAGE_CMDS = ['plan', 'design', 'build', 'test', 'deploy', 'maintain', 'review'];
15
+ const FACE_DOCS = ['AGENTS.md', '.agents/commands/new-task.md'];
16
+
17
+ export function runCheckStageIndex(ctx) {
18
+ const { root: ROOT, linesOf } = ctx;
19
+ const warnings = [];
20
+ // 插桩(selfmeasure 先例):局部收集器 + appendSegs 并入全局——模块产出局部数组,直接共用全局
21
+ // 游标会错账(check-hygiene.mjs:36-39 实测教训)
22
+ const localStats = makeCollector(warnings, []);
23
+ const markGate = (id, label) => { if (ctx.gateStats) gateSeg(localStats, id, label); };
24
+
25
+ markGate('6', '阶段索引同步');
26
+ for (const cmd of STAGE_CMDS) {
27
+ for (const doc of FACE_DOCS) {
28
+ // existsSync 守卫(2026-10-06-check19-entry-enoent 同类):两文件可合法缺失(下方 text 判空本就
29
+ // 预期缺失态),fail-loud fs 读下按空跳过、不再响亮出账
30
+ const docPath = path.join(ROOT, doc);
31
+ const text = fs.existsSync(docPath) ? linesOf(docPath) : null;
32
+ if (text && !text.some((l) => l.includes(`.agents/commands/${cmd}.md`))) {
33
+ warnings.push(`- [WARN 阶段索引漂移] ${doc} 缺 ${cmd} 指令索引(两处阶段表须同步维护)`);
34
+ }
35
+ }
36
+ }
37
+
38
+ if (ctx.gateStats) appendSegs(ctx.gateStats, finishSegs(localStats));
39
+ return warnings;
40
+ }