coding-agent-harness 1.0.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 (139) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +141 -0
  4. package/SKILL.md +423 -0
  5. package/docs-release/README.md +30 -0
  6. package/docs-release/architecture/overview.md +52 -0
  7. package/docs-release/guides/agent-installation.md +139 -0
  8. package/examples/minimal-project/.harness-capabilities.json +8 -0
  9. package/examples/minimal-project/AGENTS.md +4 -0
  10. package/examples/minimal-project/CLAUDE.md +3 -0
  11. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/execution_strategy.md +10 -0
  12. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +11 -0
  13. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/review.md +27 -0
  14. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/task_plan.md +14 -0
  15. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/visual_roadmap.md +11 -0
  16. package/examples/minimal-project/docs/Harness-Ledger.md +6 -0
  17. package/package.json +34 -0
  18. package/references/adversarial-review-standard.md +173 -0
  19. package/references/agents-md-pattern.md +140 -0
  20. package/references/cadence-ledger.md +55 -0
  21. package/references/ci-cd-standard.md +90 -0
  22. package/references/delivery-operating-model-standard.md +145 -0
  23. package/references/docs-directory-standard.md +125 -0
  24. package/references/harness-ledger.md +148 -0
  25. package/references/lessons-governance.md +157 -0
  26. package/references/long-running-task-standard.md +209 -0
  27. package/references/module-parallel-standard.md +292 -0
  28. package/references/planning-loop.md +192 -0
  29. package/references/project-onboarding-audit.md +167 -0
  30. package/references/regression-system.md +89 -0
  31. package/references/repo-governance-standard.md +131 -0
  32. package/references/review-routing-standard.md +103 -0
  33. package/references/ssot-governance.md +111 -0
  34. package/references/walkthrough-closeout.md +135 -0
  35. package/references/worktree-parallel.md +184 -0
  36. package/scripts/check-harness.mjs +728 -0
  37. package/scripts/harness.mjs +201 -0
  38. package/scripts/lib/dashboard-writer.mjs +95 -0
  39. package/scripts/lib/harness-core.mjs +1318 -0
  40. package/scripts/smoke-dashboard.mjs +70 -0
  41. package/scripts/test-harness.mjs +482 -0
  42. package/templates/AGENTS.md.template +82 -0
  43. package/templates/CLAUDE.md.template +12 -0
  44. package/templates/dashboard/assets/app.css +399 -0
  45. package/templates/dashboard/assets/app.js +435 -0
  46. package/templates/dashboard/assets/i18n.js +47 -0
  47. package/templates/dashboard/assets/markdown-reader.js +116 -0
  48. package/templates/dashboard/assets/mermaid-renderer.js +59 -0
  49. package/templates/dashboard/index.html +18 -0
  50. package/templates/ledger/Harness-Ledger.md +39 -0
  51. package/templates/lessons/lesson-arch-process-change.md +47 -0
  52. package/templates/lessons/lesson-new-doc.md +50 -0
  53. package/templates/lessons/lesson-ref-change.md +45 -0
  54. package/templates/planning/execution_strategy.md +40 -0
  55. package/templates/planning/findings.md +24 -0
  56. package/templates/planning/long-running-task-contract.md +69 -0
  57. package/templates/planning/module_plan.md +36 -0
  58. package/templates/planning/module_session_prompt.md +39 -0
  59. package/templates/planning/optional/artifacts/INDEX.md +12 -0
  60. package/templates/planning/optional/references/INDEX.md +13 -0
  61. package/templates/planning/optional/slices/_slice-template/brief.md +27 -0
  62. package/templates/planning/optional/slices/_slice-template/evidence.md +9 -0
  63. package/templates/planning/optional/slices/_slice-template/review.md +31 -0
  64. package/templates/planning/progress.md +33 -0
  65. package/templates/planning/review.md +48 -0
  66. package/templates/planning/task_plan.md +86 -0
  67. package/templates/planning/visual_roadmap.md +28 -0
  68. package/templates/reference/adversarial-review-standard.md +28 -0
  69. package/templates/reference/ci-cd-standard.md +28 -0
  70. package/templates/reference/delivery-operating-model-standard.md +28 -0
  71. package/templates/reference/docs-library-standard.md +28 -0
  72. package/templates/reference/engineering-standard.md +29 -0
  73. package/templates/reference/execution-workflow-standard.md +29 -0
  74. package/templates/reference/harness-ledger-standard.md +26 -0
  75. package/templates/reference/long-running-task-standard.md +28 -0
  76. package/templates/reference/regression-ssot-governance.md +28 -0
  77. package/templates/reference/repo-governance-standard.md +29 -0
  78. package/templates/reference/review-routing-standard.md +29 -0
  79. package/templates/reference/testing-standard.md +28 -0
  80. package/templates/reference/walkthrough-standard.md +28 -0
  81. package/templates/reference/worktree-standard.md +28 -0
  82. package/templates/regression/Cadence-Ledger.md +41 -0
  83. package/templates/ssot/Delivery-SSoT.md +43 -0
  84. package/templates/ssot/Feature-SSoT.md +43 -0
  85. package/templates/ssot/Lessons-SSoT.md +44 -0
  86. package/templates/ssot/Module-Registry.md +43 -0
  87. package/templates/ssot/Regression-SSoT.md +51 -0
  88. package/templates/verifier/verifier-output.md +43 -0
  89. package/templates/walkthrough/Closeout-SSoT.md +43 -0
  90. package/templates/walkthrough/walkthrough-template.md +63 -0
  91. package/templates-zh-CN/AGENTS.md.template +92 -0
  92. package/templates-zh-CN/CLAUDE.md.template +12 -0
  93. package/templates-zh-CN/dashboard/assets/app.css +399 -0
  94. package/templates-zh-CN/dashboard/assets/app.js +435 -0
  95. package/templates-zh-CN/dashboard/assets/i18n.js +47 -0
  96. package/templates-zh-CN/dashboard/assets/markdown-reader.js +116 -0
  97. package/templates-zh-CN/dashboard/assets/mermaid-renderer.js +59 -0
  98. package/templates-zh-CN/dashboard/index.html +18 -0
  99. package/templates-zh-CN/ledger/Harness-Ledger.md +50 -0
  100. package/templates-zh-CN/lessons/lesson-arch-process-change.md +47 -0
  101. package/templates-zh-CN/lessons/lesson-new-doc.md +49 -0
  102. package/templates-zh-CN/lessons/lesson-ref-change.md +59 -0
  103. package/templates-zh-CN/planning/execution_strategy.md +37 -0
  104. package/templates-zh-CN/planning/findings.md +24 -0
  105. package/templates-zh-CN/planning/long-running-task-contract.md +118 -0
  106. package/templates-zh-CN/planning/module_plan.md +43 -0
  107. package/templates-zh-CN/planning/module_session_prompt.md +70 -0
  108. package/templates-zh-CN/planning/optional/artifacts/INDEX.md +13 -0
  109. package/templates-zh-CN/planning/optional/references/INDEX.md +13 -0
  110. package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +35 -0
  111. package/templates-zh-CN/planning/optional/slices/_slice-template/evidence.md +12 -0
  112. package/templates-zh-CN/planning/optional/slices/_slice-template/review.md +37 -0
  113. package/templates-zh-CN/planning/progress.md +29 -0
  114. package/templates-zh-CN/planning/review.md +69 -0
  115. package/templates-zh-CN/planning/task_plan.md +116 -0
  116. package/templates-zh-CN/planning/visual_roadmap.md +24 -0
  117. package/templates-zh-CN/reference/adversarial-review-standard.md +89 -0
  118. package/templates-zh-CN/reference/ci-cd-standard.md +72 -0
  119. package/templates-zh-CN/reference/delivery-operating-model-standard.md +79 -0
  120. package/templates-zh-CN/reference/docs-library-standard.md +59 -0
  121. package/templates-zh-CN/reference/engineering-standard.md +80 -0
  122. package/templates-zh-CN/reference/execution-workflow-standard.md +81 -0
  123. package/templates-zh-CN/reference/harness-ledger-standard.md +91 -0
  124. package/templates-zh-CN/reference/long-running-task-standard.md +156 -0
  125. package/templates-zh-CN/reference/regression-ssot-governance.md +82 -0
  126. package/templates-zh-CN/reference/repo-governance-standard.md +84 -0
  127. package/templates-zh-CN/reference/review-routing-standard.md +82 -0
  128. package/templates-zh-CN/reference/testing-standard.md +72 -0
  129. package/templates-zh-CN/reference/walkthrough-standard.md +83 -0
  130. package/templates-zh-CN/reference/worktree-standard.md +116 -0
  131. package/templates-zh-CN/regression/Cadence-Ledger.md +48 -0
  132. package/templates-zh-CN/ssot/Delivery-SSoT.md +60 -0
  133. package/templates-zh-CN/ssot/Feature-SSoT.md +49 -0
  134. package/templates-zh-CN/ssot/Lessons-SSoT.md +49 -0
  135. package/templates-zh-CN/ssot/Module-Registry.md +48 -0
  136. package/templates-zh-CN/ssot/Regression-SSoT.md +51 -0
  137. package/templates-zh-CN/verifier/verifier-output.md +38 -0
  138. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +42 -0
  139. package/templates-zh-CN/walkthrough/walkthrough-template.md +62 -0
