coding-agent-harness 1.0.1 → 1.0.4

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 (262) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/CONTRIBUTING.md +98 -0
  3. package/README.en-US.md +14 -0
  4. package/README.md +230 -80
  5. package/README.zh-CN.md +290 -0
  6. package/SKILL.md +132 -198
  7. package/docs-release/README.md +80 -9
  8. package/docs-release/architecture/overview.md +298 -28
  9. package/docs-release/architecture/overview.zh-CN.md +292 -0
  10. package/docs-release/assets/dashboard-overview.png +0 -0
  11. package/docs-release/assets/harness-architecture.svg +163 -0
  12. package/docs-release/assets/harness-workflow.svg +64 -0
  13. package/docs-release/guides/agent-installation.en-US.md +237 -0
  14. package/docs-release/guides/agent-installation.md +149 -27
  15. package/docs-release/guides/contributing.md +100 -0
  16. package/docs-release/guides/contributing.zh-CN.md +99 -0
  17. package/docs-release/guides/document-audience-and-surfaces.en-US.md +113 -0
  18. package/docs-release/guides/document-audience-and-surfaces.md +113 -0
  19. package/docs-release/guides/full-legacy-migration-subagent-strategy.md +334 -0
  20. package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +334 -0
  21. package/docs-release/guides/legacy-migration-agent-prompt.md +373 -0
  22. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +350 -0
  23. package/docs-release/guides/migration-playbook.en-US.md +324 -0
  24. package/docs-release/guides/migration-playbook.md +328 -0
  25. package/docs-release/guides/parent-control-repository-pattern.en-US.md +254 -0
  26. package/docs-release/guides/parent-control-repository-pattern.md +254 -0
  27. package/docs-release/guides/preset-development.md +214 -0
  28. package/docs-release/guides/repository-operating-models.en-US.md +197 -0
  29. package/docs-release/guides/repository-operating-models.md +197 -0
  30. package/docs-release/guides/task-state-machine.en-US.md +207 -0
  31. package/docs-release/guides/task-state-machine.md +214 -0
  32. package/docs-release/intl/README.md +15 -0
  33. package/docs-release/intl/de-DE.md +18 -0
  34. package/docs-release/intl/en-US.md +18 -0
  35. package/docs-release/intl/es-ES.md +18 -0
  36. package/docs-release/intl/fr-FR.md +18 -0
  37. package/docs-release/intl/ja-JP.md +18 -0
  38. package/docs-release/intl/ko-KR.md +18 -0
  39. package/docs-release/intl/zh-CN.md +18 -0
  40. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/brief.md +13 -0
  41. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
  42. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/lesson_candidates.md +24 -0
  43. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +1 -1
  44. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/task_plan.md +4 -2
  45. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/{visual_roadmap.md → visual_map.md} +9 -1
  46. package/package.json +10 -3
  47. package/presets/legacy-migration/checks/preset-check.mjs +3 -0
  48. package/presets/legacy-migration/preset.yaml +134 -0
  49. package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
  50. package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
  51. package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
  52. package/presets/legacy-migration/templates/findings.seed.md +17 -0
  53. package/presets/legacy-migration/templates/review.seed.md +12 -0
  54. package/presets/legacy-migration/templates/task_plan.append.md +9 -0
  55. package/presets/legacy-migration/templates/visual_map.append.md +12 -0
  56. package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
  57. package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
  58. package/presets/lesson-sedimentation/preset.yaml +23 -0
  59. package/presets/lesson-sedimentation/templates/prompt.md +23 -0
  60. package/presets/module/preset.yaml +25 -0
  61. package/presets/module/templates/execution_strategy.append.md +8 -0
  62. package/presets/module/templates/task_plan.append.md +17 -0
  63. package/presets/standard-task/preset.yaml +31 -0
  64. package/presets/standard-task/templates/task_plan.append.md +7 -0
  65. package/references/adversarial-review-standard.md +2 -2
  66. package/references/agents-md-pattern.md +5 -5
  67. package/references/delivery-operating-model-standard.md +3 -3
  68. package/references/docs-directory-standard.md +53 -10
  69. package/references/external-source-intake-standard.md +75 -0
  70. package/references/harness-ledger.md +53 -94
  71. package/references/legacy-12-phase-bootstrap.md +41 -0
  72. package/references/lessons-governance.md +100 -88
  73. package/references/module-parallel-standard.md +14 -14
  74. package/references/planning-loop.md +51 -7
  75. package/references/project-onboarding-audit.md +10 -0
  76. package/references/pull-request-standard.md +118 -0
  77. package/references/repo-governance-standard.md +12 -1
  78. package/references/review-routing-standard.md +7 -1
  79. package/references/ssot-governance.md +67 -59
  80. package/references/taskr-gap-analysis.md +600 -0
  81. package/references/testing-standard.md +50 -0
  82. package/references/walkthrough-closeout.md +10 -9
  83. package/scripts/check-harness.mjs +111 -331
  84. package/scripts/commands/dashboard-command.mjs +67 -0
  85. package/scripts/commands/migration-command.mjs +96 -0
  86. package/scripts/commands/preset-command.mjs +73 -0
  87. package/scripts/commands/task-command.mjs +327 -0
  88. package/scripts/harness.mjs +106 -20
  89. package/scripts/lib/capability-registry.mjs +591 -0
  90. package/scripts/lib/check-module-parallel.mjs +237 -0
  91. package/scripts/lib/check-profiles.mjs +418 -0
  92. package/scripts/lib/check-task-contracts.mjs +47 -0
  93. package/scripts/lib/core-shared.mjs +196 -0
  94. package/scripts/lib/dashboard-data.mjs +412 -0
  95. package/scripts/lib/dashboard-workbench.mjs +257 -0
  96. package/scripts/lib/dashboard-writer.mjs +107 -4
  97. package/scripts/lib/git-status-summary.mjs +46 -0
  98. package/scripts/lib/governance-index-generator.mjs +174 -0
  99. package/scripts/lib/governance-sync.mjs +514 -0
  100. package/scripts/lib/governance-table-boundary.mjs +175 -0
  101. package/scripts/lib/harness-core.mjs +15 -1318
  102. package/scripts/lib/lesson-maintenance.mjs +152 -0
  103. package/scripts/lib/markdown-utils.mjs +158 -0
  104. package/scripts/lib/migration-planner.mjs +478 -0
  105. package/scripts/lib/migration-support.mjs +312 -0
  106. package/scripts/lib/preset-audit-contracts.mjs +37 -0
  107. package/scripts/lib/preset-engine.mjs +497 -0
  108. package/scripts/lib/preset-registry.mjs +627 -0
  109. package/scripts/lib/preset-resource-contracts.mjs +83 -0
  110. package/scripts/lib/review-confirm-git-gate.mjs +248 -0
  111. package/scripts/lib/status-dashboard-renderer.mjs +102 -0
  112. package/scripts/lib/subagent-authorization-audit.mjs +196 -0
  113. package/scripts/lib/task-completion-consistency.mjs +16 -0
  114. package/scripts/lib/task-index.mjs +93 -0
  115. package/scripts/lib/task-lesson-candidates.mjs +242 -0
  116. package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
  117. package/scripts/lib/task-lifecycle/review-confirm.mjs +101 -0
  118. package/scripts/lib/task-lifecycle/review-gates.mjs +70 -0
  119. package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
  120. package/scripts/lib/task-lifecycle.mjs +649 -0
  121. package/scripts/lib/task-review-model.mjs +469 -0
  122. package/scripts/lib/task-scanner.mjs +576 -0
  123. package/scripts/lib/task-tombstone-commands.mjs +140 -0
  124. package/scripts/postinstall.mjs +14 -0
  125. package/skills/preset-creator/SKILL.md +179 -0
  126. package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
  127. package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
  128. package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -0
  129. package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
  130. package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
  131. package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
  132. package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
  133. package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
  134. package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
  135. package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
  136. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
  137. package/{templates/planning/visual_roadmap.md → skills/preset-creator/references/complex-task-skeleton/visual_map.md} +24 -2
  138. package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
  139. package/templates/AGENTS.md.template +51 -36
  140. package/templates/architecture/Architecture-SSoT.md +21 -0
  141. package/templates/architecture/README.md +49 -0
  142. package/templates/architecture/critical-flows.md +22 -0
  143. package/templates/architecture/local-repo-context.md +20 -0
  144. package/templates/architecture/service-catalog.md +17 -0
  145. package/templates/architecture/services/service-template.md +31 -0
  146. package/templates/architecture/system-map.md +22 -0
  147. package/templates/dashboard/assets/app-src/00-state.js +42 -0
  148. package/templates/dashboard/assets/app-src/10-router.js +77 -0
  149. package/templates/dashboard/assets/app-src/20-overview.js +241 -0
  150. package/templates/dashboard/assets/app-src/30-tasks.js +409 -0
  151. package/templates/dashboard/assets/app-src/35-task-detail.js +246 -0
  152. package/templates/dashboard/assets/app-src/40-modules.js +58 -0
  153. package/templates/dashboard/assets/app-src/45-review.js +347 -0
  154. package/templates/dashboard/assets/app-src/50-migration.js +183 -0
  155. package/templates/dashboard/assets/app-src/60-shared.js +61 -0
  156. package/templates/dashboard/assets/app-src/90-bindings.js +524 -0
  157. package/templates/dashboard/assets/app.css +3107 -300
  158. package/templates/dashboard/assets/app.css.manifest.json +9 -0
  159. package/templates/dashboard/assets/app.js +2068 -306
  160. package/templates/dashboard/assets/app.manifest.json +12 -0
  161. package/templates/dashboard/assets/css-src/00-foundation.css +342 -0
  162. package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
  163. package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
  164. package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
  165. package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
  166. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +427 -0
  167. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
  168. package/templates/dashboard/assets/i18n.js +531 -44
  169. package/templates/dashboard/assets/mermaid-renderer.js +58 -8
  170. package/templates/development/README.md +52 -0
  171. package/templates/development/codebase-map.md +11 -0
  172. package/templates/development/cross-repo-debugging.md +18 -0
  173. package/templates/development/external-context/service-template.md +33 -0
  174. package/templates/development/external-source-packs/README.md +24 -0
  175. package/templates/development/external-source-packs/digest-template.md +28 -0
  176. package/templates/development/local-setup.md +16 -0
  177. package/templates/development/stubs-and-mocks.md +11 -0
  178. package/templates/integrations/README.md +40 -0
  179. package/templates/integrations/api-contract.md +42 -0
  180. package/templates/integrations/event-contract.md +46 -0
  181. package/templates/integrations/third-party/vendor-template.md +42 -0
  182. package/templates/integrations/webhook-contract.md +41 -0
  183. package/templates/ledger/Harness-Ledger.md +13 -25
  184. package/templates/lessons/lesson-arch-process-change.md +1 -1
  185. package/templates/lessons/lesson-new-doc.md +1 -1
  186. package/templates/lessons/lesson-ref-change.md +1 -1
  187. package/templates/planning/brief.md +32 -0
  188. package/templates/planning/execution_strategy.md +31 -0
  189. package/templates/planning/lesson_candidates.md +70 -0
  190. package/templates/planning/long-running-task-contract.md +7 -0
  191. package/templates/planning/module_brief.md +25 -0
  192. package/templates/planning/module_session_prompt.md +6 -0
  193. package/templates/planning/optional/artifacts/INDEX.md +3 -3
  194. package/templates/planning/optional/references/INDEX.md +3 -3
  195. package/templates/planning/review.md +59 -0
  196. package/templates/planning/task_plan.md +40 -15
  197. package/templates/planning/visual_map.md +50 -0
  198. package/templates/reference/docs-library-standard.md +31 -0
  199. package/templates/reference/execution-workflow-standard.md +5 -2
  200. package/templates/reference/external-source-intake-standard.md +82 -0
  201. package/templates/reference/harness-ledger-standard.md +1 -0
  202. package/templates/reference/pull-request-standard.md +80 -0
  203. package/templates/reference/repo-governance-standard.md +8 -5
  204. package/templates/reference/review-routing-standard.md +6 -0
  205. package/templates/reference/walkthrough-standard.md +3 -1
  206. package/templates/verifier/verifier-output.md +1 -1
  207. package/templates/walkthrough/walkthrough-template.md +2 -2
  208. package/templates-zh-CN/AGENTS.md.template +73 -70
  209. package/templates-zh-CN/architecture/Architecture-SSoT.md +21 -0
  210. package/templates-zh-CN/architecture/README.md +51 -0
  211. package/templates-zh-CN/architecture/critical-flows.md +24 -0
  212. package/templates-zh-CN/architecture/local-repo-context.md +20 -0
  213. package/templates-zh-CN/architecture/service-catalog.md +17 -0
  214. package/templates-zh-CN/architecture/services/service-template.md +31 -0
  215. package/templates-zh-CN/architecture/system-map.md +22 -0
  216. package/templates-zh-CN/development/README.md +54 -0
  217. package/templates-zh-CN/development/codebase-map.md +11 -0
  218. package/templates-zh-CN/development/cross-repo-debugging.md +18 -0
  219. package/templates-zh-CN/development/external-context/service-template.md +33 -0
  220. package/templates-zh-CN/development/external-source-packs/README.md +24 -0
  221. package/templates-zh-CN/development/external-source-packs/digest-template.md +28 -0
  222. package/templates-zh-CN/development/local-setup.md +16 -0
  223. package/templates-zh-CN/development/stubs-and-mocks.md +11 -0
  224. package/templates-zh-CN/integrations/README.md +42 -0
  225. package/templates-zh-CN/integrations/api-contract.md +42 -0
  226. package/templates-zh-CN/integrations/event-contract.md +46 -0
  227. package/templates-zh-CN/integrations/third-party/vendor-template.md +42 -0
  228. package/templates-zh-CN/integrations/webhook-contract.md +41 -0
  229. package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
  230. package/templates-zh-CN/planning/brief.md +32 -0
  231. package/templates-zh-CN/planning/execution_strategy.md +30 -0
  232. package/templates-zh-CN/planning/lesson_candidates.md +70 -0
  233. package/templates-zh-CN/planning/long-running-task-contract.md +1 -1
  234. package/templates-zh-CN/planning/module_brief.md +25 -0
  235. package/templates-zh-CN/planning/module_plan.md +2 -2
  236. package/templates-zh-CN/planning/module_session_prompt.md +4 -3
  237. package/templates-zh-CN/planning/review.md +59 -1
  238. package/templates-zh-CN/planning/task_plan.md +37 -11
  239. package/templates-zh-CN/planning/{visual_roadmap.md → visual_map.md} +21 -2
  240. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  241. package/templates-zh-CN/reference/docs-library-standard.md +36 -1
  242. package/templates-zh-CN/reference/execution-workflow-standard.md +10 -2
  243. package/templates-zh-CN/reference/external-source-intake-standard.md +82 -0
  244. package/templates-zh-CN/reference/harness-ledger-standard.md +7 -4
  245. package/templates-zh-CN/reference/pull-request-standard.md +106 -0
  246. package/templates-zh-CN/reference/repo-governance-standard.md +4 -1
  247. package/templates-zh-CN/reference/review-routing-standard.md +8 -1
  248. package/templates-zh-CN/reference/walkthrough-standard.md +6 -5
  249. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +2 -2
  250. package/templates-zh-CN/walkthrough/walkthrough-template.md +2 -2
  251. package/scripts/smoke-dashboard.mjs +0 -70
  252. package/scripts/test-harness.mjs +0 -483
  253. package/templates/ssot/Feature-SSoT.md +0 -43
  254. package/templates/ssot/Lessons-SSoT.md +0 -44
  255. package/templates-zh-CN/dashboard/assets/app.css +0 -399
  256. package/templates-zh-CN/dashboard/assets/app.js +0 -435
  257. package/templates-zh-CN/dashboard/assets/i18n.js +0 -47
  258. package/templates-zh-CN/dashboard/assets/markdown-reader.js +0 -116
  259. package/templates-zh-CN/dashboard/assets/mermaid-renderer.js +0 -59
  260. package/templates-zh-CN/dashboard/index.html +0 -18
  261. package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
  262. package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
