coding-agent-harness 1.0.5 → 1.0.7

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 (260) hide show
  1. package/CONTRIBUTING.md +2 -2
  2. package/README.md +63 -3
  3. package/README.zh-CN.md +52 -3
  4. package/SKILL.md +43 -43
  5. package/dist/build-dist.mjs +189 -0
  6. package/dist/check-dist-observation.mjs +428 -0
  7. package/dist/check-harness.mjs +489 -0
  8. package/dist/check-import-graph.mjs +511 -0
  9. package/dist/check-runtime-emit.mjs +304 -0
  10. package/dist/check-type-boundaries.mjs +139 -0
  11. package/dist/commands/dashboard-command.mjs +80 -0
  12. package/dist/commands/migration-command.mjs +152 -0
  13. package/dist/commands/preset-command.mjs +91 -0
  14. package/dist/commands/task-command.mjs +324 -0
  15. package/dist/harness.mjs +304 -0
  16. package/dist/lib/capability-registry.mjs +643 -0
  17. package/dist/lib/check-module-parallel.mjs +227 -0
  18. package/dist/lib/check-profiles.mjs +414 -0
  19. package/dist/lib/check-task-contracts.mjs +54 -0
  20. package/dist/lib/core-shared.mjs +254 -0
  21. package/dist/lib/dashboard-data.mjs +608 -0
  22. package/dist/lib/dashboard-workbench.mjs +334 -0
  23. package/dist/lib/dashboard-writer.mjs +200 -0
  24. package/dist/lib/git-status-summary.mjs +45 -0
  25. package/dist/lib/governance-index-generator.mjs +236 -0
  26. package/dist/lib/governance-sync.mjs +617 -0
  27. package/dist/lib/governance-table-boundary.mjs +161 -0
  28. package/{scripts → dist}/lib/harness-core.mjs +2 -0
  29. package/dist/lib/harness-paths.mjs +338 -0
  30. package/dist/lib/lesson-maintenance.mjs +139 -0
  31. package/dist/lib/markdown-utils.mjs +193 -0
  32. package/dist/lib/migration-planner.mjs +439 -0
  33. package/dist/lib/migration-support.mjs +317 -0
  34. package/dist/lib/phase-kind.mjs +46 -0
  35. package/dist/lib/preset-audit-contracts.mjs +40 -0
  36. package/dist/lib/preset-engine.mjs +516 -0
  37. package/dist/lib/preset-registry.mjs +831 -0
  38. package/dist/lib/preset-resource-contracts.mjs +83 -0
  39. package/dist/lib/review-confirm-git-gate.mjs +244 -0
  40. package/dist/lib/status-builder.mjs +87 -0
  41. package/{scripts → dist}/lib/status-dashboard-renderer.mjs +44 -46
  42. package/dist/lib/structure-migration.mjs +404 -0
  43. package/dist/lib/subagent-authorization-audit.mjs +198 -0
  44. package/dist/lib/task-audit-metadata.mjs +376 -0
  45. package/dist/lib/task-audit-migration.mjs +355 -0
  46. package/dist/lib/task-completion-consistency.mjs +29 -0
  47. package/dist/lib/task-index.mjs +133 -0
  48. package/dist/lib/task-lesson-candidates.mjs +239 -0
  49. package/dist/lib/task-lesson-sedimentation.mjs +300 -0
  50. package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
  51. package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
  52. package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
  53. package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
  54. package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
  55. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
  56. package/dist/lib/task-lifecycle/template-files.mjs +52 -0
  57. package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
  58. package/dist/lib/task-lifecycle.mjs +611 -0
  59. package/dist/lib/task-metadata.mjs +116 -0
  60. package/dist/lib/task-review-model.mjs +474 -0
  61. package/dist/lib/task-scanner.mjs +439 -0
  62. package/dist/lib/task-tombstone-commands.mjs +125 -0
  63. package/dist/postinstall.mjs +14 -0
  64. package/dist/run-built-tests.mjs +84 -0
  65. package/docs-release/README.md +1 -0
  66. package/docs-release/architecture/overview.md +12 -12
  67. package/docs-release/architecture/overview.zh-CN.md +12 -12
  68. package/docs-release/architecture/system-explainer/01-system-overview.md +15 -14
  69. package/docs-release/architecture/system-explainer/02-module-dependency.md +8 -8
  70. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +3 -3
  71. package/docs-release/architecture/system-explainer/04-check-and-governance.md +9 -7
  72. package/docs-release/architecture/system-explainer/05-data-flow.md +5 -5
  73. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +1 -4
  74. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +15 -14
  75. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +8 -8
  76. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +3 -3
  77. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +10 -8
  78. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
  79. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +1 -4
  80. package/docs-release/guides/agent-installation.en-US.md +14 -8
  81. package/docs-release/guides/agent-installation.md +14 -8
  82. package/docs-release/guides/contributing.md +3 -3
  83. package/docs-release/guides/contributing.zh-CN.md +3 -3
  84. package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
  85. package/docs-release/guides/document-audience-and-surfaces.md +10 -10
  86. package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
  87. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
  88. package/docs-release/guides/migration-playbook.en-US.md +63 -1
  89. package/docs-release/guides/migration-playbook.md +59 -1
  90. package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
  91. package/docs-release/guides/parent-control-repository-pattern.md +25 -25
  92. package/docs-release/guides/preset-development.md +2 -2
  93. package/docs-release/guides/repository-operating-models.en-US.md +21 -21
  94. package/docs-release/guides/repository-operating-models.md +21 -21
  95. package/docs-release/guides/task-state-machine.en-US.md +5 -5
  96. package/docs-release/guides/task-state-machine.md +5 -5
  97. package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
  98. package/examples/minimal-project/AGENTS.md +2 -2
  99. package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
  100. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
  101. package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
  102. package/package.json +20 -12
  103. package/presets/legacy-migration/preset.yaml +5 -5
  104. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  105. package/presets/lesson-sedimentation/preset.yaml +3 -3
  106. package/presets/module/preset.yaml +2 -2
  107. package/presets/module/templates/execution_strategy.append.md +1 -1
  108. package/presets/module/templates/task_plan.append.md +3 -3
  109. package/presets/standard-task/preset.yaml +2 -2
  110. package/references/adversarial-review-standard.md +2 -2
  111. package/references/agents-md-pattern.md +14 -14
  112. package/references/cadence-ledger.md +1 -1
  113. package/references/ci-cd-standard.md +1 -1
  114. package/references/delivery-operating-model-standard.md +4 -4
  115. package/references/docs-directory-standard.md +65 -159
  116. package/references/external-source-intake-standard.md +10 -10
  117. package/references/harness-ledger.md +5 -5
  118. package/references/legacy-12-phase-bootstrap.md +2 -2
  119. package/references/lessons-governance.md +15 -15
  120. package/references/long-running-task-standard.md +6 -6
  121. package/references/module-parallel-standard.md +34 -34
  122. package/references/planning-loop.md +6 -6
  123. package/references/project-onboarding-audit.md +4 -4
  124. package/references/regression-system.md +2 -2
  125. package/references/repo-governance-standard.md +4 -4
  126. package/references/review-routing-standard.md +1 -1
  127. package/references/ssot-governance.md +19 -19
  128. package/references/taskr-gap-analysis.md +5 -5
  129. package/references/walkthrough-closeout.md +14 -14
  130. package/references/worktree-parallel.md +3 -3
  131. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
  132. package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
  133. package/templates/AGENTS.md.template +26 -26
  134. package/templates/architecture/README.md +4 -4
  135. package/templates/architecture/service-catalog.md +2 -2
  136. package/templates/architecture/services/service-template.md +1 -1
  137. package/templates/dashboard/assets/app-src/20-overview.js +11 -5
  138. package/templates/dashboard/assets/app-src/40-modules.js +1 -1
  139. package/templates/dashboard/assets/app.js +12 -6
  140. package/templates/dashboard/assets/i18n.js +4 -2
  141. package/templates/development/README.md +10 -10
  142. package/templates/development/cross-repo-debugging.md +3 -3
  143. package/templates/development/external-context/service-template.md +2 -2
  144. package/templates/development/external-source-packs/README.md +4 -4
  145. package/templates/integrations/README.md +4 -4
  146. package/templates/integrations/api-contract.md +2 -2
  147. package/templates/integrations/event-contract.md +2 -2
  148. package/templates/integrations/third-party/vendor-template.md +2 -2
  149. package/templates/integrations/webhook-contract.md +2 -2
  150. package/templates/ledger/Harness-Ledger.md +1 -1
  151. package/templates/planning/INDEX.md +1 -0
  152. package/templates/planning/module_session_prompt.md +1 -1
  153. package/templates/planning/task_plan.md +1 -1
  154. package/templates/planning/walkthrough.md +47 -0
  155. package/templates/reference/docs-library-standard.md +8 -8
  156. package/templates/reference/external-source-intake-standard.md +15 -15
  157. package/templates/reference/repo-governance-standard.md +1 -1
  158. package/templates/ssot/Module-Registry.md +1 -1
  159. package/templates/walkthrough/walkthrough-template.md +2 -2
  160. package/templates-zh-CN/AGENTS.md.template +26 -26
  161. package/templates-zh-CN/CLAUDE.md.template +1 -1
  162. package/templates-zh-CN/architecture/README.md +4 -4
  163. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  164. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  165. package/templates-zh-CN/development/README.md +10 -10
  166. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  167. package/templates-zh-CN/development/external-context/service-template.md +2 -2
  168. package/templates-zh-CN/development/external-source-packs/README.md +4 -4
  169. package/templates-zh-CN/integrations/README.md +4 -4
  170. package/templates-zh-CN/integrations/api-contract.md +2 -2
  171. package/templates-zh-CN/integrations/event-contract.md +2 -2
  172. package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
  173. package/templates-zh-CN/integrations/webhook-contract.md +2 -2
  174. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  175. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  176. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  177. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  178. package/templates-zh-CN/planning/module_session_prompt.md +11 -11
  179. package/templates-zh-CN/planning/walkthrough.md +47 -0
  180. package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
  181. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  182. package/templates-zh-CN/reference/docs-library-standard.md +28 -28
  183. package/templates-zh-CN/reference/execution-workflow-standard.md +1 -1
  184. package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
  185. package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
  186. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  187. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  188. package/templates-zh-CN/reference/review-routing-standard.md +1 -1
  189. package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
  190. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  191. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  192. package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
  193. package/templates-zh-CN/ssot/Module-Registry.md +3 -3
  194. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  195. package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
  196. package/tsconfig.dist.json +16 -0
  197. package/tsconfig.json +25 -0
  198. package/tsconfig.runtime.json +24 -0
  199. package/examples/minimal-project/.harness-capabilities.json +0 -8
  200. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
  201. package/scripts/check-harness.mjs +0 -508
  202. package/scripts/commands/dashboard-command.mjs +0 -67
  203. package/scripts/commands/migration-command.mjs +0 -126
  204. package/scripts/commands/preset-command.mjs +0 -73
  205. package/scripts/commands/task-command.mjs +0 -328
  206. package/scripts/harness.mjs +0 -291
  207. package/scripts/lib/capability-registry.mjs +0 -587
  208. package/scripts/lib/check-module-parallel.mjs +0 -230
  209. package/scripts/lib/check-profiles.mjs +0 -372
  210. package/scripts/lib/check-task-contracts.mjs +0 -55
  211. package/scripts/lib/core-shared.mjs +0 -249
  212. package/scripts/lib/dashboard-data.mjs +0 -520
  213. package/scripts/lib/dashboard-workbench.mjs +0 -336
  214. package/scripts/lib/dashboard-writer.mjs +0 -202
  215. package/scripts/lib/git-status-summary.mjs +0 -46
  216. package/scripts/lib/governance-index-generator.mjs +0 -174
  217. package/scripts/lib/governance-sync.mjs +0 -611
  218. package/scripts/lib/governance-table-boundary.mjs +0 -175
  219. package/scripts/lib/lesson-maintenance.mjs +0 -152
  220. package/scripts/lib/markdown-utils.mjs +0 -191
  221. package/scripts/lib/migration-planner.mjs +0 -476
  222. package/scripts/lib/migration-support.mjs +0 -312
  223. package/scripts/lib/phase-kind.mjs +0 -50
  224. package/scripts/lib/preset-audit-contracts.mjs +0 -37
  225. package/scripts/lib/preset-engine.mjs +0 -494
  226. package/scripts/lib/preset-registry.mjs +0 -776
  227. package/scripts/lib/preset-resource-contracts.mjs +0 -83
  228. package/scripts/lib/review-confirm-git-gate.mjs +0 -248
  229. package/scripts/lib/status-builder.mjs +0 -88
  230. package/scripts/lib/subagent-authorization-audit.mjs +0 -196
  231. package/scripts/lib/task-audit-metadata.mjs +0 -385
  232. package/scripts/lib/task-audit-migration.mjs +0 -350
  233. package/scripts/lib/task-completion-consistency.mjs +0 -26
  234. package/scripts/lib/task-index.mjs +0 -93
  235. package/scripts/lib/task-lesson-candidates.mjs +0 -242
  236. package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
  237. package/scripts/lib/task-lifecycle/create-task-helpers.mjs +0 -67
  238. package/scripts/lib/task-lifecycle/phase-sync.mjs +0 -88
  239. package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -112
  240. package/scripts/lib/task-lifecycle/review-gates.mjs +0 -73
  241. package/scripts/lib/task-lifecycle/review-submission.mjs +0 -63
  242. package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +0 -49
  243. package/scripts/lib/task-lifecycle/template-files.mjs +0 -53
  244. package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
  245. package/scripts/lib/task-lifecycle.mjs +0 -616
  246. package/scripts/lib/task-metadata.mjs +0 -118
  247. package/scripts/lib/task-review-model.mjs +0 -455
  248. package/scripts/lib/task-scanner.mjs +0 -503
  249. package/scripts/lib/task-tombstone-commands.mjs +0 -140
  250. package/scripts/postinstall.mjs +0 -14
  251. package/templates/walkthrough/Closeout-SSoT.md +0 -43
  252. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
  253. /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
  254. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/INDEX.md +0 -0
  255. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
  256. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
  257. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
  258. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
  259. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
  260. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/visual_map.md +0 -0