@@ -0,0 +1,728 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ const targetRoot = path.resolve(process.argv[2] || process.cwd());
7
+ const requireGlobalModuleSync = process.env.HARNESS_REQUIRE_GLOBAL_MODULE_SYNC === "1";
8
+
9
+ const requiredFiles = [
10
+ "AGENTS.md",
11
+ "CLAUDE.md",
12
+ "docs/Harness-Ledger.md",
13
+ "docs/11-REFERENCE/testing-standard.md",
14
+ "docs/11-REFERENCE/execution-workflow-standard.md",
15
+ "docs/11-REFERENCE/repo-governance-standard.md",
16
+ "docs/11-REFERENCE/ci-cd-standard.md",
17
+ "docs/11-REFERENCE/long-running-task-standard.md",
18
+ "docs/11-REFERENCE/adversarial-review-standard.md",
19
+ "docs/11-REFERENCE/review-routing-standard.md",
20
+ "docs/11-REFERENCE/docs-library-standard.md",
21
+ "docs/11-REFERENCE/harness-ledger-standard.md",
22
+ "docs/10-WALKTHROUGH/_walkthrough-template.md",
23
+ "docs/10-WALKTHROUGH/Closeout-SSoT.md",
24
+ "docs/05-TEST-QA/Regression-SSoT.md",
25
+ "docs/05-TEST-QA/Cadence-Ledger.md",
26
+ "docs/01-GOVERNANCE/Lessons-SSoT.md",
27
+ ];
28
+
29
+ const legacyPlanningFiles = [
30
+ "docs/09-PLANNING/TASKS/_task-template/task_plan.md",
31
+ "docs/09-PLANNING/TASKS/_task-template/findings.md",
32
+ "docs/09-PLANNING/TASKS/_task-template/progress.md",
33
+ "docs/09-PLANNING/TASKS/_task-template/review.md",
34
+ "docs/09-PLANNING/TASKS/_task-template/long-running-task-contract.md",
35
+ ];
36
+
37
+ const agAgentsRefs = [
38
+ "repo-governance-standard.md",
39
+ "ci-cd-standard.md",
40
+ "execution-workflow-standard.md",
41
+ "adversarial-review-standard.md",
42
+ "review-routing-standard.md",
43
+ "walkthrough-standard.md",
44
+ "Lessons-SSoT.md",
45
+ "harness-ledger-standard.md",
46
+ "Closeout-SSoT.md",
47
+ ];
48
+
49
+ const forbiddenTemplatePatterns = [
50
+ /\[如有[^\]]*\]/,
51
+ /\[[^\]]*(根据项目|框架名|目标覆盖率|列出关键|示例)[^\]]*\]/,
52
+ /\[TODO\]/i,
53
+ /\bTODO\b/,
54
+ /\bTBD\b/i,
55
+ /\[command\]/,
56
+ /\[workflow path\]/,
57
+ /\[owner\/repo or URL\]/,
58
+ ];
59
+
60
+ const statusWords = ["designed", "implemented", "verified", "blocked-with-owner"];
61
+ const closedLedgerStatuses = new Set(["closed", "closed-with-residual", "closed-local-only"]);
62
+ const allowedWalkthroughSkip =
63
+ /walkthrough skipped-with-reason:\s*(docs-only|no-runtime|superseded|historical-backfill|owner-deferred)/i;
64
+ const lessonsCreatedPattern = /checked-created:\s*(L-\d{4}-\d{2}-\d{2}-\d{3}|L-\d+)/i;
65
+ const lessonsNonePattern = /checked-none:\s*\S+/i;
66
+
67
+ const failures = [];
68
+ const warnings = [];
69
+
70
+ function rel(file) {
71
+ return file.split(path.sep).join("/");
72
+ }
73
+
74
+ function filePath(relativePath) {
75
+ return path.join(targetRoot, relativePath);
76
+ }
77
+
78
+ function exists(relativePath) {
79
+ return fs.existsSync(filePath(relativePath));
80
+ }
81
+
82
+ function read(relativePath) {
83
+ return fs.readFileSync(filePath(relativePath), "utf8");
84
+ }
85
+
86
+ function fail(message) {
87
+ failures.push(message);
88
+ }
89
+
90
+ function warn(message) {
91
+ warnings.push(message);
92
+ }
93
+
94
+ function requireFile(relativePath) {
95
+ if (!exists(relativePath)) {
96
+ fail(`missing required file: ${relativePath}`);
97
+ return false;
98
+ }
99
+ return true;
100
+ }
101
+
102
+ function checkRequiredFiles() {
103
+ for (const requiredFile of requiredFiles) {
104
+ requireFile(requiredFile);
105
+ }
106
+ }
107
+
108
+ function checkPlanningStructure() {
109
+ if (exists("docs/09-PLANNING/Module-Registry.md")) {
110
+ checkModuleParallelStructure();
111
+ return;
112
+ }
113
+ for (const legacyFile of legacyPlanningFiles) {
114
+ requireFile(legacyFile);
115
+ }
116
+ }
117
+
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
+ function checkAgentsIndex() {
352
+ if (!exists("AGENTS.md")) return;
353
+ const content = read("AGENTS.md");
354
+ for (const ref of agAgentsRefs) {
355
+ if (!content.includes(ref)) {
356
+ fail(`AGENTS.md does not route to ${ref}`);
357
+ }
358
+ }
359
+ if (exists("docs/11-REFERENCE/delivery-operating-model-standard.md") && !content.includes("delivery-operating-model-standard.md")) {
360
+ fail("AGENTS.md does not route to delivery-operating-model-standard.md");
361
+ }
362
+ if (exists("docs/09-PLANNING/Module-Registry.md")) {
363
+ for (const ref of ["Module-Registry.md", "Session-Prompt-Pack.md"]) {
364
+ if (!content.includes(ref)) {
365
+ fail(`AGENTS.md does not route to ${ref}`);
366
+ }
367
+ }
368
+ if (!/Subagent|worker/i.test(content) || !/worktree/i.test(content) || !/commit SHA|commit/i.test(content)) {
369
+ fail("AGENTS.md does not define subagent worker worktree/commit handoff rule");
370
+ }
371
+ }
372
+ }
373
+
374
+ function checkNoGenericPlaceholders(relativePath, { allowTemplates = false } = {}) {
375
+ if (!exists(relativePath)) return;
376
+ const content = read(relativePath);
377
+ if (allowTemplates) return;
378
+ for (const pattern of forbiddenTemplatePatterns) {
379
+ if (pattern.test(content)) {
380
+ fail(`${relativePath} still contains generic placeholder matching ${pattern}`);
381
+ }
382
+ }
383
+ }
384
+
385
+ function checkGovernanceContent() {
386
+ const governancePath = "docs/11-REFERENCE/repo-governance-standard.md";
387
+ if (!exists(governancePath)) return;
388
+ const content = read(governancePath);
389
+ const requiredTerms = [
390
+ "Repo Platform Profile",
391
+ "Branch Model",
392
+ "PR Policy",
393
+ "Required Checks",
394
+ "Branch Protection",
395
+ "Worktree Concurrency",
396
+ ];
397
+ for (const term of requiredTerms) {
398
+ if (!content.includes(term)) fail(`${governancePath} missing section: ${term}`);
399
+ }
400
+ if (!statusWords.some((status) => content.includes(status))) {
401
+ fail(`${governancePath} does not use evidence status model`);
402
+ }
403
+ checkNoGenericPlaceholders(governancePath);
404
+ }
405
+
406
+ function checkCiCdContent() {
407
+ const ciPath = "docs/11-REFERENCE/ci-cd-standard.md";
408
+ if (!exists(ciPath)) return;
409
+ const content = read(ciPath);
410
+ const requiredTerms = [
411
+ "CI Profile",
412
+ "Workflow",
413
+ "Required Checks",
414
+ "Evidence Status",
415
+ ];
416
+ for (const term of requiredTerms) {
417
+ if (!content.includes(term)) fail(`${ciPath} missing section: ${term}`);
418
+ }
419
+ if (!statusWords.some((status) => content.includes(status))) {
420
+ fail(`${ciPath} does not use evidence status model`);
421
+ }
422
+ checkNoGenericPlaceholders(ciPath);
423
+ }
424
+
425
+ function checkDeliveryOperatingModelContent() {
426
+ const deliveryPath = "docs/11-REFERENCE/delivery-operating-model-standard.md";
427
+ if (!exists(deliveryPath)) return;
428
+ const content = read(deliveryPath);
429
+ const normalized = content.toLowerCase();
430
+ const requiredTerms = [
431
+ "operating model profile",
432
+ "work decomposition rule",
433
+ "agent visibility",
434
+ "integration owner",
435
+ "delivery ssot",
436
+ ];
437
+ for (const term of requiredTerms) {
438
+ if (!normalized.includes(term)) fail(`${deliveryPath} missing section: ${term}`);
439
+ }
440
+ if (!/solo-orchestrator|team-feature-lead|split-repo-contract|program-multi-repo|waterfall-stage-gate|kanban-continuous/.test(content)) {
441
+ fail(`${deliveryPath} does not define a recognized operating model`);
442
+ }
443
+ checkNoGenericPlaceholders(deliveryPath);
444
+ }
445
+
446
+ function checkPrTemplateOrResidual() {
447
+ const templateCandidates = [
448
+ ".github/pull_request_template.md",
449
+ ".github/PULL_REQUEST_TEMPLATE.md",
450
+ ".gitlab/merge_request_templates/default.md",
451
+ ];
452
+ if (templateCandidates.some((candidate) => exists(candidate))) return;
453
+ if (exists("docs/11-REFERENCE/repo-governance-standard.md")) {
454
+ const content = read("docs/11-REFERENCE/repo-governance-standard.md");
455
+ if (/PR template/i.test(content) && /blocked-with-owner|manual setup residual|manual-setup-residual/i.test(content)) {
456
+ warn("PR template missing, but repo governance records residual");
457
+ return;
458
+ }
459
+ }
460
+ fail("missing PR template or explicit blocked-with-owner residual");
461
+ }
462
+
463
+ function checkWorkflowOrResidual() {
464
+ const workflowDir = filePath(".github/workflows");
465
+ const hasGitHubWorkflow =
466
+ fs.existsSync(workflowDir) &&
467
+ fs.readdirSync(workflowDir).some((name) => /\.(ya?ml)$/i.test(name));
468
+ if (hasGitHubWorkflow) return;
469
+ if (exists("docs/11-REFERENCE/ci-cd-standard.md")) {
470
+ const content = read("docs/11-REFERENCE/ci-cd-standard.md");
471
+ if (/blocked-with-owner|unsupported checks|residual/i.test(content)) {
472
+ warn("CI workflow missing, but CI/CD standard records residual");
473
+ return;
474
+ }
475
+ }
476
+ fail("missing CI workflow or explicit blocked-with-owner residual");
477
+ }
478
+
479
+ function checkReviewTemplate() {
480
+ const reviewPath = "docs/09-PLANNING/TASKS/_task-template/review.md";
481
+ if (!exists(reviewPath)) return;
482
+ const content = read(reviewPath);
483
+ if (!content.includes("Confidence Challenge")) {
484
+ fail(`${reviewPath} missing Confidence Challenge`);
485
+ }
486
+ if (/\|\s*R-001\s*\|\s*P[01]\s*\|.*\|\s*open\s*\|/i.test(content)) {
487
+ fail(`${reviewPath} ships with an open P0/P1 example finding`);
488
+ }
489
+ }
490
+
491
+ function checkHarnessLedger() {
492
+ if (!exists("docs/Harness-Ledger.md")) return;
493
+ const content = read("docs/Harness-Ledger.md");
494
+ if (!/Repo Governance|CI\/CD|ci-cd|repo-governance/i.test(content)) {
495
+ fail("docs/Harness-Ledger.md does not mention repo governance / CI-CD update status");
496
+ }
497
+ }
498
+
499
+ function markdownTableRows(content, idPattern) {
500
+ return content
501
+ .split(/\r?\n/)
502
+ .filter((line) => line.trim().startsWith("|"))
503
+ .map((line) => line.split("|").slice(1, -1).map((cell) => cell.trim()))
504
+ .filter((cells) => cells.length > 0 && idPattern.test(cells[0] || ""));
505
+ }
506
+
507
+ function markdownTable(content) {
508
+ return content
509
+ .split(/\r?\n/)
510
+ .filter((line) => line.trim().startsWith("|"))
511
+ .map((line) => line.split("|").slice(1, -1).map((cell) => cell.trim()));
512
+ }
513
+
514
+ function findHeaderIndex(rows, pattern) {
515
+ return rows.findIndex((cells) => cells.some((cell) => pattern.test(cell)));
516
+ }
517
+
518
+ function columnIndex(header, pattern) {
519
+ return header.findIndex((cell) => pattern.test(cell));
520
+ }
521
+
522
+ function checkDuplicateIds(rows, sourcePath) {
523
+ const seen = new Set();
524
+ for (const cells of rows) {
525
+ const id = cells[0];
526
+ if (seen.has(id)) {
527
+ fail(`${sourcePath} contains duplicate closeout id: ${id}`);
528
+ }
529
+ seen.add(id);
530
+ }
531
+ }
532
+
533
+ function checkCloseoutSsot() {
534
+ const closeoutPath = "docs/10-WALKTHROUGH/Closeout-SSoT.md";
535
+ if (!exists(closeoutPath)) return;
536
+
537
+ 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}`);
541
+ }
542
+ }
543
+ checkNoGenericPlaceholders(closeoutPath);
544
+
545
+ const closeoutTable = markdownTable(closeoutContent);
546
+ const closeoutHeaderIndex = findHeaderIndex(closeoutTable, /^Harness ID$/i);
547
+ const closeoutHeader = closeoutHeaderIndex >= 0 ? closeoutTable[closeoutHeaderIndex] : [];
548
+ const lessonsColumn = columnIndex(closeoutHeader, /^Lessons Check$/i);
549
+ if (lessonsColumn < 0) {
550
+ fail(`${closeoutPath} missing Lessons Check column`);
551
+ }
552
+
553
+ if (!exists("docs/Harness-Ledger.md")) return;
554
+ const ledgerContent = read("docs/Harness-Ledger.md");
555
+ const lessonIds = collectLessonIds();
556
+ const ledgerTable = markdownTable(ledgerContent);
557
+ const ledgerHeaderIndex = findHeaderIndex(ledgerTable, /^ID$/i);
558
+ const ledgerHeader = ledgerHeaderIndex >= 0 ? ledgerTable[ledgerHeaderIndex] : [];
559
+ const ledgerLessonsColumn = columnIndex(ledgerHeader, /^Lessons Check$/i);
560
+ if (ledgerLessonsColumn < 0) {
561
+ fail("docs/Harness-Ledger.md missing Lessons Check column");
562
+ }
563
+ const ledgerRows = markdownTableRows(ledgerContent, /^H-\d+/i);
564
+ const closeoutTableRows = markdownTableRows(closeoutContent, /^H-\d+/i);
565
+ checkDuplicateIds(ledgerRows, "docs/Harness-Ledger.md");
566
+ checkDuplicateIds(closeoutTableRows, closeoutPath);
567
+ const closeoutRows = new Map(closeoutTableRows.map((cells) => [cells[0], cells]));
568
+
569
+ for (const cells of ledgerRows) {
570
+ const id = cells[0];
571
+ const status = (cells[cells.length - 1] || "").toLowerCase();
572
+ if (!closedLedgerStatuses.has(status)) continue;
573
+
574
+ const closeout = closeoutRows.get(id);
575
+ if (!closeout) {
576
+ fail(`${closeoutPath} missing row for closed Harness Ledger item ${id}`);
577
+ continue;
578
+ }
579
+
580
+ const joined = closeout.join(" ");
581
+ const hasWalkthrough = /docs\/10-WALKTHROUGH\/[^|\s]+\.md/.test(joined);
582
+ const hasAllowedSkip = allowedWalkthroughSkip.test(joined);
583
+ if (!hasWalkthrough && !hasAllowedSkip) {
584
+ fail(`${closeoutPath} row ${id} needs walkthrough path or allowed skipped-with-reason`);
585
+ }
586
+
587
+ if (lessonsColumn >= 0) {
588
+ const lessonsCheck = closeout[lessonsColumn] || "";
589
+ 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>`);
592
+ } else if (createdMatch && !lessonIds.has(createdMatch[1])) {
593
+ fail(`${closeoutPath} row ${id} references missing Lessons SSoT id: ${createdMatch[1]}`);
594
+ }
595
+ }
596
+
597
+ if (ledgerLessonsColumn >= 0) {
598
+ const ledgerLessonsCheck = cells[ledgerLessonsColumn] || "";
599
+ 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>`);
602
+ } else if (ledgerCreatedMatch && !lessonIds.has(ledgerCreatedMatch[1])) {
603
+ fail(`docs/Harness-Ledger.md row ${id} references missing Lessons SSoT id: ${ledgerCreatedMatch[1]}`);
604
+ }
605
+ }
606
+ }
607
+ }
608
+
609
+ 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
+ );
622
+ }
623
+
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
+ }
670
+ }
671
+ }
672
+ }
673
+
674
+ function checkWalkthroughTemplate() {
675
+ const walkthroughTemplate = "docs/10-WALKTHROUGH/_walkthrough-template.md";
676
+ if (!exists(walkthroughTemplate)) return;
677
+ const content = read(walkthroughTemplate);
678
+ if (!content.includes("Lessons Reflection")) {
679
+ fail(`${walkthroughTemplate} missing Lessons Reflection section`);
680
+ }
681
+ }
682
+
683
+ function checkReferencePlaceholders() {
684
+ const refDir = filePath("docs/11-REFERENCE");
685
+ if (!fs.existsSync(refDir)) return;
686
+ for (const entry of fs.readdirSync(refDir)) {
687
+ const full = path.join(refDir, entry);
688
+ if (!fs.statSync(full).isFile() || !entry.endsWith(".md")) continue;
689
+ checkNoGenericPlaceholders(rel(path.relative(targetRoot, full)));
690
+ }
691
+ }
692
+
693
+ function main() {
694
+ if (!fs.existsSync(targetRoot)) {
695
+ console.error(`Target path does not exist: ${targetRoot}`);
696
+ process.exit(2);
697
+ }
698
+
699
+ checkRequiredFiles();
700
+ checkPlanningStructure();
701
+ checkAgentsIndex();
702
+ checkGovernanceContent();
703
+ checkCiCdContent();
704
+ checkDeliveryOperatingModelContent();
705
+ checkPrTemplateOrResidual();
706
+ checkWorkflowOrResidual();
707
+ checkReviewTemplate();
708
+ checkHarnessLedger();
709
+ checkCloseoutSsot();
710
+ checkLessonsSsot();
711
+ checkWalkthroughTemplate();
712
+ checkReferencePlaceholders();
713
+
714
+ if (warnings.length > 0) {
715
+ console.log("Warnings:");
716
+ for (const warning of warnings) console.log(`- ${warning}`);
717
+ }
718
+
719
+ if (failures.length > 0) {
720
+ console.error("Harness check failed:");
721
+ for (const failure of failures) console.error(`- ${failure}`);
722
+ process.exit(1);
723
+ }
724
+
725
+ console.log(`Harness check passed: ${targetRoot}`);
726
+ }
727
+
728
+ main();