@@ -35,8 +35,8 @@
35
35
  - 本轮有没有发现 reference / workflow / checker 不够用或有误:[有/无,写一句理由]
36
36
  - 有没有反复出现、跨页面/跨模块/跨阶段的共性问题:[有/无,写一句理由]
37
37
  - 有没有下次 agent 也可能重复踩的坑:[有/无,写一句理由]
38
- - Lessons 结果:[checked-created: L-YYYY-MM-DD-NNN / checked-none: 一句话原因]
39
- - Lessons Detail Doc:[如 checked-created,填 `docs/01-GOVERNANCE/lessons/...md`;否则写"无"]
38
+ - Lessons 结果:[checked-created: L-YYYY-MM-DD-NNN / queued-promotion: LC-YYYYMMDD-NNN / checked-candidate: LC-YYYYMMDD-NNN / checked-none: 一句话原因]
39
+ - Lessons Detail Doc:[如 checked-created,填 `docs/01-GOVERNANCE/lessons/...md`;如 queued/checked-candidate,填 `lesson_candidates.md` 和任务本地 `lessons/LC-...md`;否则写"无"]
40
40
 
41
41
  ## 相关文件
42
42
  - Task Plan: [路径]
@@ -106,22 +106,23 @@ docs/10-WALKTHROUGH/Closeout-SSoT.md
106
106
 