@@ -0,0 +1,83 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ // @ts-ignore core-shared remains a JS runtime dependency until its migration PR.
4
+ import { readFileSafe, toPosix } from "./core-shared.mjs";
5
+ export function validatePresetResourcesForTask(target, task, presetPackage) {
6
+ const failures = [];
7
+ const taskRelativePath = String(task.path || "").replace(/^TARGET:/, "").replace(/^\/+/, "");
8
+ if (!taskRelativePath)
9
+ return failures;
10
+ const taskDir = path.join(target.projectRoot, taskRelativePath);
11
+ const referenceIndex = readFileSafe(path.join(taskDir, "references/INDEX.md"));
12
+ const referenceRows = parseMarkdownRows(referenceIndex, ["ID", "Path"]);
13
+ const artifactIndex = readFileSafe(path.join(taskDir, "artifacts/INDEX.md"));
14
+ const artifactRows = parseMarkdownRows(artifactIndex, ["ID", "Path"]);
15
+ const taskPlan = task.taskPlanPath ? readFileSafe(path.join(target.projectRoot, String(task.taskPlanPath).replace(/^TARGET:/, "").replace(/^\/+/, ""))) : "";
16
+ const requiredReadRows = parseMarkdownRows(taskPlan, ["Reference", "Path"]);
17
+ const expectedReferencePaths = new Map();
18
+ for (const resource of Object.values(presetPackage.resources?.references || {})) {
19
+ const relativePath = toPosix(path.join(taskRelativePath, resource.path));
20
+ expectedReferencePaths.set(resource.index.id, `TARGET:${relativePath}`);
21
+ if (!fs.existsSync(path.join(target.projectRoot, relativePath))) {
22
+ failures.push(`${task.path} ${task.taskPreset} preset resource missing: TARGET:${relativePath}`);
23
+ }
24
+ if (!hasIndexedResource(referenceRows, resource.index.id, `TARGET:${relativePath}`)) {
25
+ failures.push(`${task.path} ${task.taskPreset} preset reference index missing ${resource.index.id}`);
26
+ }
27
+ }
28
+ for (const resource of Object.values(presetPackage.resources?.artifacts || {})) {
29
+ const relativePath = toPosix(path.join(taskRelativePath, resource.path));
30
+ if (!fs.existsSync(path.join(target.projectRoot, relativePath))) {
31
+ failures.push(`${task.path} ${task.taskPreset} preset resource missing: TARGET:${relativePath}`);
32
+ }
33
+ if (!hasIndexedResource(artifactRows, resource.index.id, `TARGET:${relativePath}`)) {
34
+ failures.push(`${task.path} ${task.taskPreset} preset artifact index missing ${resource.index.id}`);
35
+ }
36
+ }
37
+ for (const requiredRead of presetPackage.context?.requiredReads || []) {
38
+ const expectedPath = expectedReferencePaths.get(requiredRead);
39
+ if (!referenceRows.some((row) => row.ID === requiredRead && (!expectedPath || row.Path === expectedPath))) {
40
+ failures.push(`${task.path} ${task.taskPreset} preset required read missing from references index: ${requiredRead}`);
41
+ }
42
+ if (!requiredReadRows.some((row) => row.Reference === requiredRead && (!expectedPath || row.Path === expectedPath))) {
43
+ failures.push(`${task.path} ${task.taskPreset} preset required read missing from task plan: ${requiredRead}`);
44
+ }
45
+ }
46
+ return failures;
47
+ }
48
+ function hasIndexedResource(rows, id, expectedPath) {
49
+ return rows.some((row) => row.ID === id && row.Path === expectedPath);
50
+ }
51
+ function parseMarkdownRows(markdown, requiredColumns) {
52
+ const rows = [];
53
+ const lines = String(markdown || "").split(/\r?\n/);
54
+ for (let index = 0; index < lines.length; index += 1) {
55
+ const line = lines[index].trim();
56
+ const next = lines[index + 1]?.trim() || "";
57
+ if (!isTableRow(line) || !isTableSeparator(next))
58
+ continue;
59
+ const header = splitTableRow(line);
60
+ if (!requiredColumns.every((column) => header.includes(column)))
61
+ continue;
62
+ index += 2;
63
+ while (index < lines.length && isTableRow(lines[index].trim())) {
64
+ const cells = splitTableRow(lines[index].trim());
65
+ if (cells.length === header.length)
66
+ rows.push(Object.fromEntries(header.map((column, cellIndex) => [column, cells[cellIndex] || ""])));
67
+ index += 1;
68
+ }
69
+ index -= 1;
70
+ }
71
+ return rows;
72
+ }
73
+ function isTableRow(line) {
74
+ return line.startsWith("|") && line.endsWith("|");
75
+ }
76
+ function isTableSeparator(line) {
77
+ if (!isTableRow(line))
78
+ return false;
79
+ return splitTableRow(line).every((cell) => /^:?-{3,}:?$/.test(cell));
80
+ }
81
+ function splitTableRow(line) {
82
+ return line.slice(1, -1).split("|").map((cell) => cell.trim());
83
+ }
@@ -0,0 +1,244 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import { spawnSync } from "node:child_process";
4
+ // @ts-ignore core-shared remains a JS runtime dependency until its migration PR.
5
+ import { toPosix } from "./core-shared.mjs";
6
+ export class ReviewConfirmGitGateError extends Error {
7
+ code;
8
+ status;
9
+ details;
10
+ recovery;
11
+ constructor(message, { code = "review-confirm-git-gate-failed", status = 409, details = {}, recovery = [] } = {}) {
12
+ super(message);
13
+ this.name = "ReviewConfirmGitGateError";
14
+ this.code = code;
15
+ this.status = status;
16
+ this.details = details;
17
+ this.recovery = recovery;
18
+ }
19
+ }
20
+ export function prepareReviewConfirmGitGate(projectRoot, allowedFilesAbs) {
21
+ const root = path.resolve(projectRoot);
22
+ const resolvedGitRoot = requireGitRoot(root);
23
+ if (real(resolvedGitRoot) !== real(root)) {
24
+ throw new ReviewConfirmGitGateError("Target must be the Git repository root for review confirmation auto-commit.", {
25
+ code: "git-root-mismatch",
26
+ details: { targetRoot: root, gitRoot: resolvedGitRoot },
27
+ recovery: [
28
+ "Run review-confirm from the repository root for the target task.",
29
+ "For private harness tasks, run against the private harness repository root, not the public parent.",
30
+ ],
31
+ });
32
+ }
33
+ const gitRoot = root;
34
+ const allowedPaths = allowedFilesAbs.map((filePath) => toPosix(path.relative(gitRoot, path.resolve(filePath))));
35
+ assertAllowedPaths(allowedPaths);
36
+ assertCleanWorkingTree(gitRoot);
37
+ assertCommitIdentity(gitRoot);
38
+ return { gitRoot, allowedPaths };
39
+ }
40
+ export function commitReviewConfirmationGate(gate, { taskId, reviewPath, writeFinalAudit, message = "" }) {
41
+ const subjectSuffix = taskId.replace(/[^A-Za-z0-9._/-]+/g, "-");
42
+ assertOnlyAllowedChanged(gate.gitRoot, gate.allowedPaths);
43
+ git(gate.gitRoot, ["add", "--", ...gate.allowedPaths]);
44
+ assertOnlyAllowedStaged(gate.gitRoot, gate.allowedPaths);
45
+ const confirmCommit = commit(gate.gitRoot, `chore: confirm review ${subjectSuffix}`, {
46
+ recovery: [
47
+ "Review confirmation files were written but not committed.",
48
+ `Inspect and either fix hooks then run: git add -- ${gate.allowedPaths.join(" ")} && git commit`,
49
+ "Or manually revert the written review confirmation files if the confirmation should not proceed.",
50
+ ],
51
+ });
52
+ writeFinalAudit(confirmCommit);
53
+ const reviewRelativePath = toPosix(path.relative(gate.gitRoot, path.resolve(reviewPath)));
54
+ git(gate.gitRoot, ["add", "--", reviewRelativePath]);
55
+ assertOnlyAllowedStaged(gate.gitRoot, gate.allowedPaths);
56
+ const auditCommit = commit(gate.gitRoot, `chore: record review confirmation audit ${subjectSuffix}`, {
57
+ recovery: [
58
+ "The confirmation commit was created, but final audit metadata could not be committed.",
59
+ `Confirmation commit SHA: ${confirmCommit}`,
60
+ `Fix hooks, then stage ${reviewRelativePath} and commit the audit metadata.`,
61
+ ],
62
+ });
63
+ assertCleanWorkingTree(gate.gitRoot);
64
+ return {
65
+ commitSha: confirmCommit,
66
+ auditCommitSha: auditCommit,
67
+ auditStatus: "committed",
68
+ allowedPaths: gate.allowedPaths,
69
+ message,
70
+ };
71
+ }
72
+ export function validateReviewConfirmationGitAudit({ projectRoot, taskId, reviewPath, progressPath, commitSha }) {
73
+ const issues = [];
74
+ const addIssue = (code) => issues.push(code);
75
+ const root = projectRoot ? path.resolve(projectRoot) : "";
76
+ const reviewRelativePath = root && reviewPath ? toPosix(path.relative(root, path.resolve(reviewPath))) : "";
77
+ const progressRelativePath = root && progressPath ? toPosix(path.relative(root, path.resolve(progressPath))) : "";
78
+ const expectedPaths = [reviewRelativePath, progressRelativePath].filter(Boolean).sort();
79
+ if (!root)
80
+ addIssue("git-audit-context-missing");
81
+ if (!commitSha)
82
+ addIssue("git-audit-commit-missing");
83
+ if (issues.length > 0)
84
+ return { valid: false, issues };
85
+ const gitRootResult = git(root, ["rev-parse", "--show-toplevel"], { allowFailure: true });
86
+ if (gitRootResult.status !== 0) {
87
+ return { valid: false, issues: ["git-audit-repository-missing"] };
88
+ }
89
+ const gitRoot = path.resolve(gitRootResult.stdout.trim());
90
+ if (real(gitRoot) !== real(root))
91
+ addIssue("git-audit-root-mismatch");
92
+ const commitResult = git(root, ["rev-parse", "--verify", `${commitSha}^{commit}`], { allowFailure: true });
93
+ if (commitResult.status !== 0) {
94
+ return { valid: false, issues: [...issues, "git-audit-commit-missing"] };
95
+ }
96
+ const fullCommitSha = commitResult.stdout.trim();
97
+ const reachable = git(root, ["merge-base", "--is-ancestor", fullCommitSha, "HEAD"], { allowFailure: true });
98
+ if (reachable.status !== 0)
99
+ addIssue("git-audit-commit-not-reachable");
100
+ const subject = git(root, ["show", "-s", "--format=%s", fullCommitSha], { allowFailure: true }).stdout.trim();
101
+ const expectedSubject = `chore: confirm review ${String(taskId || "").replace(/[^A-Za-z0-9._/-]+/g, "-")}`;
102
+ if (subject !== expectedSubject)
103
+ addIssue("git-audit-subject-mismatch");
104
+ const changedPaths = git(root, ["diff-tree", "--no-commit-id", "--name-only", "-r", fullCommitSha], { allowFailure: true }).stdout
105
+ .split(/\r?\n/)
106
+ .filter(Boolean)
107
+ .map(toPosix)
108
+ .sort();
109
+ if (expectedPaths.length === 0)
110
+ addIssue("git-audit-allowlist-missing");
111
+ if (changedPaths.join("\n") !== expectedPaths.join("\n"))
112
+ addIssue("git-audit-allowlist-mismatch");
113
+ return {
114
+ valid: issues.length === 0,
115
+ issues,
116
+ commitSha: fullCommitSha,
117
+ changedPaths,
118
+ expectedPaths,
119
+ subject,
120
+ };
121
+ }
122
+ function requireGitRoot(root) {
123
+ const result = git(root, ["rev-parse", "--show-toplevel"], { allowFailure: true });
124
+ if (result.status !== 0) {
125
+ throw new ReviewConfirmGitGateError("Review confirmation auto-commit requires a Git repository.", {
126
+ code: "git-repository-missing",
127
+ details: { root, stderr: result.stderr.trim() },
128
+ recovery: ["Initialize Git for the target project or run review-confirm from the correct repository root."],
129
+ });
130
+ }
131
+ return path.resolve(result.stdout.trim());
132
+ }
133
+ function assertAllowedPaths(paths) {
134
+ const disallowed = paths.filter((relativePath) => {
135
+ if (!relativePath || relativePath.startsWith("../") || path.isAbsolute(relativePath))
136
+ return true;
137
+ if (relativePath === "AGENTS.md" || relativePath === "CLAUDE.md")
138
+ return true;
139
+ if (relativePath === "docs" || relativePath.startsWith("docs/"))
140
+ return false;
141
+ if (relativePath === ".harness-private" || relativePath.startsWith(".harness-private/"))
142
+ return true;
143
+ return false;
144
+ });
145
+ if (disallowed.length > 0) {
146
+ throw new ReviewConfirmGitGateError("Review confirmation write allowlist contains forbidden paths.", {
147
+ code: "git-allowlist-forbidden-path",
148
+ details: { disallowed },
149
+ recovery: ["Limit review-confirm writes to the current task INDEX.md file."],
150
+ });
151
+ }
152
+ }
153
+ function assertCleanWorkingTree(gitRoot) {
154
+ const entries = statusEntries(gitRoot);
155
+ if (entries.length > 0) {
156
+ throw new ReviewConfirmGitGateError("Git working tree is not clean; refusing review confirmation auto-commit.", {
157
+ code: "git-dirty-working-tree",
158
+ details: { entries },
159
+ recovery: [
160
+ "Commit, move, or intentionally discard unrelated changes before review-confirm.",
161
+ "Do not stash/reset automatically; resolve ownership of the dirty files first.",
162
+ ],
163
+ });
164
+ }
165
+ }
166
+ function assertCommitIdentity(gitRoot) {
167
+ const name = git(gitRoot, ["config", "--get", "user.name"], { allowFailure: true }).stdout.trim();
168
+ const email = git(gitRoot, ["config", "--get", "user.email"], { allowFailure: true }).stdout.trim();
169
+ if (!name || !email) {
170
+ throw new ReviewConfirmGitGateError("Git commit identity is missing; refusing review confirmation auto-commit.", {
171
+ code: "git-identity-missing",
172
+ details: { hasName: Boolean(name), hasEmail: Boolean(email) },
173
+ recovery: [
174
+ "Set a local Git identity for this repository:",
175
+ "git config user.name \"Your Name\"",
176
+ "git config user.email \"you@example.com\"",
177
+ ],
178
+ });
179
+ }
180
+ }
181
+ function assertOnlyAllowedChanged(gitRoot, allowedPaths) {
182
+ const entries = statusEntries(gitRoot);
183
+ const outside = entries.filter((entry) => !allowedPaths.includes(entry.path));
184
+ if (outside.length > 0) {
185
+ throw new ReviewConfirmGitGateError("Review confirmation produced changes outside the write allowlist.", {
186
+ code: "git-allowlist-violation",
187
+ details: { entries, allowedPaths },
188
+ recovery: [
189
+ "Inspect the extra files and do not commit them through review-confirm.",
190
+ "Revert only the unintended review-confirm side effects, then retry.",
191
+ ],
192
+ });
193
+ }
194
+ }
195
+ function assertOnlyAllowedStaged(gitRoot, allowedPaths) {
196
+ const entries = statusEntries(gitRoot);
197
+ const stagedOutside = entries.filter((entry) => entry.index !== " " && !allowedPaths.includes(entry.path));
198
+ if (stagedOutside.length > 0) {
199
+ throw new ReviewConfirmGitGateError("Git index contains staged files outside the review confirmation allowlist.", {
200
+ code: "git-index-allowlist-violation",
201
+ details: { stagedOutside, allowedPaths },
202
+ recovery: ["Unstage unrelated files before retrying review-confirm."],
203
+ });
204
+ }
205
+ }
206
+ function commit(gitRoot, message, { recovery }) {
207
+ const result = git(gitRoot, ["commit", "-m", message], { allowFailure: true });
208
+ if (result.status !== 0) {
209
+ throw new ReviewConfirmGitGateError("Git commit failed during review confirmation auto-commit.", {
210
+ code: "git-commit-failed",
211
+ details: { stdout: result.stdout.trim(), stderr: result.stderr.trim() },
212
+ recovery,
213
+ });
214
+ }
215
+ return git(gitRoot, ["rev-parse", "HEAD"]).stdout.trim();
216
+ }
217
+ function statusEntries(gitRoot) {
218
+ const output = git(gitRoot, ["status", "--porcelain=v1", "--untracked-files=all"]).stdout;
219
+ return output.split(/\r?\n/).filter(Boolean).map((line) => ({
220
+ index: line.slice(0, 1),
221
+ worktree: line.slice(1, 2),
222
+ path: parseStatusPath(line.slice(3)),
223
+ raw: line,
224
+ }));
225
+ }
226
+ function parseStatusPath(value) {
227
+ const unquoted = value.replace(/^"|"$/g, "");
228
+ const renamed = unquoted.includes(" -> ") ? unquoted.split(" -> ").pop() : unquoted;
229
+ return renamed || "";
230
+ }
231
+ function git(cwd, args, { allowFailure = false } = {}) {
232
+ const result = spawnSync("git", args, { cwd, encoding: "utf8" });
233
+ if (!allowFailure && result.status !== 0) {
234
+ throw new ReviewConfirmGitGateError(`git ${args.join(" ")} failed`, {
235
+ code: "git-command-failed",
236
+ details: { stdout: result.stdout.trim(), stderr: result.stderr.trim() },
237
+ recovery: ["Inspect the Git error and retry review-confirm after resolving it."],
238
+ });
239
+ }
240
+ return result;
241
+ }
242
+ function real(filePath) {
243
+ return fs.realpathSync(filePath);
244
+ }
@@ -0,0 +1,87 @@
1
+ // @ts-nocheck
2
+ import path from "node:path";
3
+ import { normalizeTarget, toPosix } from "./core-shared.mjs";
4
+ import { capabilityDefinitions, readCapabilityRegistry } from "./capability-registry.mjs";
5
+ import { summarizeGitState } from "./git-status-summary.mjs";
6
+ import { collectTasks, taskCutoverCounters } from "./task-scanner.mjs";
7
+ export function buildStatusData(targetInput, options = {}) {
8
+ const target = targetInput?.projectRoot ? targetInput : normalizeTarget(targetInput);
9
+ const validationMode = options.validationMode || "data-only";
10
+ const gitState = options.gitState || summarizeGitState(target);
11
+ const registry = options.capabilityState?.registry || readCapabilityRegistry(target);
12
+ const detected = options.capabilityState?.detected || [];
13
+ const capabilityWarnings = options.capabilityState?.warnings || [];
14
+ const failures = [...(options.failures || [])];
15
+ const warnings = [...(options.warnings || [])];
16
+ const legacy = options.legacy || { status: "skipped", code: 0, stdout: "", stderr: "" };
17
+ const tasks = options.tasks || collectTasks(target, {
18
+ requireGeneratedScaffoldProvenance: options.requireGeneratedScaffoldProvenance === true,
19
+ taskPlanPaths: options.taskPlanPaths,
20
+ closeoutContent: options.closeoutContent,
21
+ });
22
+ const briefReady = tasks.filter((task) => task.briefSource === "standalone").length;
23
+ const briefMissing = tasks.length - briefReady;
24
+ const capabilityNames = new Map(registry.capabilities.map((capability) => [capability.name, capability]));
25
+ for (const capability of detected) {
26
+ if (!capabilityNames.has(capability))
27
+ capabilityNames.set(capability, { name: capability, state: "configured" });
28
+ }
29
+ const cutoverCounters = taskCutoverCounters(tasks);
30
+ const fullCutoverEligible = validationMode === "validated" &&
31
+ failures.length === 0 &&
32
+ warnings.length === 0 &&
33
+ cutoverCounters.legacyVisualOnlyCount === 0 &&
34
+ cutoverCounters.unknownClassificationCount === 0 &&
35
+ cutoverCounters.weakBriefCount === 0 &&
36
+ cutoverCounters.missingCanonicalVisualMapCount === 0;
37
+ return {
38
+ project: {
39
+ name: path.basename(target.projectRoot),
40
+ root: `TARGET:${target.docsOnly ? toPosix(path.relative(target.projectRoot, target.docsRoot)) : "."}`,
41
+ docsOnly: target.docsOnly,
42
+ },
43
+ schemaVersion: 2,
44
+ generatedAt: options.generatedAt || new Date().toISOString(),
45
+ mode: registry.mode,
46
+ checkState: {
47
+ status: failures.length > 0 ? "fail" : warnings.length > 0 ? "warn" : "pass",
48
+ validationMode,
49
+ failures: failures.length,
50
+ warnings: warnings.length,
51
+ details: { failures, warnings },
52
+ legacy,
53
+ },
54
+ git: gitState.summary,
55
+ summary: {
56
+ tasks: tasks.length,
57
+ briefCoverage: {
58
+ ready: briefReady,
59
+ missing: briefMissing,
60
+ total: tasks.length,
61
+ },
62
+ visualMapCoverage: {
63
+ canonical: tasks.filter((task) => task.visualMapSource === "canonical").length,
64
+ legacyOnly: cutoverCounters.legacyVisualOnlyCount,
65
+ missing: tasks.filter((task) => task.visualMapStatus === "missing").length,
66
+ total: tasks.length,
67
+ },
68
+ fullCutoverEligible,
69
+ legacyVisualOnlyCount: cutoverCounters.legacyVisualOnlyCount,
70
+ unknownClassificationCount: cutoverCounters.unknownClassificationCount,
71
+ weakBriefCount: cutoverCounters.weakBriefCount,
72
+ visualMapRequiredCount: cutoverCounters.visualMapRequiredCount,
73
+ missingCanonicalVisualMapCount: cutoverCounters.missingCanonicalVisualMapCount,
74
+ },
75
+ capabilities: [...capabilityNames.values()].map((capability) => ({
76
+ name: capability.name,
77
+ state: capability.state || "configured",
78
+ dependencyStatus: capabilityDefinitions[capability.name]?.dependencies.every((dependency) => capabilityNames.has(dependency))
79
+ ? "valid"
80
+ : "invalid",
81
+ warnings: capabilityWarnings.filter((warning) => warning.includes(capability.name)),
82
+ })),
83
+ tasks,
84
+ handoffs: tasks.flatMap((task) => task.handoffs || []),
85
+ recentActivity: tasks.slice(0, 8).map((task) => ({ at: new Date().toISOString(), type: "task", summary: task.title })),
86
+ };
87
+ }
@@ -1,27 +1,24 @@
1
1
  import { implementationPhases } from "./phase-kind.mjs";
