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
@@ -0,0 +1,237 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ function stripMarkdownCode(value) {
5
+ return String(value || "").replace(/`/g, "").trim();
6
+ }
7
+
8
+ function modulePromptBlock(content, key) {
9
+ const heading = `## Module: ${key}`;
10
+ const start = content.indexOf(heading);
11
+ if (start < 0) return "";
12
+ const rest = content.slice(start + heading.length);
13
+ const next = rest.search(/\n## Module: /);
14
+ return next >= 0 ? rest.slice(0, next) : rest;
15
+ }
16
+
17
+ function listModuleTaskPlans({ targetRoot, rel, filePath }) {
18
+ const modulesRoot = filePath("docs/09-PLANNING/MODULES");
19
+ if (!fs.existsSync(modulesRoot)) return [];
20
+ const results = [];
21
+ function walk(dir) {
22
+ for (const entry of fs.readdirSync(dir)) {
23
+ const full = path.join(dir, entry);
24
+ const relativePath = rel(path.relative(targetRoot, full));
25
+ const stat = fs.statSync(full);
26
+ if (stat.isDirectory()) {
27
+ if (relativePath.includes("/_archive/") || relativePath.endsWith("/_task-template")) continue;
28
+ walk(full);
29
+ } else if (/\/TASKS\/[^/]+\/task_plan\.md$/.test(relativePath)) {
30
+ results.push(relativePath);
31
+ }
32
+ }
33
+ }
34
+ walk(modulesRoot);
35
+ return results;
36
+ }
37
+
38
+ function parseModuleTaskPath(taskPlanPath) {
39
+ const match = taskPlanPath.match(/^docs\/09-PLANNING\/MODULES\/([^/]+)\/TASKS\/([^/]+)\/task_plan\.md$/);
40
+ if (!match) return null;
41
+ return { moduleKey: match[1], taskDir: match[2] };
42
+ }
43
+
44
+ function extractStepId(taskPlanContent, taskDir) {
45
+ const fromPlan = taskPlanContent.match(/^- Step ID:\s*`?([A-Z]{2,5}-\d{2})`?/m);
46
+ if (fromPlan) return fromPlan[1];
47
+ const fromModuleSection = taskPlanContent.match(/^- Step:\s*`?([A-Z]{2,5}-\d{2})`?/m);
48
+ if (fromModuleSection) return fromModuleSection[1];
49
+ const fromDir = taskDir.match(/^([A-Z]{2,5}-\d{2})-/);
50
+ return fromDir ? fromDir[1] : "";
51
+ }
52
+
53
+ function readTaskProgress(taskPlanPath, { exists, read }) {
54
+ const progressPath = taskPlanPath.replace(/task_plan\.md$/, "progress.md");
55
+ return exists(progressPath) ? read(progressPath) : "";
56
+ }
57
+
58
+ function normalizeModuleTaskStatus(status) {
59
+ const value = String(status || "").trim().toLowerCase();
60
+ const aliases = new Map([
61
+ ["未开始", "not-started"],
62
+ ["未启动", "not-started"],
63
+ ["进行中", "in-progress"],
64
+ ["开发中", "in-progress"],
65
+ ["规划审查", "planning-review"],
66
+ ["已完成", "completed"],
67
+ ["完成", "completed"],
68
+ ["已关闭", "closed"],
69
+ ["关闭", "closed"],
70
+ ["已阻塞", "blocked"],
71
+ ["阻塞", "blocked"],
72
+ ]);
73
+ return aliases.get(value) || value;
74
+ }
75
+
76
+ function readTaskProgressStatus(taskPlanPath, context) {
77
+ const progress = readTaskProgress(taskPlanPath, context);
78
+ if (!progress) return "";
79
+ const match = progress.match(/^##\s*(?:Status|状态)\s*[::]?\s*(?:\n\s*)?([^\n]+)/im);
80
+ return match ? normalizeModuleTaskStatus(stripMarkdownCode(match[1])) : "";
81
+ }
82
+
83
+ function isActiveModuleTaskStatus(status) {
84
+ if (!status) return false;
85
+ return !new Set([
86
+ "not-started",
87
+ "blocked-not-started",
88
+ "complete",
89
+ "completed",
90
+ "closed",
91
+ "closed-with-residual",
92
+ "closed-local-only",
93
+ "superseded",
94
+ ]).has(status);
95
+ }
96
+
97
+ function hasPendingCoordinatorHandoff(taskPlanContent, progressContent) {
98
+ const combined = `${taskPlanContent}\n${progressContent}`;
99
+ return /Coordinator Handoff/i.test(combined) && /pending-coordinator-pass/i.test(combined);
100
+ }
101
+
102
+ function checkModuleTaskSsotIndex(registryRows, context) {
103
+ const { exists, read, fail, warn, requireGlobalModuleSync } = context;
104
+ const registryByModule = new Map(registryRows.map((cells) => [cells[0], cells]));
105
+ const ledgerContent = exists("docs/Harness-Ledger.md") ? read("docs/Harness-Ledger.md") : "";
106
+ const taskPlans = listModuleTaskPlans(context);
107
+
108
+ for (const taskPlanPath of taskPlans) {
109
+ const parsed = parseModuleTaskPath(taskPlanPath);
110
+ if (!parsed) continue;
111
+ const { moduleKey, taskDir } = parsed;
112
+ const modulePlanPath = `docs/09-PLANNING/MODULES/${moduleKey}/module_plan.md`;
113
+ if (!exists(modulePlanPath)) continue;
114
+
115
+ const taskPlan = read(taskPlanPath);
116
+ const taskProgress = readTaskProgress(taskPlanPath, context);
117
+ const taskProgressStatus = readTaskProgressStatus(taskPlanPath, context);
118
+ const taskIsActive = isActiveModuleTaskStatus(taskProgressStatus);
119
+ const stepId = extractStepId(taskPlan, taskDir);
120
+ if (!stepId) {
121
+ if (taskIsActive) {
122
+ fail(`${taskPlanPath} does not expose a Step ID and task directory does not start with <PREFIX-NN>`);
123
+ }
124
+ continue;
125
+ }
126
+
127
+ const modulePlan = read(modulePlanPath);
128
+ const moduleRelativeTaskPlan = `TASKS/${taskDir}/task_plan.md`;
129
+ if (!modulePlan.includes(stepId) || !modulePlan.includes(moduleRelativeTaskPlan)) {
130
+ fail(`${modulePlanPath} does not index ${stepId} task plan ${moduleRelativeTaskPlan}`);
131
+ }
132
+
133
+ if (!taskIsActive) continue;
134
+
135
+ const registryRow = registryByModule.get(moduleKey);
136
+ const reviewPath = taskPlanPath.replace(/task_plan\.md$/, "review.md");
137
+ const registrySynced = Boolean(registryRow && registryRow[4] === stepId);
138
+ const ledgerSynced = ledgerContent.includes(taskPlanPath) && (!exists(reviewPath) || ledgerContent.includes(reviewPath));
139
+ if (registrySynced && ledgerSynced) continue;
140
+
141
+ if (requireGlobalModuleSync) {
142
+ if (!registryRow) {
143
+ fail(`docs/09-PLANNING/Module-Registry.md does not include active module ${moduleKey} for ${taskPlanPath}`);
144
+ } else if (registryRow[4] !== stepId) {
145
+ fail(`docs/09-PLANNING/Module-Registry.md row ${moduleKey} current step is ${registryRow[4]}, but active task is ${stepId}`);
146
+ }
147
+ if (!ledgerContent.includes(taskPlanPath)) {
148
+ fail(`docs/Harness-Ledger.md does not index active module task plan ${taskPlanPath}`);
149
+ }
150
+ if (exists(reviewPath) && !ledgerContent.includes(reviewPath)) {
151
+ fail(`docs/Harness-Ledger.md does not index active module review ${reviewPath}`);
152
+ }
153
+ continue;
154
+ }
155
+
156
+ if (hasPendingCoordinatorHandoff(taskPlan, taskProgress)) {
157
+ warn(`${taskPlanPath} has pending coordinator handoff; run coordinator pass before final integration or set HARNESS_REQUIRE_GLOBAL_MODULE_SYNC=1 for strict gate`);
158
+ continue;
159
+ }
160
+ fail(`${taskPlanPath} is active but is neither globally synced nor marked with Coordinator Handoff: pending-coordinator-pass`);
161
+ }
162
+ }
163
+
164
+ export function checkModuleParallelStructure(context) {
165
+ const { exists, read, fail, requireFile, markdownTable } = context;
166
+ if (!exists("docs/09-PLANNING/Module-Registry.md")) return;
167
+
168
+ requireFile("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md");
169
+ const hasPromptPack = exists("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md");
170
+ for (const templateFile of [
171
+ "docs/09-PLANNING/MODULES/_task-template/task_plan.md",
172
+ "docs/09-PLANNING/MODULES/_task-template/progress.md",
173
+ "docs/09-PLANNING/MODULES/_task-template/findings.md",
174
+ "docs/09-PLANNING/MODULES/_task-template/review.md",
175
+ ]) {
176
+ requireFile(templateFile);
177
+ }
178
+
179
+ const registryContent = read("docs/09-PLANNING/Module-Registry.md");
180
+ for (const term of ["PREFIX", "Current Step", "Status", "Write Scope"]) {
181
+ if (!registryContent.includes(term)) {
182
+ fail(`docs/09-PLANNING/Module-Registry.md missing registry column or section: ${term}`);
183
+ }
184
+ }
185
+
186
+ const registryRows = markdownTable(registryContent)
187
+ .filter((cells) => cells.length >= 6)
188
+ .filter((cells) => /^(_shared|[a-z][a-z0-9-]*)$/.test(cells[0] || "") && /^[A-Z]{2,5}$/.test(cells[2] || ""));
189
+
190
+ if (registryRows.length === 0) {
191
+ fail("docs/09-PLANNING/Module-Registry.md has no active module rows");
192
+ }
193
+
194
+ const promptPack = hasPromptPack ? read("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md") : "";
195
+ if (hasPromptPack && !/Subagent Worker Invariant|worker[\s\S]{0,120}worktree[\s\S]{0,120}commit SHA/i.test(promptPack)) {
196
+ fail("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md missing subagent worker worktree/commit handoff rule");
197
+ }
198
+ for (const cells of registryRows) {
199
+ const [key, , prefix, branch, currentStep, status] = cells;
200
+ requireFile(`docs/09-PLANNING/MODULES/${key}/module_plan.md`);
201
+ if (!/^(planned|in-progress|paused|completed)$/.test(status)) {
202
+ fail(`docs/09-PLANNING/Module-Registry.md row ${key} has invalid status: ${status}`);
203
+ }
204
+ if (currentStep !== `${prefix}-00` && !currentStep.startsWith(`${prefix}-`)) {
205
+ fail(`docs/09-PLANNING/Module-Registry.md row ${key} current step does not match prefix ${prefix}: ${currentStep}`);
206
+ }
207
+ const branchName = stripMarkdownCode(branch);
208
+ if (!branchName.startsWith("codex/")) {
209
+ fail(`docs/09-PLANNING/Module-Registry.md row ${key} branch must use codex/ prefix: ${branch}`);
210
+ }
211
+
212
+ const block = modulePromptBlock(promptPack, key);
213
+ if (!block) {
214
+ if (!exists(`docs/09-PLANNING/MODULES/${key}/session_prompt.md`)) {
215
+ fail(`missing module session prompt for ${key}`);
216
+ }
217
+ continue;
218
+ }
219
+ for (const term of [
220
+ "Current Step",
221
+ branchName,
222
+ "Preflight:",
223
+ "Before code edits:",
224
+ "Write scope:",
225
+ "Forbidden without coordination:",
226
+ "Shared Coordination:",
227
+ "Verification:",
228
+ "Closeout:",
229
+ "Stop conditions:",
230
+ ]) {
231
+ if (!block.includes(term)) {
232
+ fail(`module session prompt for ${key} missing required term: ${term}`);
233
+ }
234
+ }
235
+ }
236
+ checkModuleTaskSsotIndex(registryRows, context);
237
+ }
@@ -0,0 +1,418 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+ import {
5
+ repoRoot,
6
+ legacyChecker,
7
+ visualMapFile,
8
+ legacyVisualRoadmapFile,
9
+ allowedReviewDispositions,
10
+ allowedPhaseStates,
11
+ allowedEvidenceStatus,
12
+ normalizeTarget,
13
+ toPosix,
14
+ readFileSafe,
15
+ walkFiles,
16
+ } from "./core-shared.mjs";
17
+ import {
18
+ tableAfterHeading,
19
+ getColumn,
20
+ getColumnAny,
21
+ splitList,
22
+ firstColumn,
23
+ contentHasAny,
24
+ } from "./markdown-utils.mjs";
25
+ import { capabilityDefinitions, validateCapabilities } from "./capability-registry.mjs";
26
+ import { readPresetPackage } from "./preset-registry.mjs";
27
+ import { validateTaskPresetAuditSnapshot } from "./preset-audit-contracts.mjs";
28
+ import { validatePresetResourcesForTask } from "./preset-resource-contracts.mjs";
29
+ import {
30
+ collectTasks,
31
+ listTaskPlanPaths,
32
+ readVisualMapContractFile,
33
+ parsePhases,
34
+ taskCutoverCounters,
35
+ } from "./task-scanner.mjs";
36
+ import {
37
+ normalizeReviewBoolean,
38
+ reviewFindingColumns,
39
+ } from "./task-review-model.mjs";
40
+ import { validateTaskCompletionConsistency } from "./task-completion-consistency.mjs";
41
+ import { validatePlanContracts } from "./check-task-contracts.mjs";
42
+ import { validateGovernanceTableBoundaries } from "./governance-table-boundary.mjs";
43
+ import { validateSubagentAuthorization } from "./subagent-authorization-audit.mjs";
44
+ import { summarizeGitState } from "./git-status-summary.mjs";
45
+ export { renderDashboard } from "./status-dashboard-renderer.mjs";
46
+
47
+ export function runLegacyCheck(target) {
48
+ const checkTarget = target.docsOnly ? target.projectRoot : target.input;
49
+ const result = spawnSync(process.execPath, [legacyChecker, checkTarget], {
50
+ cwd: repoRoot,
51
+ encoding: "utf8",
52
+ });
53
+ return {
54
+ status: result.status === 0 ? "pass" : "fail",
55
+ code: result.status ?? 1,
56
+ stdout: result.stdout || "",
57
+ stderr: result.stderr || "",
58
+ };
59
+ }
60
+
61
+ export function validateReviewSchema(target, { strict = true } = {}) {
62
+ const failures = [];
63
+ const warnings = [];
64
+ const report = (message) => {
65
+ if (strict) failures.push(message);
66
+ else warnings.push(`adoption-needed: ${message}`);
67
+ };
68
+ const reviewPaths = walkFiles(target.docsRoot)
69
+ .filter((file) => file.endsWith("review.md"))
70
+ .filter((file) => !file.includes(`${path.sep}_task-template${path.sep}`))
71
+ .filter((file) => !file.includes(`${path.sep}_optional-structures${path.sep}`))
72
+ .filter((file) => !file.includes(`${path.sep}_archive${path.sep}`));
73
+
74
+ for (const reviewPath of reviewPaths) {
75
+ const relative = toPosix(path.relative(target.projectRoot, reviewPath));
76
+ const content = readFileSafe(reviewPath);
77
+ const requiredSections = [
78
+ ["Reviewer Identity", "Reviewer 身份", "审查者身份"],
79
+ ["Confidence Challenge", "信心挑战"],
80
+ ["Evidence Checked", "已检查 Evidence", "已检查证据"],
81
+ ["Final Confidence Basis", "最终信心依据"],
82
+ ];
83
+ for (const [label, ...aliases] of requiredSections) {
84
+ if (!contentHasAny(content, [label, ...aliases])) {
85
+ if (strict) failures.push(`${relative} missing ${label}`);
86
+ else warnings.push(`${relative} missing ${label}`);
87
+ }
88
+ }
89
+ const evidenceTable = tableAfterHeading(content, /^(Evidence ID|证据 ID)$/i);
90
+ if (strict && evidenceTable.rows.length === 0) {
91
+ failures.push(`${relative} Evidence Checked table needs at least one evidence row`);
92
+ }
93
+ const usesVerifier = /verifier-backed|(^|\|)[^|\n]*\|\s*verifier\s*\|/im.test(content);
94
+ if (usesVerifier) {
95
+ if (!/template_id:\s*`?harness-verifier\/v1`?/i.test(content)) {
96
+ report(`${relative} verifier-backed review missing template_id: harness-verifier/v1`);
97
+ }
98
+ if (!/verdict:\s*`?(pass|fail|inconclusive)`?/i.test(content)) {
99
+ report(`${relative} verifier-backed review missing verdict`);
100
+ }
101
+ }
102
+ const { header, rows } = tableAfterHeading(content, /^ID$/i);
103
+ if (rows.length === 0) continue;
104
+ const severityIndex = getColumnAny(header, reviewFindingColumns.severity);
105
+ const openIndex = getColumnAny(header, reviewFindingColumns.open);
106
+ const dispositionIndex = getColumnAny(header, reviewFindingColumns.disposition);
107
+ const blocksIndex = getColumnAny(header, reviewFindingColumns.blocksRelease);
108
+ const followUpIndex = getColumnAny(header, ["Follow-up", "跟进"]);
109
+ const evidenceCheckedIndex = getColumnAny(header, ["Evidence Checked", "已检查证据"]);
110
+ if ([severityIndex, openIndex, dispositionIndex, blocksIndex].some((index) => index < 0)) {
111
+ report(`${relative} findings table missing Severity/Open/Disposition/Blocks Release columns`);
112
+ continue;
113
+ }
114
+ for (const row of rows) {
115
+ const id = row[0] || "";
116
+ const severity = row[severityIndex] || "";
117
+ if (!/^P[0-3]$/.test(severity) && !/^(R|SR)-\d+/i.test(id)) continue;
118
+ const open = normalizeReviewBoolean(row[openIndex] || "");
119
+ const disposition = (row[dispositionIndex] || "").toLowerCase();
120
+ const blocks = normalizeReviewBoolean(row[blocksIndex] || "");
121
+ const followUp = row[followUpIndex] || "";
122
+ if (!/^P[0-3]$/.test(severity)) report(`${relative} ${id} invalid severity: ${severity}`);
123
+ if (!["yes", "no"].includes(open)) report(`${relative} ${id} invalid Open value: ${open}`);
124
+ if (!allowedReviewDispositions.has(disposition)) report(`${relative} ${id} invalid Disposition: ${disposition}`);
125
+ if (!["yes", "no"].includes(blocks)) report(`${relative} ${id} invalid Blocks Release value: ${blocks}`);
126
+ if ((open === "yes" || blocks === "yes") && /^P[01]$/.test(severity)) {
127
+ report(`${relative} ${id} has release-blocking open ${severity}`);
128
+ }
129
+ if (["accepted-risk", "deferred"].includes(disposition) && (!followUp || /^none|无$/i.test(followUp))) {
130
+ report(`${relative} ${id} ${disposition} requires follow-up routing`);
131
+ }
132
+ if (strict && evidenceCheckedIndex >= 0) {
133
+ const refs = splitList(row[evidenceCheckedIndex] || "");
134
+ const evidenceIds = new Set(evidenceTable.rows.map((evidenceRow) => evidenceRow[0]));
135
+ for (const ref of refs) {
136
+ if (ref !== "none" && /^E-\d+/i.test(ref) && !evidenceIds.has(ref)) {
137
+ failures.push(`${relative} ${id} references missing evidence id: ${ref}`);
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ }
144
+ return { failures, warnings };
145
+ }
146
+
147
+ export function validateVisualMaps(target) {
148
+ const failures = [];
149
+ const warnings = [];
150
+ for (const taskPlanPath of listTaskPlanPaths(target)) {
151
+ const taskDir = path.dirname(taskPlanPath);
152
+ const visualMapPath = path.join(taskDir, visualMapFile);
153
+ const legacyPath = path.join(taskDir, legacyVisualRoadmapFile);
154
+ const relative = toPosix(path.relative(target.projectRoot, visualMapPath));
155
+ const taskPlan = readFileSafe(taskPlanPath);
156
+ const visualMap = readVisualMapContractFile(taskDir, taskPlan);
157
+ const { header, rows } = tableAfterHeading(visualMap.content, /^Phase ID$/i);
158
+ if (rows.length > 0) {
159
+ for (const column of ["Phase ID", "Depends On", "State", "Completion", "Output", "Required Evidence", "Evidence Status", "Blocking Risk", "Owner / Handoff"]) {
160
+ if (getColumn(header, column) < 0) failures.push(`${relative} Visual Map missing column: ${column}`);
161
+ }
162
+ }
163
+ const phases = parsePhases(visualMap.content);
164
+ for (const phase of phases) {
165
+ if (!allowedPhaseStates.has(phase.state)) failures.push(`${relative} phase ${phase.id} invalid state: ${phase.state}`);
166
+ if (!allowedEvidenceStatus.has(phase.evidenceStatus)) {
167
+ failures.push(`${relative} phase ${phase.id} invalid evidence status: ${phase.evidenceStatus}`);
168
+ }
169
+ if (!Number.isInteger(phase.completion) || phase.completion < 0 || phase.completion > 100) {
170
+ failures.push(`${relative} phase ${phase.id} completion must be integer 0..100`);
171
+ }
172
+ if (phase.state === "done" && phase.completion !== 100) failures.push(`${relative} phase ${phase.id} done must be 100`);
173
+ if (phase.state === "planned" && phase.completion !== 0) failures.push(`${relative} phase ${phase.id} planned must be 0`);
174
+ }
175
+ if (visualMap.source === "canonical" && !/Visual Map Contract:\s*v1\.0/i.test(visualMap.content)) {
176
+ failures.push(`${relative} missing Visual Map Contract: v1.0`);
177
+ }
178
+ if (visualMap.source === "canonical" && phases.length === 0) warnings.push(`${relative} has no Visual Map phase table`);
179
+ if (visualMap.source === "legacy" && fs.existsSync(legacyPath)) {
180
+ warnings.push(`${relative} missing; legacy visual_roadmap.md is rewrite input only`);
181
+ } else if (visualMap.source === "legacy" && phases.length > 0) {
182
+ warnings.push(`${relative} missing; using legacy task_plan.md visual map fallback`);
183
+ }
184
+ }
185
+ return { failures, warnings };
186
+ }
187
+
188
+ export function validateTaskPresetContracts(target) {
189
+ const failures = [];
190
+ const allowedMigrationLevels = new Set([
191
+ "migration-baseline",
192
+ "migration-current-cutover",
193
+ "migration-full-cutover",
194
+ "migration-deferred",
195
+ ]);
196
+ for (const task of collectTasks(target)) {
197
+ if (!task.taskPreset || task.taskPreset === "none") continue;
198
+ let presetPackage = null;
199
+ try {
200
+ presetPackage = readPresetPackage(task.taskPreset, { targetInput: target.projectRoot });
201
+ } catch (error) {
202
+ failures.push(`${task.path} unsupported Task Preset: ${task.taskPreset} (${error.message})`);
203
+ continue;
204
+ }
205
+ if (presetPackage?.task?.kind && task.taskKind !== presetPackage.task.kind) {
206
+ failures.push(`${task.path} ${task.taskPreset} preset Task Kind mismatch: expected ${presetPackage.task.kind}, got ${task.taskKind || "(missing)"}`);
207
+ }
208
+ if (String(task.presetVersion || "") !== String(presetPackage.version)) {
209
+ failures.push(`${task.path} ${task.taskPreset} preset missing Preset Version ${presetPackage.version}`);
210
+ }
211
+ if (task.taskPreset !== "lesson-sedimentation" && (presetPackage.evidence?.bundleDir || presetPackage.audit?.evidenceFiles?.length || Object.keys(presetPackage.evidence?.files || {}).length)) {
212
+ if (!task.evidenceBundle) failures.push(`${task.path} ${task.taskPreset} preset missing Evidence Bundle`);
213
+ else if (!fs.existsSync(path.join(target.projectRoot, String(task.evidenceBundle).replace(/^TARGET:/, "").replace(/^\/+/, "")))) {
214
+ failures.push(`${task.path} ${task.taskPreset} preset Evidence Bundle missing: ${task.evidenceBundle}`);
215
+ }
216
+ }
217
+ if (task.taskPreset !== "lesson-sedimentation") {
218
+ failures.push(...validateTaskPresetAuditSnapshot(target, task, presetPackage));
219
+ }
220
+ failures.push(...validatePresetResourcesForTask(target, task, presetPackage));
221
+ if (task.taskPreset === "lesson-sedimentation") {
222
+ if (!["standard", "complex"].includes(task.budget)) failures.push(`${task.path} lesson-sedimentation preset requires Selected budget: standard or complex`);
223
+ if (!task.taskPlanPath) failures.push(`${task.path} lesson-sedimentation preset missing task plan`);
224
+ continue;
225
+ }
226
+ if (task.taskPreset !== "legacy-migration") {
227
+ continue;
228
+ }
229
+ if (task.budget !== "complex") failures.push(`${task.path} legacy-migration preset requires Selected budget: complex`);
230
+ if (!allowedMigrationLevels.has(task.migrationTargetLevel)) {
231
+ failures.push(`${task.path} legacy-migration preset invalid Migration Target Level: ${task.migrationTargetLevel || "(missing)"}`);
232
+ }
233
+ const achievedLevel = task.migrationAchievedLevel || "";
234
+ if (achievedLevel !== "pending" && !allowedMigrationLevels.has(achievedLevel)) {
235
+ failures.push(`${task.path} legacy-migration preset invalid Migration Achieved Level: ${achievedLevel || "(missing)"}`);
236
+ }
237
+ if (task.evidenceBundle && !task.migrationSnapshot?.evidencePresent) {
238
+ failures.push(`${task.path} legacy-migration preset Evidence Bundle missing: ${task.evidenceBundle}`);
239
+ } else if (!task.migrationSnapshot?.sessionPresent) {
240
+ failures.push(`${task.path} legacy-migration preset Evidence Bundle missing session.json`);
241
+ }
242
+ if (achievedLevel === "migration-full-cutover") {
243
+ const snapshot = task.migrationSnapshot || {};
244
+ const blockers = [];
245
+ if (!snapshot.sessionPresent) blockers.push("missing session evidence");
246
+ if (snapshot.sessionResult !== "complete") blockers.push(`session result is ${snapshot.sessionResult || "(missing)"}`);
247
+ if (snapshot.strictDeferred) blockers.push("strictDeferred is present");
248
+ if (snapshot.strictStatus !== "pass") blockers.push(`strict status is ${snapshot.strictStatus || "(missing)"}`);
249
+ for (const [field, value] of [
250
+ ["warnings", snapshot.warnings],
251
+ ["taskActions", snapshot.taskActions],
252
+ ["reviewSchemaGaps", snapshot.reviewSchemaGaps],
253
+ ["legacyReferenceGaps", snapshot.legacyReferenceGaps],
254
+ ["legacyResiduals", snapshot.legacyResiduals],
255
+ ]) {
256
+ if (Number(value || 0) !== 0) blockers.push(`${field}=${value}`);
257
+ }
258
+ if (snapshot.fullCutoverEligible !== true) blockers.push("fullCutoverEligible is not true");
259
+ if (blockers.length) {
260
+ failures.push(`${task.path} migration-full-cutover is not proven: ${blockers.join("; ")}`);
261
+ }
262
+ }
263
+ }
264
+ return { failures, warnings: [] };
265
+ }
266
+
267
+ export function validateContextDocs(target, { strict = true } = {}) {
268
+ const failures = [];
269
+ const warnings = [];
270
+ const report = (message) => {
271
+ if (strict) failures.push(message);
272
+ else warnings.push(`adoption-needed: ${message}`);
273
+ };
274
+ const contextRoots = ["03-ARCHITECTURE", "04-DEVELOPMENT", "06-INTEGRATIONS"];
275
+ const files = contextRoots.flatMap((root) => walkFiles(path.join(target.docsRoot, root))).filter((file) => file.endsWith(".md"));
276
+ for (const file of files) {
277
+ if (file.includes(`${path.sep}_archive${path.sep}`)) continue;
278
+ const relative = toPosix(path.relative(target.projectRoot, file));
279
+ const content = readFileSafe(file);
280
+ if (!/Context Doc Type:\s*\S+/i.test(content) && !/上下文文档类型[::]\s*\S+/.test(content)) report(`${relative} missing Context Doc Type`);
281
+ if (path.basename(file) === "README.md") continue;
282
+ if (!contentHasAny(content, [/Source Evidence/i, "来源证据"])) report(`${relative} missing Source Evidence field`);
283
+ if (!/Last Verified:\s*\S+|Last Verified\s*\|/i.test(content) && !/最近验证[::]\s*\S+|最近验证\s*\|/.test(content)) report(`${relative} missing Last Verified field`);
284
+ if (!/Confidence:\s*(high|medium|low|unknown)|Confidence\s*\|/i.test(content) && !/信心[::]\s*(high|medium|low|unknown|高|中|低|未知)|信心\s*\|/.test(content)) report(`${relative} missing Confidence field`);
285
+ if (/03-ARCHITECTURE\/service-catalog\.md$/.test(relative)) {
286
+ for (const [column, ...aliases] of [
287
+ ["Service / Component", "服务 / 组件"],
288
+ ["Interfaces", "接口"],
289
+ ["Source Evidence", "来源证据"],
290
+ ["Last Verified", "最近验证"],
291
+ ["Confidence", "信心"],
292
+ ]) {
293
+ if (!contentHasAny(content, [column, ...aliases])) report(`${relative} service catalog missing column: ${column}`);
294
+ }
295
+ }
296
+ if (/04-DEVELOPMENT\/external-context\/[^/]+\.md$/.test(relative)) {
297
+ for (const [heading, ...aliases] of [
298
+ ["Development Use", "开发用途"],
299
+ ["Do Not Assume", "不要假设"],
300
+ ["Mocks / Stubs", "Mock / Stub", "模拟 / 桩"],
301
+ ]) {
302
+ if (!contentHasAny(content, [heading, ...aliases])) report(`${relative} external context missing section: ${heading}`);
303
+ }
304
+ }
305
+ if (/06-INTEGRATIONS\/(?:[^/_][^/]*|third-party\/[^/_][^/]*)\.md$/.test(relative)) {
306
+ for (const [heading, ...aliases] of [
307
+ ["Contract Type", "合同类型"],
308
+ ["Auth", "认证"],
309
+ ["Payload", "载荷"],
310
+ ["Errors", "错误"],
311
+ ["Contract Tests", "合同测试"],
312
+ ]) {
313
+ if (!contentHasAny(content, [heading, ...aliases])) report(`${relative} integration contract missing section: ${heading}`);
314
+ }
315
+ }
316
+ }
317
+ return { failures, warnings };
318
+ }
319
+
320
+ export function buildStatus(targetInput, options = {}) {
321
+ const target = normalizeTarget(targetInput);
322
+ const gitState = summarizeGitState(target);
323
+ const capabilityState = validateCapabilities(target);
324
+ const declaredCapabilities = new Set(capabilityState.registry.capabilities.map((capability) => capability.name));
325
+ const safeAdoptionMode = declaredCapabilities.has("safe-adoption");
326
+ const shouldRunLegacy = !options.skipLegacyCheck && (capabilityState.registry.mode === "legacy-compat" || safeAdoptionMode);
327
+ const legacy = shouldRunLegacy ? runLegacyCheck(target) : { status: "skipped", code: 0, stdout: "", stderr: "" };
328
+ const contractStrict = Boolean(options.strict) || (capabilityState.registry.mode !== "legacy-compat" && !safeAdoptionMode);
329
+ const reviews = validateReviewSchema(target, { strict: contractStrict });
330
+ const visualMaps = validateVisualMaps(target);
331
+ const planContracts = validatePlanContracts(target, { strict: contractStrict });
332
+ const presetContracts = validateTaskPresetContracts(target);
333
+ const contextDocs = validateContextDocs(target, { strict: contractStrict });
334
+ const governanceBoundaries = validateGovernanceTableBoundaries(target);
335
+ const subagentAuthorization = validateSubagentAuthorization(target, { strict: contractStrict });
336
+ const failures = [...capabilityState.failures, ...reviews.failures, ...visualMaps.failures, ...planContracts.failures, ...presetContracts.failures, ...contextDocs.failures, ...governanceBoundaries.failures, ...subagentAuthorization.failures];
337
+ const warnings = [...capabilityState.warnings, ...reviews.warnings, ...visualMaps.warnings, ...planContracts.warnings, ...presetContracts.warnings, ...contextDocs.warnings, ...governanceBoundaries.warnings, ...subagentAuthorization.warnings, ...gitState.warnings];
338
+ if (legacy.status === "fail") {
339
+ if (options.strictLegacy) failures.push("legacy check failed");
340
+ else warnings.push(`adoption-needed: legacy check failed: ${(legacy.stderr || legacy.stdout).trim()}`);
341
+ }
342
+
343
+ const tasks = collectTasks(target);
344
+ const taskCompletionConsistency = validateTaskCompletionConsistency(tasks);
345
+ failures.push(...taskCompletionConsistency.failures);
346
+ warnings.push(...taskCompletionConsistency.warnings);
347
+ const briefReady = tasks.filter((task) => task.briefSource === "standalone").length;
348
+ const briefMissing = tasks.length - briefReady;
349
+ for (const task of tasks) {
350
+ if (task.stateSource === "invalid") {
351
+ const message = `${task.path}/progress.md invalid task state: ${task.stateRaw}`;
352
+ if (contractStrict || options.strictLegacy) failures.push(message);
353
+ else warnings.push(`adoption-needed: ${message}`);
354
+ }
355
+ }
356
+ const capabilityNames = new Map(capabilityState.registry.capabilities.map((capability) => [capability.name, capability]));
357
+ for (const detected of capabilityState.detected) {
358
+ if (!capabilityNames.has(detected)) capabilityNames.set(detected, { name: detected, state: "configured" });
359
+ }
360
+ const cutoverCounters = taskCutoverCounters(tasks);
361
+ const fullCutoverEligible =
362
+ failures.length === 0 &&
363
+ warnings.length === 0 &&
364
+ cutoverCounters.legacyVisualOnlyCount === 0 &&
365
+ cutoverCounters.unknownClassificationCount === 0 &&
366
+ cutoverCounters.weakBriefCount === 0 &&
367
+ cutoverCounters.missingCanonicalVisualMapCount === 0;
368
+
369
+ return {
370
+ project: {
371
+ name: path.basename(target.projectRoot),
372
+ root: `TARGET:${target.docsOnly ? toPosix(path.relative(target.projectRoot, target.docsRoot)) : "."}`,
373
+ docsOnly: target.docsOnly,
374
+ },
375
+ schemaVersion: 2,
376
+ generatedAt: new Date().toISOString(),
377
+ mode: capabilityState.registry.mode,
378
+ checkState: {
379
+ status: failures.length > 0 ? "fail" : warnings.length > 0 ? "warn" : "pass",
380
+ failures: failures.length,
381
+ warnings: warnings.length,
382
+ details: { failures, warnings },
383
+ legacy,
384
+ },
385
+ git: gitState.summary,
386
+ summary: {
387
+ tasks: tasks.length,
388
+ briefCoverage: {
389
+ ready: briefReady,
390
+ missing: briefMissing,
391
+ total: tasks.length,
392
+ },
393
+ visualMapCoverage: {
394
+ canonical: tasks.filter((task) => task.visualMapSource === "canonical").length,
395
+ legacyOnly: cutoverCounters.legacyVisualOnlyCount,
396
+ missing: tasks.filter((task) => task.visualMapStatus === "missing").length,
397
+ total: tasks.length,
398
+ },
399
+ fullCutoverEligible,
400
+ legacyVisualOnlyCount: cutoverCounters.legacyVisualOnlyCount,
401
+ unknownClassificationCount: cutoverCounters.unknownClassificationCount,
402
+ weakBriefCount: cutoverCounters.weakBriefCount,
403
+ visualMapRequiredCount: cutoverCounters.visualMapRequiredCount,
404
+ missingCanonicalVisualMapCount: cutoverCounters.missingCanonicalVisualMapCount,
405
+ },
406
+ capabilities: [...capabilityNames.values()].map((capability) => ({
407
+ name: capability.name,
408
+ state: capability.state || "configured",
409
+ dependencyStatus: capabilityDefinitions[capability.name]?.dependencies.every((dependency) => capabilityNames.has(dependency))
410
+ ? "valid"
411
+ : "invalid",
412
+ warnings: capabilityState.warnings.filter((warning) => warning.includes(capability.name)),
413
+ })),
414
+ tasks,
415
+ handoffs: tasks.flatMap((task) => task.handoffs || []),
416
+ recentActivity: tasks.slice(0, 8).map((task) => ({ at: new Date().toISOString(), type: "task", summary: task.title })),
417
+ };
418
+ }