107
107
  如果任何一条答案是“有”:
108
108
 
109
- 1. 完整读一遍 `docs/01-GOVERNANCE/Lessons-SSoT.md`
109
+ 1. 查找任务本地 `lesson_candidates.md`、任务本地 `lessons/LC-*.md` 和 `docs/01-GOVERNANCE/lessons/*.md`
110
110
  2. 按 `references/lessons-governance.md` 中的规则处理冲突
111
- 3. `docs/01-GOVERNANCE/lessons/` 下写入详细建议(使用 `templates/lessons/` 下的对应模板)
112
- 4. 更新 Lessons SSoT 表,`Detail Doc` 必须指向刚写的详情文档
113
- 5. 在 Closeout SSoT 和 Harness Ledger 中记录 `checked-created: L-YYYY-MM-DD-NNN`
111
+ 3. 先在任务目录 `lesson_candidates.md` 中登记候选;候选进入 `needs-promotion` 时同步写任务本地 `lessons/LC-*.md` 详情文件并在 `Detail Artifact` 链接
112
+ 4. 人工确认后,如需沉淀,使用 maintenance CLI 写入 `docs/01-GOVERNANCE/lessons/` promoted 详情文档
113
+ 5. 在 Closeout SSoT 和 Harness Ledger 中记录 `queued-promotion: LC-...` 或 `checked-created: L-YYYY-MM-DD-NNN`
114
114
 
115
115
  如果所有答案都是“没有”,不能静默跳过;在 Closeout SSoT 和 Harness Ledger 中记录
116
+ 新任务在 `lesson_candidates.md` 中写 `no-candidate-accepted` 和 No-Candidate Reason;旧任务可记录
116
117
  `checked-none: <一句话原因>`。
117
118
 
118
119
  ## Harness Ledger 回写
119
120
 
120
- 写完 Walkthrough、更新 Feature/Regression SSoT,并完成 Lessons 检查后,Agent 必须更新
121
- `docs/Harness-Ledger.md` `docs/10-WALKTHROUGH/Closeout-SSoT.md`:
121
+ 写完 Walkthrough、更新 Regression SSoT 或其他本轮实际触达的非任务生命周期 SSoT,并完成 Lessons 检查后,Agent 必须更新
122
+ `docs/10-WALKTHROUGH/Closeout-SSoT.md`;任务生命周期总账由 CLI 重新生成 `docs/Harness-Ledger.md`:
122
123
 