2
-
3
2
  export function renderDashboard(status) {
4
- const taskCards = status.tasks
5
- .map((task) => {
6
- const phases = task.phases
7
- .map(
8
- (phase) => `<div class="phase ${escapeHtml(phase.state)} ${escapeHtml(phase.kind || "execution")}">
3
+ const taskCards = status.tasks
4
+ .map((task) => {
5
+ const phases = task.phases
6
+ .map((phase) => `<div class="phase ${escapeHtml(phase.state)} ${escapeHtml(phase.kind || "execution")}">
9
7
  <div class="phase-top"><strong>${escapeHtml(phase.id)}</strong><span>${escapeHtml(phase.kind || "execution")} · ${phase.completion}%</span></div>
10
8
  <div class="phase-output">${escapeHtml(phase.output)}</div>
11
9
  <div class="meter"><i style="width:${phase.completion}%"></i></div>
12
10
  <div class="muted">${escapeHtml(phase.state)} · actor ${escapeHtml(phase.actor || "agent")} · evidence ${escapeHtml(phase.evidenceStatus)}</div>
13
11
  ${phase.exitCommand ? `<div class="muted">exit ${escapeHtml(phase.exitCommand)}</div>` : ""}
14
- </div>`,
15
- )
16
- .join("");
17
- const risks = task.risks
18
- .map((risk) => `<span class="risk ${risk.open || risk.blocksRelease ? "open" : ""}">${escapeHtml(risk.severity)} ${escapeHtml(risk.summary)}</span>`)
19
- .join("");
20
- const evidence = task.evidence
21
- .map((item) => `<span class="evidence">${escapeHtml(item.type)} · ${escapeHtml(item.summary)}</span>`)
22
- .join("");
23
- const evidenceMeter = evidenceCompletion(task.phases);
24
- return `<section class="task">
12
+ </div>`)
13
+ .join("");
14
+ const risks = task.risks
15
+ .map((risk) => `<span class="risk ${risk.open || risk.blocksRelease ? "open" : ""}">${escapeHtml(risk.severity)} ${escapeHtml(risk.summary)}</span>`)
16
+ .join("");
17
+ const evidence = task.evidence
18
+ .map((item) => `<span class="evidence">${escapeHtml(item.type)} · ${escapeHtml(item.summary)}</span>`)
19
+ .join("");
20
+ const evidenceMeter = evidenceCompletion(task.phases);
21
+ return `<section class="task">
25
22
  <div class="task-head">
26
23
  <div><h2>${escapeHtml(task.title)}</h2><p>${escapeHtml(task.path)}</p></div>
27
24
  <div class="score">${task.completion}%</div>
@@ -32,19 +29,19 @@ export function renderDashboard(status) {
32
29
  <div class="risks">${risks || '<span class="ok">No open visual risk</span>'}</div>
33
30
  </section>`;
34
31
  })
35
- .join("");
36
- const chips = status.capabilities
37
- .map((capability) => `<span class="chip ${escapeHtml(capability.state)}">${escapeHtml(capability.name)} · ${escapeHtml(capability.state)}</span>`)
38
- .join("");
39
- const failures = status.checkState.details.failures.map((failure) => `<li>${escapeHtml(failure)}</li>`).join("");
40
- const warnings = status.checkState.details.warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join("");
41
- const handoffs = status.handoffs
42
- .map((handoff) => `<span class="handoff">${escapeHtml(handoff.state)} · ${escapeHtml(handoff.summary)}</span>`)
43
- .join("");
44
- const activity = status.recentActivity
45
- .map((item) => `<li><strong>${escapeHtml(item.type)}</strong> ${escapeHtml(item.summary)}</li>`)
46
- .join("");
47
- return `<!doctype html>
32
+ .join("");
33
+ const chips = status.capabilities
34
+ .map((capability) => `<span class="chip ${escapeHtml(capability.state)}">${escapeHtml(capability.name)} · ${escapeHtml(capability.state)}</span>`)
35
+ .join("");
36
+ const failures = status.checkState.details.failures.map((failure) => `<li>${escapeHtml(failure)}</li>`).join("");
37
+ const warnings = status.checkState.details.warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join("");
38
+ const handoffs = status.handoffs
39
+ .map((handoff) => `<span class="handoff">${escapeHtml(handoff.state)} · ${escapeHtml(handoff.summary)}</span>`)
40
+ .join("");
41
+ const activity = status.recentActivity
42
+ .map((item) => `<li><strong>${escapeHtml(item.type)}</strong> ${escapeHtml(item.summary)}</li>`)
43
+ .join("");
44
+ return `<!doctype html>
48
45
  <html lang="zh-CN">
49
46
  <head>
50
47
  <meta charset="utf-8">
@@ -84,22 +81,23 @@ export function renderDashboard(status) {
84
81
  <section class="panel"><h2>Failures</h2><ul>${failures || "<li>None</li>"}</ul><h2>Warnings</h2><ul>${warnings || "<li>None</li>"}</ul></section>
85
82
  </main></body></html>`;
86
83
  }
87
-
88
84
  function escapeHtml(value) {
89
- return String(value ?? "")
90
- .replaceAll("&", "&amp;")
91
- .replaceAll("<", "&lt;")
92
- .replaceAll(">", "&gt;")
93
- .replaceAll('"', "&quot;");
85
+ return String(value ?? "")
86
+ .replaceAll("&", "&amp;")
87
+ .replaceAll("<", "&lt;")
88
+ .replaceAll(">", "&gt;")
89
+ .replaceAll('"', "&quot;");
94
90
  }
95
-
96
- function evidenceCompletion(phases) {
97
- const scored = implementationPhases(phases);
98
- if (scored.length === 0) return 0;
99
- const score = scored.reduce((sum, phase) => {
100
- if (["present", "waived"].includes(phase.evidenceStatus)) return sum + 100;
101
- if (phase.evidenceStatus === "partial") return sum + 50;
102
- return sum;
103
- }, 0);
104
- return Math.round(score / scored.length);
91
+ function evidenceCompletion(phases = []) {
92
+ const scored = implementationPhases(phases);
93
+ if (scored.length === 0)
94
+ return 0;
95
+ const score = scored.reduce((sum, phase) => {
96
+ if (["present", "waived"].includes(String(phase.evidenceStatus)))
97
+ return sum + 100;
98
+ if (phase.evidenceStatus === "partial")
99
+ return sum + 50;
100
+ return sum;
101
+ }, 0);
102
+ return Math.round(score / scored.length);
105
103
  }