123
124
  1. 为本轮任务追加或更新对应 `HL-*` 条目
124
- 2. 记录 Task Plan、Feature SSoT、Regression SSoT、Review Report、Walkthrough、Lessons Check 的结果
125
+ 2. 记录 Task Plan、Regression SSoT、Review Report、Walkthrough、Lessons Check 的结果
125
126
  3. 列出本轮触碰的 harness 文档
126
127
  4. 如有未完成项,使用 `missing` 或 `skipped-with-reason` 并写明 residual
127
128
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
+ import { checkModuleParallelStructure } from "./lib/check-module-parallel.mjs";
5
6
 
6
7
  const targetRoot = path.resolve(process.argv[2] || process.cwd());
7
8
  const requireGlobalModuleSync = process.env.HARNESS_REQUIRE_GLOBAL_MODULE_SYNC === "1";
@@ -23,7 +24,6 @@ const requiredFiles = [
23
24
  "docs/10-WALKTHROUGH/Closeout-SSoT.md",
24
25
  "docs/05-TEST-QA/Regression-SSoT.md",
25
26
  "docs/05-TEST-QA/Cadence-Ledger.md",
26
- "docs/01-GOVERNANCE/Lessons-SSoT.md",
27
27
  ];
28
28
 
29
29
  const legacyPlanningFiles = [
@@ -41,7 +41,6 @@ const agAgentsRefs = [
41
41
  "adversarial-review-standard.md",
42
42
  "review-routing-standard.md",
43
43
  "walkthrough-standard.md",
44
- "Lessons-SSoT.md",
45
44
  "harness-ledger-standard.md",
46
45
  "Closeout-SSoT.md",
47
46
  ];
@@ -63,6 +62,8 @@ const allowedWalkthroughSkip =
63
62
  /walkthrough skipped-with-reason:\s*(docs-only|no-runtime|superseded|historical-backfill|owner-deferred)/i;
64
63
  const lessonsCreatedPattern = /checked-created:\s*(L-\d{4}-\d{2}-\d{2}-\d{3}|L-\d+)/i;
65
64
  const lessonsNonePattern = /checked-none:\s*\S+/i;
65
+ const lessonsCandidatePattern = /checked-candidate:\s*(LC-[A-Za-z0-9-]+)/i;
66
+ const lessonsQueuedPromotionPattern = /queued-promotion:\s*(LC-[A-Za-z0-9-]+)/i;
66
67
 
67
68
  const failures = [];
68
69
  const warnings = [];
@@ -107,7 +108,18 @@ function checkRequiredFiles() {
107
108
 
108
109
  function checkPlanningStructure() {
109
110
  if (exists("docs/09-PLANNING/Module-Registry.md")) {
110
- checkModuleParallelStructure();
111
+ checkModuleParallelStructure({
112
+ exists,
113
+ fail,
114
+ filePath,
115
+ markdownTable,
116
+ read,
117
+ rel,
118
+ requireFile,
119
+ requireGlobalModuleSync,
120
+ targetRoot,
121
+ warn,
122
+ });
111
123
  return;
112
124
  }
113
125
  for (const legacyFile of legacyPlanningFiles) {
@@ -115,239 +127,6 @@ function checkPlanningStructure() {
115
127
  }
116
128
  }
117
129
 
118
- function stripMarkdownCode(value) {
119
- return String(value || "").replace(/`/g, "").trim();
120
- }
121
-
122
- function modulePromptBlock(content, key) {
123
- const heading = `## Module: ${key}`;
124
- const start = content.indexOf(heading);
125
- if (start < 0) return "";
126
- const rest = content.slice(start + heading.length);
127
- const next = rest.search(/\n## Module: /);
128
- return next >= 0 ? rest.slice(0, next) : rest;
129
- }
130
-
131
- function checkModuleParallelStructure() {
132
- if (!exists("docs/09-PLANNING/Module-Registry.md")) return;
133
-
134
- requireFile("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md");
135
- const hasPromptPack = exists("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md");
136
- for (const templateFile of [
137
- "docs/09-PLANNING/MODULES/_task-template/task_plan.md",
138
- "docs/09-PLANNING/MODULES/_task-template/progress.md",
139
- "docs/09-PLANNING/MODULES/_task-template/findings.md",
140
- "docs/09-PLANNING/MODULES/_task-template/review.md",
141
- ]) {
142
- requireFile(templateFile);
143
- }
144
-
145
- const registryContent = read("docs/09-PLANNING/Module-Registry.md");
146
- for (const term of ["PREFIX", "Current Step", "Status", "Write Scope"]) {
147
- if (!registryContent.includes(term)) {
148
- fail(`docs/09-PLANNING/Module-Registry.md missing registry column or section: ${term}`);
149
- }
150
- }
151
-
152
- const registryRows = markdownTable(registryContent)
153
- .filter((cells) => cells.length >= 6)
154
- .filter((cells) => /^(_shared|[a-z][a-z0-9-]*)$/.test(cells[0] || "") && /^[A-Z]{2,5}$/.test(cells[2] || ""));
155
-
156
- if (registryRows.length === 0) {
157
- fail("docs/09-PLANNING/Module-Registry.md has no active module rows");
158
- }
159
-
160
- const promptPack = hasPromptPack ? read("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md") : "";
161
- if (hasPromptPack && !/Subagent Worker Invariant|worker[\s\S]{0,120}worktree[\s\S]{0,120}commit SHA/i.test(promptPack)) {
162
- fail("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md missing subagent worker worktree/commit handoff rule");
163
- }
164
- for (const cells of registryRows) {
165
- const [key, , prefix, branch, currentStep, status] = cells;
166
- requireFile(`docs/09-PLANNING/MODULES/${key}/module_plan.md`);
167
- if (!/^(planned|in-progress|paused|completed)$/.test(status)) {
168
- fail(`docs/09-PLANNING/Module-Registry.md row ${key} has invalid status: ${status}`);
169
- }
170
- if (currentStep !== `${prefix}-00` && !currentStep.startsWith(`${prefix}-`)) {
171
- fail(`docs/09-PLANNING/Module-Registry.md row ${key} current step does not match prefix ${prefix}: ${currentStep}`);
172
- }
173
- const branchName = stripMarkdownCode(branch);
174
- if (!branchName.startsWith("codex/")) {
175
- fail(`docs/09-PLANNING/Module-Registry.md row ${key} branch must use codex/ prefix: ${branch}`);
176
- }
177
-
178
- const block = modulePromptBlock(promptPack, key);
179
- if (!block) {
180
- if (!exists(`docs/09-PLANNING/MODULES/${key}/session_prompt.md`)) {
181
- fail(`missing module session prompt for ${key}`);
182
- }
183
- continue;
184
- }
185
- for (const term of [
186
- "Current Step",
187
- branchName,
188
- "Preflight:",
189
- "Before code edits:",
190
- "Write scope:",
191
- "Forbidden without coordination:",
192
- "Shared Coordination:",
193
- "Verification:",
194
- "Closeout:",
195
- "Stop conditions:",
196
- ]) {
197
- if (!block.includes(term)) {
198
- fail(`module session prompt for ${key} missing required term: ${term}`);
199
- }
200
- }
201
- }
202
- checkModuleTaskSsotIndex(registryRows);
203
- }
204
-
205
- function checkModuleTaskSsotIndex(registryRows) {
206
- const registryByModule = new Map(registryRows.map((cells) => [cells[0], cells]));
207
- const ledgerContent = exists("docs/Harness-Ledger.md") ? read("docs/Harness-Ledger.md") : "";
208
- const taskPlans = listModuleTaskPlans();
209
-
210
- for (const taskPlanPath of taskPlans) {
211
- const parsed = parseModuleTaskPath(taskPlanPath);
212
- if (!parsed) continue;
213
- const { moduleKey, taskDir } = parsed;
214
- const modulePlanPath = `docs/09-PLANNING/MODULES/${moduleKey}/module_plan.md`;
215
- if (!exists(modulePlanPath)) continue;
216
-
217
- const taskPlan = read(taskPlanPath);
218
- const taskProgress = readTaskProgress(taskPlanPath);
219
- const taskProgressStatus = readTaskProgressStatus(taskPlanPath);
220
- const taskIsActive = isActiveModuleTaskStatus(taskProgressStatus);
221
- const stepId = extractStepId(taskPlan, taskDir);
222
- if (!stepId) {
223
- if (taskIsActive) {
224
- fail(`${taskPlanPath} does not expose a Step ID and task directory does not start with <PREFIX-NN>`);
225
- }
226
- continue;
227
- }
228
-
229
- const modulePlan = read(modulePlanPath);
230
- const moduleRelativeTaskPlan = `TASKS/${taskDir}/task_plan.md`;
231
- if (!modulePlan.includes(stepId) || !modulePlan.includes(moduleRelativeTaskPlan)) {
232
- fail(`${modulePlanPath} does not index ${stepId} task plan ${moduleRelativeTaskPlan}`);
233
- }
234
-
235
- if (!taskIsActive) continue;
236
-
237
- const registryRow = registryByModule.get(moduleKey);
238
- const reviewPath = taskPlanPath.replace(/task_plan\.md$/, "review.md");
239
- const registrySynced = Boolean(registryRow && registryRow[4] === stepId);
240
- const ledgerSynced = ledgerContent.includes(taskPlanPath) && (!exists(reviewPath) || ledgerContent.includes(reviewPath));
241
- if (registrySynced && ledgerSynced) continue;
242
-
243
- if (requireGlobalModuleSync) {
244
- if (!registryRow) {
245
- fail(`docs/09-PLANNING/Module-Registry.md does not include active module ${moduleKey} for ${taskPlanPath}`);
246
- } else if (registryRow[4] !== stepId) {
247
- fail(`docs/09-PLANNING/Module-Registry.md row ${moduleKey} current step is ${registryRow[4]}, but active task is ${stepId}`);
248
- }
249
- if (!ledgerContent.includes(taskPlanPath)) {
250
- fail(`docs/Harness-Ledger.md does not index active module task plan ${taskPlanPath}`);
251
- }
252
- if (exists(reviewPath) && !ledgerContent.includes(reviewPath)) {
253
- fail(`docs/Harness-Ledger.md does not index active module review ${reviewPath}`);
254
- }
255
- continue;
256
- }
257
-
258
- if (hasPendingCoordinatorHandoff(taskPlan, taskProgress)) {
259
- warn(`${taskPlanPath} has pending coordinator handoff; run coordinator pass before final integration or set HARNESS_REQUIRE_GLOBAL_MODULE_SYNC=1 for strict gate`);
260
- continue;
261
- }
262
- fail(`${taskPlanPath} is active but is neither globally synced nor marked with Coordinator Handoff: pending-coordinator-pass`);
263
- }
264
- }
265
-
266
- function listModuleTaskPlans() {
267
- const modulesRoot = filePath("docs/09-PLANNING/MODULES");
268
- if (!fs.existsSync(modulesRoot)) return [];
269
- const results = [];
270
- function walk(dir) {
271
- for (const entry of fs.readdirSync(dir)) {
272
- const full = path.join(dir, entry);
273
- const relativePath = rel(path.relative(targetRoot, full));
274
- const stat = fs.statSync(full);
275
- if (stat.isDirectory()) {
276
- if (relativePath.includes("/_archive/") || relativePath.endsWith("/_task-template")) continue;
277
- walk(full);
278
- } else if (/\/TASKS\/[^/]+\/task_plan\.md$/.test(relativePath)) {
279
- results.push(relativePath);
280
- }
281
- }
282
- }
283
- walk(modulesRoot);
284
- return results;
285
- }
286
-
287
- function parseModuleTaskPath(taskPlanPath) {
288
- const match = taskPlanPath.match(/^docs\/09-PLANNING\/MODULES\/([^/]+)\/TASKS\/([^/]+)\/task_plan\.md$/);
289
- if (!match) return null;
290
- return { moduleKey: match[1], taskDir: match[2] };
291
- }
292
-
293
- function extractStepId(taskPlanContent, taskDir) {
294
- const fromPlan = taskPlanContent.match(/^- Step ID:\s*`?([A-Z]{2,5}-\d{2})`?/m);
295
- if (fromPlan) return fromPlan[1];
296
- const fromModuleSection = taskPlanContent.match(/^- Step:\s*`?([A-Z]{2,5}-\d{2})`?/m);
297
- if (fromModuleSection) return fromModuleSection[1];
298
- const fromDir = taskDir.match(/^([A-Z]{2,5}-\d{2})-/);
299
- return fromDir ? fromDir[1] : "";
300
- }
301
-
302
- function readTaskProgress(taskPlanPath) {
303
- const progressPath = taskPlanPath.replace(/task_plan\.md$/, "progress.md");
304
- return exists(progressPath) ? read(progressPath) : "";
305
- }
306
-
307
- function readTaskProgressStatus(taskPlanPath) {
308
- const progress = readTaskProgress(taskPlanPath);
309
- if (!progress) return "";
310
- const match = progress.match(/^##\s*(?:Status|状态)\s*[::]?\s*(?:\n\s*)?([^\n]+)/im);
311
- return match ? normalizeModuleTaskStatus(stripMarkdownCode(match[1])) : "";
312
- }
313
-
314
- function normalizeModuleTaskStatus(status) {
315
- const value = String(status || "").trim().toLowerCase();
316
- const aliases = new Map([
317
- ["未开始", "not-started"],
318
- ["未启动", "not-started"],
319
- ["进行中", "in-progress"],
320
- ["开发中", "in-progress"],
321
- ["规划审查", "planning-review"],
322
- ["已完成", "completed"],
323
- ["完成", "completed"],
324
- ["已关闭", "closed"],
325
- ["关闭", "closed"],
326
- ["已阻塞", "blocked"],
327
- ["阻塞", "blocked"],
328
- ]);
329
- return aliases.get(value) || value;
330
- }
331
-
332
- function isActiveModuleTaskStatus(status) {
333
- if (!status) return false;
334
- return !new Set([
335
- "not-started",
336
- "blocked-not-started",
337
- "complete",
338
- "completed",
339
- "closed",
340
- "closed-with-residual",
341
- "closed-local-only",
342
- "superseded",
343
- ]).has(status);
344
- }
345
-
346
- function hasPendingCoordinatorHandoff(taskPlanContent, progressContent) {
347
- const combined = `${taskPlanContent}\n${progressContent}`;
348
- return /Coordinator Handoff/i.test(combined) && /pending-coordinator-pass/i.test(combined);
349
- }
350
-
351
130
  function checkAgentsIndex() {
352
131
  if (!exists("AGENTS.md")) return;
353
132
  const content = read("AGENTS.md");
@@ -387,15 +166,15 @@ function checkGovernanceContent() {
387
166
  if (!exists(governancePath)) return;
388
167
  const content = read(governancePath);
389
168
  const requiredTerms = [
390
- "Repo Platform Profile",
391
- "Branch Model",
392
- "PR Policy",
393
- "Required Checks",
394
- "Branch Protection",
395
- "Worktree Concurrency",
169
+ ["Repo Platform Profile", "仓库平台画像"],
170
+ ["Branch Model", "分支模型"],
171
+ ["PR Policy", "PR 规则"],
172
+ ["Required Checks", "必要检查"],
173
+ ["Branch Protection", "分支保护"],
174
+ ["Worktree Concurrency", "Worktree 并发"],
396
175
  ];
397
- for (const term of requiredTerms) {
398
- if (!content.includes(term)) fail(`${governancePath} missing section: ${term}`);
176
+ for (const terms of requiredTerms) {
177
+ if (!contentIncludesAny(content, terms)) fail(`${governancePath} missing section: ${terms[0]}`);
399
178
  }
400
179
  if (!statusWords.some((status) => content.includes(status))) {
401
180
  fail(`${governancePath} does not use evidence status model`);
@@ -408,13 +187,13 @@ function checkCiCdContent() {
408
187
  if (!exists(ciPath)) return;
409
188
  const content = read(ciPath);
410
189
  const requiredTerms = [
411
- "CI Profile",
412
- "Workflow",
413
- "Required Checks",
414
- "Evidence Status",
190
+ ["CI Profile", "CI 画像"],
191
+ ["Workflow", "工作流"],
192
+ ["Required Checks", "必要检查"],
193
+ ["Evidence Status", "证据状态"],
415
194
  ];
416
- for (const term of requiredTerms) {
417
- if (!content.includes(term)) fail(`${ciPath} missing section: ${term}`);
195
+ for (const terms of requiredTerms) {
196
+ if (!contentIncludesAny(content, terms)) fail(`${ciPath} missing section: ${terms[0]}`);
418
197
  }
419
198
  if (!statusWords.some((status) => content.includes(status))) {
420
199
  fail(`${ciPath} does not use evidence status model`);
@@ -428,14 +207,14 @@ function checkDeliveryOperatingModelContent() {
428
207
  const content = read(deliveryPath);
429
208
  const normalized = content.toLowerCase();
430
209
  const requiredTerms = [
431
- "operating model profile",
432
- "work decomposition rule",
433
- "agent visibility",
434
- "integration owner",
435
- "delivery ssot",
210
+ ["operating model profile", "运行模型"],
211
+ ["work decomposition rule", "工作拆分规则"],
212
+ ["agent visibility", "agent 可见性"],
213
+ ["integration owner", "集成 owner"],
214
+ ["delivery ssot", "交付 ssot"],
436
215
  ];
437
- for (const term of requiredTerms) {
438
- if (!normalized.includes(term)) fail(`${deliveryPath} missing section: ${term}`);
216
+ for (const terms of requiredTerms) {
217
+ if (!contentIncludesAny(normalized, terms.map((term) => term.toLowerCase()))) fail(`${deliveryPath} missing section: ${terms[0]}`);
439
218
  }
440
219
  if (!/solo-orchestrator|team-feature-lead|split-repo-contract|program-multi-repo|waterfall-stage-gate|kanban-continuous/.test(content)) {
441
220
  fail(`${deliveryPath} does not define a recognized operating model`);
@@ -480,7 +259,7 @@ function checkReviewTemplate() {
480
259
  const reviewPath = "docs/09-PLANNING/TASKS/_task-template/review.md";
481
260
  if (!exists(reviewPath)) return;
482
261
  const content = read(reviewPath);
483
- if (!content.includes("Confidence Challenge")) {
262
+ if (!contentIncludesAny(content, ["Confidence Challenge", "信心挑战"])) {
484
263
  fail(`${reviewPath} missing Confidence Challenge`);
485
264
  }
486
265
  if (/\|\s*R-001\s*\|\s*P[01]\s*\|.*\|\s*open\s*\|/i.test(content)) {
@@ -515,8 +294,12 @@ function findHeaderIndex(rows, pattern) {
515
294
  return rows.findIndex((cells) => cells.some((cell) => pattern.test(cell)));
516
295
  }
517
296
 
518
- function columnIndex(header, pattern) {
519
- return header.findIndex((cell) => pattern.test(cell));
297
+ function contentIncludesAny(content, terms) {
298
+ return terms.some((term) => (term instanceof RegExp ? term.test(content) : content.includes(term)));
299
+ }
300
+
301
+ function columnIndexAny(header, patterns) {
302
+ return header.findIndex((cell) => patterns.some((pattern) => pattern.test(cell)));
520
303
  }
521
304
 
522
305
  function checkDuplicateIds(rows, sourcePath) {
@@ -535,9 +318,9 @@ function checkCloseoutSsot() {
535
318
  if (!exists(closeoutPath)) return;
536
319
 
537
320
  const closeoutContent = read(closeoutPath);
538
- for (const term of ["Walkthrough", "Lessons Check", "Closeout Status"]) {
539
- if (!closeoutContent.includes(term)) {
540
- fail(`${closeoutPath} missing required closeout column or section: ${term}`);
321
+ for (const terms of [["Walkthrough"], ["Lessons Check", "Lessons 检查"], ["Closeout Status", "收口状态"]]) {
322
+ if (!contentIncludesAny(closeoutContent, terms)) {
323
+ fail(`${closeoutPath} missing required closeout column or section: ${terms[0]}`);
541
324
  }
542
325
  }
543
326
  checkNoGenericPlaceholders(closeoutPath);
@@ -545,7 +328,7 @@ function checkCloseoutSsot() {
545
328
  const closeoutTable = markdownTable(closeoutContent);
546
329
  const closeoutHeaderIndex = findHeaderIndex(closeoutTable, /^Harness ID$/i);
547
330
  const closeoutHeader = closeoutHeaderIndex >= 0 ? closeoutTable[closeoutHeaderIndex] : [];
548
- const lessonsColumn = columnIndex(closeoutHeader, /^Lessons Check$/i);
331
+ const lessonsColumn = columnIndexAny(closeoutHeader, [/^Lessons Check$/i, /^Lessons 检查$/i, /^Lessons 检查\s*\/\s*Lessons Check$/i]);
549
332
  if (lessonsColumn < 0) {
550
333
  fail(`${closeoutPath} missing Lessons Check column`);
551
334
  }
@@ -553,10 +336,11 @@ function checkCloseoutSsot() {
553
336
  if (!exists("docs/Harness-Ledger.md")) return;
554
337
  const ledgerContent = read("docs/Harness-Ledger.md");
555
338
  const lessonIds = collectLessonIds();
339
+ const lessonCandidateIds = collectLessonCandidateIds();
556
340
  const ledgerTable = markdownTable(ledgerContent);
557
341
  const ledgerHeaderIndex = findHeaderIndex(ledgerTable, /^ID$/i);
558
342
  const ledgerHeader = ledgerHeaderIndex >= 0 ? ledgerTable[ledgerHeaderIndex] : [];
559
- const ledgerLessonsColumn = columnIndex(ledgerHeader, /^Lessons Check$/i);
343
+ const ledgerLessonsColumn = columnIndexAny(ledgerHeader, [/^Lessons Check$/i, /^Lessons 检查$/i, /^Lessons 检查\s*\/\s*Lessons Check$/i]);
560
344
  if (ledgerLessonsColumn < 0) {
561
345
  fail("docs/Harness-Ledger.md missing Lessons Check column");
562
346
  }
@@ -587,86 +371,82 @@ function checkCloseoutSsot() {
587
371
  if (lessonsColumn >= 0) {
588
372
  const lessonsCheck = closeout[lessonsColumn] || "";
589
373
  const createdMatch = lessonsCheck.match(lessonsCreatedPattern);
590
- if (!createdMatch && !lessonsNonePattern.test(lessonsCheck)) {
591
- fail(`${closeoutPath} row ${id} needs Lessons Check value: checked-created:<lesson-id> or checked-none:<reason>`);
374
+ const candidateMatch = lessonsCheck.match(lessonsCandidatePattern) || lessonsCheck.match(lessonsQueuedPromotionPattern);
375
+ if (!createdMatch && !candidateMatch && !lessonsNonePattern.test(lessonsCheck)) {
376
+ fail(`${closeoutPath} row ${id} needs Lessons Check value: checked-created:<lesson-id>, checked-candidate:<candidate-id>, queued-promotion:<candidate-id>, or checked-none:<reason>`);
592
377
  } else if (createdMatch && !lessonIds.has(createdMatch[1])) {
593
- fail(`${closeoutPath} row ${id} references missing Lessons SSoT id: ${createdMatch[1]}`);
378
+ fail(`${closeoutPath} row ${id} references missing lesson detail doc id: ${createdMatch[1]}`);
379
+ } else if (candidateMatch && !lessonCandidateIds.has(candidateMatch[1])) {
380
+ fail(`${closeoutPath} row ${id} references missing lesson candidate id: ${candidateMatch[1]}`);
594
381
  }
595
382
  }
596
383
 
597
384
  if (ledgerLessonsColumn >= 0) {
598
385
  const ledgerLessonsCheck = cells[ledgerLessonsColumn] || "";
599
386
  const ledgerCreatedMatch = ledgerLessonsCheck.match(lessonsCreatedPattern);
600
- if (!ledgerCreatedMatch && !lessonsNonePattern.test(ledgerLessonsCheck)) {
601
- fail(`docs/Harness-Ledger.md row ${id} needs Lessons Check value: checked-created:<lesson-id> or checked-none:<reason>`);
387
+ const ledgerCandidateMatch = ledgerLessonsCheck.match(lessonsCandidatePattern) || ledgerLessonsCheck.match(lessonsQueuedPromotionPattern);
388
+ if (!ledgerCreatedMatch && !ledgerCandidateMatch && !lessonsNonePattern.test(ledgerLessonsCheck)) {
389
+ fail(`docs/Harness-Ledger.md row ${id} needs Lessons Check value: checked-created:<lesson-id>, checked-candidate:<candidate-id>, queued-promotion:<candidate-id>, or checked-none:<reason>`);
602
390
  } else if (ledgerCreatedMatch && !lessonIds.has(ledgerCreatedMatch[1])) {
603
- fail(`docs/Harness-Ledger.md row ${id} references missing Lessons SSoT id: ${ledgerCreatedMatch[1]}`);
391
+ fail(`docs/Harness-Ledger.md row ${id} references missing lesson detail doc id: ${ledgerCreatedMatch[1]}`);
392
+ } else if (ledgerCandidateMatch && !lessonCandidateIds.has(ledgerCandidateMatch[1])) {
393
+ fail(`docs/Harness-Ledger.md row ${id} references missing lesson candidate id: ${ledgerCandidateMatch[1]}`);
604
394
  }
605
395
  }
606
396
  }
607
397
  }
608
398
 
609
399
  function collectLessonIds() {
610
- const lessonsPath = "docs/01-GOVERNANCE/Lessons-SSoT.md";
611
- if (!exists(lessonsPath)) return new Set();
612
- const table = markdownTable(read(lessonsPath));
613
- const headerIndex = findHeaderIndex(table, /^ID$/i);
614
- const header = headerIndex >= 0 ? table[headerIndex] : [];
615
- const idColumn = columnIndex(header, /^ID$/i);
616
- if (idColumn < 0) return new Set();
617
- return new Set(
618
- table
619
- .map((cells) => cells[idColumn] || "")
620
- .filter((id) => /^L-\d{4}(-\d{2}-\d{2})?-\d+/i.test(id)),
621
- );
400
+ const root = filePath("docs/01-GOVERNANCE/lessons");
401
+ const ids = new Set();
402
+ if (!fs.existsSync(root)) return ids;
403
+ for (const entry of fs.readdirSync(root)) {
404
+ if (!entry.endsWith(".md")) continue;
405
+ const full = path.join(root, entry);
406
+ if (!fs.statSync(full).isFile()) continue;
407
+ const content = fs.readFileSync(full, "utf8");
408
+ const pathMatch = entry.match(/(L-\d{4}(?:-\d{2}-\d{2})?-\d+)/i);
409
+ const titleMatch = content.match(/#\s*(L-\d{4}(?:-\d{2}-\d{2})?-\d+)/i);
410
+ const id = titleMatch?.[1] || pathMatch?.[1];
411
+ if (id) ids.add(id);
412
+ }
413
+ return ids;
414
+ }
415
+
416
+ function collectLessonCandidateIds() {
417
+ const root = filePath("docs/09-PLANNING");
418
+ const ids = new Set();
419
+ if (!fs.existsSync(root)) return ids;
420
+ function visit(dir) {
421
+ for (const entry of fs.readdirSync(dir)) {
422
+ const full = path.join(dir, entry);
423
+ const stat = fs.statSync(full);
424
+ if (stat.isDirectory()) {
425
+ if (entry === "_archive") continue;
426
+ visit(full);
427
+ continue;
428
+ }
429
+ if (entry !== "lesson_candidates.md") continue;
430
+ const content = fs.readFileSync(full, "utf8");
431
+ for (const match of content.matchAll(/\|\s*(LC-[A-Za-z0-9-]+)\s*\|/g)) {
432
+ ids.add(match[1]);
433
+ }
434
+ }
435
+ }
436
+ visit(root);
437
+ return ids;
622
438
  }
623
439
 
624
- function checkLessonsSsot() {
625
- const lessonsPath = "docs/01-GOVERNANCE/Lessons-SSoT.md";
626
- if (!exists(lessonsPath)) return;
627
-
628
- const content = read(lessonsPath);
629
- if (!/Detail Doc/i.test(content)) {
630
- fail(`${lessonsPath} missing Detail Doc column`);
631
- }
632
-
633
- const table = markdownTable(content);
634
- const headerIndex = findHeaderIndex(table, /^ID$/i);
635
- const header = headerIndex >= 0 ? table[headerIndex] : [];
636
- const detailColumn = columnIndex(header, /^(Detail Doc|Detail)$/i);
637
- const idColumn = columnIndex(header, /^ID$/i);
638
- const statusColumn = columnIndex(header, /^Status$/i);
639
- if (idColumn < 0) fail(`${lessonsPath} missing ID column`);
640
- if (detailColumn < 0) fail(`${lessonsPath} missing Detail Doc column`);
641
- if (statusColumn < 0) fail(`${lessonsPath} missing Status column`);
642
- if (idColumn < 0 || detailColumn < 0 || statusColumn < 0) return;
643
-
644
- const lessonRows = table.filter((cells) => /^L-\d{4}(-\d{2}-\d{2})?-\d+/i.test(cells[idColumn] || ""));
645
- for (const cells of lessonRows) {
646
- const id = cells[idColumn] || "";
647
- const status = cells[statusColumn] || "";
648
- const detail = cells[detailColumn] || "";
649
- if (!/pending|approved|merged|rejected|superseded|🟡|🟢|✅|❌|🔀/i.test(status)) {
650
- fail(`${lessonsPath} row ${id} has unrecognized status: ${status}`);
651
- }
652
- const detailMatch = detail.match(/docs\/01-GOVERNANCE\/lessons\/[^|\s`]+\.md/);
653
- if (!detailMatch) {
654
- fail(`${lessonsPath} row ${id} Detail Doc must point to docs/01-GOVERNANCE/lessons/*.md`);
655
- continue;
656
- }
657
- const detailPath = detailMatch[0];
658
- if (!exists(detailPath)) {
659
- fail(`${lessonsPath} row ${id} Detail Doc missing file: ${detailPath}`);
660
- continue;
661
- }
662
- const detailContent = read(detailPath);
663
- if (!detailContent.includes(id)) {
664
- fail(`${detailPath} does not include lesson id ${id}`);
665
- }
666
- for (const requiredTerm of ["背景", "冲突声明"]) {
667
- if (!detailContent.includes(requiredTerm)) {
668
- fail(`${detailPath} missing required lesson section: ${requiredTerm}`);
669
- }
440
+ function checkLessonDetailDocs() {
441
+ const root = filePath("docs/01-GOVERNANCE/lessons");
442
+ if (!fs.existsSync(root)) return;
443
+ for (const entry of fs.readdirSync(root)) {
444
+ if (!entry.endsWith(".md")) continue;
445
+ const relativePath = `docs/01-GOVERNANCE/lessons/${entry}`;
446
+ const content = read(relativePath);
447
+ const id = entry.match(/(L-\d{4}(?:-\d{2}-\d{2})?-\d+)/i)?.[1];
448
+ if (id && !content.includes(id)) {
449
+ fail(`${relativePath} does not include lesson id ${id}`);
670
450
  }
671
451
  }
672
452
  }
@@ -675,7 +455,7 @@ function checkWalkthroughTemplate() {
675
455
  const walkthroughTemplate = "docs/10-WALKTHROUGH/_walkthrough-template.md";
676
456
  if (!exists(walkthroughTemplate)) return;
677
457
  const content = read(walkthroughTemplate);
678
- if (!content.includes("Lessons Reflection")) {
458
+ if (!contentIncludesAny(content, ["Lessons Reflection", "Lessons 回看"])) {
679
459
  fail(`${walkthroughTemplate} missing Lessons Reflection section`);
680
460
  }
681
461
  }
@@ -707,7 +487,7 @@ function main() {
707
487
  checkReviewTemplate();
708
488
  checkHarnessLedger();
709
489
  checkCloseoutSsot();
710
- checkLessonsSsot();
490
+ checkLessonDetailDocs();
711
491
  checkWalkthroughTemplate();
712
492
  checkReferencePlaceholders();
713
493