coding-agent-harness 1.0.2 → 1.0.5

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 (219) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONTRIBUTING.md +98 -0
  3. package/LICENSE +661 -21
  4. package/LICENSE-EXCEPTION.md +37 -0
  5. package/README.md +244 -87
  6. package/README.zh-CN.md +77 -35
  7. package/SKILL.md +32 -24
  8. package/docs-release/README.md +9 -5
  9. package/docs-release/architecture/overview.md +17 -5
  10. package/docs-release/architecture/overview.zh-CN.md +9 -5
  11. package/docs-release/architecture/system-explainer/01-system-overview.md +217 -0
  12. package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
  13. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
  14. package/docs-release/architecture/system-explainer/04-check-and-governance.md +239 -0
  15. package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
  16. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +303 -0
  17. package/docs-release/architecture/system-explainer/README.md +67 -0
  18. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +226 -0
  19. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
  20. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
  21. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +250 -0
  22. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
  23. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +323 -0
  24. package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
  25. package/docs-release/assets/dashboard-overview.png +0 -0
  26. package/docs-release/guides/agent-installation.en-US.md +39 -15
  27. package/docs-release/guides/agent-installation.md +43 -16
  28. package/docs-release/guides/contributing.md +100 -0
  29. package/docs-release/guides/contributing.zh-CN.md +99 -0
  30. package/docs-release/guides/document-audience-and-surfaces.en-US.md +3 -2
  31. package/docs-release/guides/document-audience-and-surfaces.md +3 -2
  32. package/docs-release/guides/full-legacy-migration-subagent-strategy.md +2 -2
  33. package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +2 -2
  34. package/docs-release/guides/legacy-migration-agent-prompt.md +0 -11
  35. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +0 -11
  36. package/docs-release/guides/migration-playbook.en-US.md +14 -15
  37. package/docs-release/guides/migration-playbook.md +14 -15
  38. package/docs-release/guides/parent-control-repository-pattern.en-US.md +7 -5
  39. package/docs-release/guides/parent-control-repository-pattern.md +7 -5
  40. package/docs-release/guides/preset-development.md +238 -0
  41. package/docs-release/guides/repository-operating-models.en-US.md +5 -4
  42. package/docs-release/guides/repository-operating-models.md +5 -4
  43. package/docs-release/guides/task-state-machine.en-US.md +224 -0
  44. package/docs-release/guides/task-state-machine.md +231 -0
  45. package/docs-release/intl/en-US.md +1 -1
  46. package/docs-release/intl/zh-CN.md +1 -1
  47. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/INDEX.md +60 -0
  48. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
  49. package/package.json +10 -4
  50. package/presets/legacy-migration/checks/preset-check.mjs +3 -0
  51. package/presets/legacy-migration/preset.yaml +134 -0
  52. package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
  53. package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
  54. package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
  55. package/presets/legacy-migration/templates/findings.seed.md +17 -0
  56. package/presets/legacy-migration/templates/review.seed.md +12 -0
  57. package/presets/legacy-migration/templates/task_plan.append.md +9 -0
  58. package/presets/legacy-migration/templates/visual_map.append.md +12 -0
  59. package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
  60. package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
  61. package/presets/lesson-sedimentation/preset.yaml +23 -0
  62. package/presets/lesson-sedimentation/templates/prompt.md +23 -0
  63. package/presets/module/preset.yaml +25 -0
  64. package/presets/module/templates/execution_strategy.append.md +8 -0
  65. package/presets/module/templates/task_plan.append.md +17 -0
  66. package/presets/standard-task/preset.yaml +31 -0
  67. package/presets/standard-task/templates/task_plan.append.md +7 -0
  68. package/references/adversarial-review-standard.md +2 -2
  69. package/references/agents-md-pattern.md +2 -2
  70. package/references/delivery-operating-model-standard.md +3 -3
  71. package/references/docs-directory-standard.md +6 -7
  72. package/references/harness-ledger.md +53 -96
  73. package/references/lessons-governance.md +88 -93
  74. package/references/module-parallel-standard.md +14 -14
  75. package/references/planning-loop.md +12 -6
  76. package/references/pull-request-standard.md +118 -0
  77. package/references/repo-governance-standard.md +11 -2
  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/walkthrough-closeout.md +7 -7
  82. package/scripts/check-harness.mjs +40 -301
  83. package/scripts/commands/dashboard-command.mjs +67 -0
  84. package/scripts/commands/migration-command.mjs +126 -0
  85. package/scripts/commands/preset-command.mjs +73 -0
  86. package/scripts/commands/task-command.mjs +328 -0
  87. package/scripts/harness.mjs +59 -260
  88. package/scripts/lib/capability-registry.mjs +82 -28
  89. package/scripts/lib/check-module-parallel.mjs +230 -0
  90. package/scripts/lib/check-profiles.mjs +90 -228
  91. package/scripts/lib/check-task-contracts.mjs +55 -0
  92. package/scripts/lib/core-shared.mjs +65 -2
  93. package/scripts/lib/dashboard-data.mjs +155 -24
  94. package/scripts/lib/dashboard-workbench.mjs +131 -12
  95. package/scripts/lib/dashboard-writer.mjs +20 -4
  96. package/scripts/lib/git-status-summary.mjs +46 -0
  97. package/scripts/lib/governance-index-generator.mjs +174 -0
  98. package/scripts/lib/governance-sync.mjs +611 -0
  99. package/scripts/lib/governance-table-boundary.mjs +175 -0
  100. package/scripts/lib/harness-core.mjs +6 -0
  101. package/scripts/lib/lesson-maintenance.mjs +36 -29
  102. package/scripts/lib/markdown-utils.mjs +33 -0
  103. package/scripts/lib/migration-planner.mjs +4 -6
  104. package/scripts/lib/migration-support.mjs +1 -1
  105. package/scripts/lib/phase-kind.mjs +50 -0
  106. package/scripts/lib/preset-audit-contracts.mjs +37 -0
  107. package/scripts/lib/preset-engine.mjs +494 -0
  108. package/scripts/lib/preset-registry.mjs +776 -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-builder.mjs +88 -0
  112. package/scripts/lib/status-dashboard-renderer.mjs +105 -0
  113. package/scripts/lib/subagent-authorization-audit.mjs +196 -0
  114. package/scripts/lib/task-audit-metadata.mjs +385 -0
  115. package/scripts/lib/task-audit-migration.mjs +350 -0
  116. package/scripts/lib/task-completion-consistency.mjs +26 -0
  117. package/scripts/lib/task-index.mjs +93 -0
  118. package/scripts/lib/task-lesson-candidates.mjs +242 -0
  119. package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
  120. package/scripts/lib/task-lifecycle/create-task-helpers.mjs +67 -0
  121. package/scripts/lib/task-lifecycle/phase-sync.mjs +88 -0
  122. package/scripts/lib/task-lifecycle/review-confirm.mjs +112 -0
  123. package/scripts/lib/task-lifecycle/review-gates.mjs +73 -0
  124. package/scripts/lib/task-lifecycle/review-submission.mjs +63 -0
  125. package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +49 -0
  126. package/scripts/lib/task-lifecycle/template-files.mjs +53 -0
  127. package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
  128. package/scripts/lib/task-lifecycle.mjs +338 -477
  129. package/scripts/lib/task-metadata.mjs +118 -0
  130. package/scripts/lib/task-review-model.mjs +455 -0
  131. package/scripts/lib/task-scanner.mjs +193 -372
  132. package/scripts/lib/task-tombstone-commands.mjs +140 -0
  133. package/scripts/postinstall.mjs +14 -0
  134. package/skills/preset-creator/SKILL.md +179 -0
  135. package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
  136. package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
  137. package/skills/preset-creator/references/complex-task-skeleton/brief.md +43 -0
  138. package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
  139. package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
  140. package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
  141. package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
  142. package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
  143. package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
  144. package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
  145. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
  146. package/skills/preset-creator/references/complex-task-skeleton/visual_map.md +50 -0
  147. package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
  148. package/templates/AGENTS.md.template +24 -18
  149. package/templates/dashboard/assets/app-src/00-state.js +13 -0
  150. package/templates/dashboard/assets/app-src/10-router.js +5 -1
  151. package/templates/dashboard/assets/app-src/20-overview.js +18 -8
  152. package/templates/dashboard/assets/app-src/30-tasks.js +92 -246
  153. package/templates/dashboard/assets/app-src/35-task-detail.js +286 -0
  154. package/templates/dashboard/assets/app-src/45-review.js +241 -22
  155. package/templates/dashboard/assets/app-src/50-migration.js +24 -10
  156. package/templates/dashboard/assets/app-src/55-presets.js +375 -0
  157. package/templates/dashboard/assets/app-src/60-shared.js +3 -1
  158. package/templates/dashboard/assets/app-src/90-bindings.js +302 -29
  159. package/templates/dashboard/assets/app.css +1501 -376
  160. package/templates/dashboard/assets/app.css.manifest.json +10 -0
  161. package/templates/dashboard/assets/app.js +1240 -101
  162. package/templates/dashboard/assets/app.manifest.json +2 -0
  163. package/templates/dashboard/assets/css-src/00-foundation.css +346 -0
  164. package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
  165. package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
  166. package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
  167. package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
  168. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +489 -0
  169. package/templates/dashboard/assets/css-src/45-presets.css +516 -0
  170. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
  171. package/templates/dashboard/assets/i18n.js +263 -23
  172. package/templates/ledger/Harness-Ledger.md +13 -25
  173. package/templates/lessons/lesson-arch-process-change.md +1 -1
  174. package/templates/lessons/lesson-new-doc.md +1 -1
  175. package/templates/lessons/lesson-ref-change.md +1 -1
  176. package/templates/planning/INDEX.md +87 -0
  177. package/templates/planning/brief.md +1 -1
  178. package/templates/planning/execution_strategy.md +31 -0
  179. package/templates/planning/lesson_candidates.md +18 -6
  180. package/templates/planning/module_session_prompt.md +1 -0
  181. package/templates/planning/optional/artifacts/INDEX.md +3 -3
  182. package/templates/planning/optional/references/INDEX.md +3 -3
  183. package/templates/planning/review.md +41 -0
  184. package/templates/planning/task_plan.md +5 -21
  185. package/templates/planning/visual_map.md +13 -9
  186. package/templates/planning/visual_map.simple.md +52 -0
  187. package/templates/reference/execution-workflow-standard.md +31 -3
  188. package/templates/reference/pull-request-standard.md +80 -0
  189. package/templates/reference/repo-governance-standard.md +7 -6
  190. package/templates/reference/review-routing-standard.md +6 -0
  191. package/templates/reference/walkthrough-standard.md +2 -1
  192. package/templates/verifier/verifier-output.md +1 -1
  193. package/templates-zh-CN/AGENTS.md.template +25 -19
  194. package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
  195. package/templates-zh-CN/planning/INDEX.md +87 -0
  196. package/templates-zh-CN/planning/brief.md +1 -1
  197. package/templates-zh-CN/planning/execution_strategy.md +30 -0
  198. package/templates-zh-CN/planning/lesson_candidates.md +18 -6
  199. package/templates-zh-CN/planning/module_session_prompt.md +1 -0
  200. package/templates-zh-CN/planning/review.md +41 -1
  201. package/templates-zh-CN/planning/task_plan.md +4 -44
  202. package/templates-zh-CN/planning/visual_map.md +14 -7
  203. package/templates-zh-CN/planning/visual_map.simple.md +48 -0
  204. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  205. package/templates-zh-CN/reference/docs-library-standard.md +1 -1
  206. package/templates-zh-CN/reference/execution-workflow-standard.md +33 -7
  207. package/templates-zh-CN/reference/harness-ledger-standard.md +2 -2
  208. package/templates-zh-CN/reference/pull-request-standard.md +106 -0
  209. package/templates-zh-CN/reference/repo-governance-standard.md +4 -3
  210. package/templates-zh-CN/reference/review-routing-standard.md +8 -1
  211. package/templates-zh-CN/reference/walkthrough-standard.md +3 -2
  212. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +1 -1
  213. package/docs-release/assets/dashboard-overview-en.png +0 -0
  214. package/scripts/smoke-dashboard.mjs +0 -92
  215. package/scripts/test-harness.mjs +0 -1395
  216. package/templates/ssot/Feature-SSoT.md +0 -43
  217. package/templates/ssot/Lessons-SSoT.md +0 -44
  218. package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
  219. package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
@@ -0,0 +1,55 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ lessonCandidatesFile,
5
+ readFileSafe,
6
+ toPosix,
7
+ visualMapFile,
8
+ } from "./core-shared.mjs";
9
+ import {
10
+ listTaskPlanPaths,
11
+ parseTaskBudget,
12
+ parseTaskContractInfo,
13
+ } from "./task-scanner.mjs";
14
+ import { parseTaskAuditMetadata } from "./task-audit-metadata.mjs";
15
+
16
+ export function validatePlanContracts(target, { strict = true, taskPlanPaths } = {}) {
17
+ const failures = [];
18
+ const warnings = [];
19
+ const report = (message) => {
20
+ if (strict) failures.push(message);
21
+ else warnings.push(`adoption-needed: ${message}`);
22
+ };
23
+ for (const taskPlanPath of taskPlanPaths || listTaskPlanPaths(target)) {
24
+ const taskDir = path.dirname(taskPlanPath);
25
+ const relativeDir = toPosix(path.relative(target.projectRoot, taskDir));
26
+ const taskPlanContent = readFileSafe(taskPlanPath);
27
+ const indexContent = readFileSafe(path.join(taskDir, "INDEX.md"));
28
+ const budget = parseTaskBudget(taskPlanContent);
29
+ const taskContract = parseTaskContractInfo(taskPlanContent);
30
+ const taskAudit = parseTaskAuditMetadata(indexContent, { required: strict && taskContract.generated });
31
+ if (!taskContract.generated) {
32
+ warnings.push(`adoption-needed: ${relativeDir} missing Task Contract: harness-task/v1 marker`);
33
+ }
34
+ for (const issue of taskAudit.issues) {
35
+ if (taskContract.generated || taskAudit.present) failures.push(`${relativeDir}/INDEX.md ${issue.message}`);
36
+ else report(`${relativeDir}/INDEX.md ${issue.message}`);
37
+ }
38
+ const indexRequired = /^Task Package Index\s*[::]\s*(required|yes|true|必需|必须|required)\s*$/im.test(taskPlanContent);
39
+ for (const fileName of requiredTaskFilesForBudget(budget, { indexRequired })) {
40
+ if (!fs.existsSync(path.join(taskDir, fileName))) {
41
+ if (taskContract.generated) failures.push(`${relativeDir} missing ${fileName}`);
42
+ else report(`${relativeDir} missing ${fileName}`);
43
+ }
44
+ }
45
+ }
46
+ return { failures, warnings };
47
+ }
48
+
49
+ function requiredTaskFilesForBudget(budget, { indexRequired = false } = {}) {
50
+ const simpleFiles = [...(indexRequired ? ["INDEX.md"] : []), "brief.md", "task_plan.md", visualMapFile, "progress.md"];
51
+ if (budget === "simple") return simpleFiles;
52
+ const standardFiles = [...simpleFiles, "execution_strategy.md", "findings.md", lessonCandidatesFile, "review.md"];
53
+ if (budget === "complex") return [...standardFiles, "references/INDEX.md", "artifacts/INDEX.md"];
54
+ return standardFiles;
55
+ }
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
 
@@ -10,6 +11,11 @@ export const legacyVisualRoadmapFile = "visual_roadmap.md";
10
11
  export const lessonCandidatesFile = "lesson_candidates.md";
11
12
  export const longRunningTaskContractFile = "long-running-task-contract.md";
12
13
  export const taskContractMarker = "Task Contract: harness-task/v1";
14
+ export const builtinPresetRoot = path.join(repoRoot, "presets");
15
+ export function userPresetRootForHome(home = "") {
16
+ return path.join(path.resolve(home || os.homedir()), ".coding-agent-harness/presets");
17
+ }
18
+ export const userPresetRoot = userPresetRootForHome();
13
19
 
14
20
 
15
21
  export const supportedLocales = new Set(["zh-CN", "en-US"]);
@@ -40,6 +46,10 @@ export function normalizeTarget(input = ".") {
40
46
  };
41
47
  }
42
48
 
49
+ export function projectPresetRoot(targetInput = ".") {
50
+ return path.join(normalizeTarget(targetInput).projectRoot, ".coding-agent-harness/presets");
51
+ }
52
+
43
53
  export function toPosix(value) {
44
54
  return value.split(path.sep).join("/");
45
55
  }
@@ -60,6 +70,15 @@ export function readFileSafe(filePath) {
60
70
  }
61
71
  }
62
72
 
73
+ export function readJsonSafe(filePath, fallback = null, { onError } = {}) {
74
+ try {
75
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
76
+ } catch (error) {
77
+ if (typeof onError === "function") onError(error);
78
+ return fallback;
79
+ }
80
+ }
81
+
63
82
  export function readBundledTemplate(source) {
64
83
  const sourcePath = path.join(repoRoot, source);
65
84
  if (!fs.existsSync(sourcePath)) throw new Error(`Bundled template missing: ${source}`);
@@ -68,15 +87,17 @@ export function readBundledTemplate(source) {
68
87
  return content;
69
88
  }
70
89
 
71
- export function walkFiles(root) {
90
+ export function walkFiles(root, options = {}) {
72
91
  const results = [];
73
92
  if (!fs.existsSync(root)) return results;
93
+ const dirFilter = typeof options.dirFilter === "function" ? options.dirFilter : () => true;
74
94
  function walk(dir) {
75
95
  for (const entry of fs.readdirSync(dir)) {
76
96
  const full = path.join(dir, entry);
77
97
  const stat = fs.statSync(full);
78
98
  if (stat.isDirectory()) {
79
99
  if ([".git", "node_modules", "tmp"].includes(entry)) continue;
100
+ if (!dirFilter(entry, full)) continue;
80
101
  walk(full);
81
102
  } else {
82
103
  results.push(full);
@@ -170,14 +191,56 @@ export function normalizeTaskId(value) {
170
191
  return slug(value || "task");
171
192
  }
172
193
 
173
- export function renderTaskTemplate(content, { taskId, title, locale, budget = "standard" }) {
194
+ export function renderTaskTemplate(content, { taskId, title, locale, budget = "standard", moduleKey = "", preset = "none", presetVersion = "", evidenceBundle = "", longRunning = false, scaffoldProvenance = {}, taskAudit = {} }) {
174
195
  const date = todayDate();
196
+ const provenance = {
197
+ createdBy: scaffoldProvenance.createdBy || "harness new-task",
198
+ command: scaffoldProvenance.command || "harness new-task [task-id] <target>",
199
+ createdAt: scaffoldProvenance.createdAt || date,
200
+ budget: scaffoldProvenance.budget || budget,
201
+ templateSource: scaffoldProvenance.templateSource || "templates/planning/brief.md",
202
+ exceptionReason: scaffoldProvenance.exceptionReason || "n/a",
203
+ };
175
204
  return String(content)
176
205
  .replaceAll("{{TASK_ID}}", taskId)
177
206
  .replaceAll("{{TASK_TITLE}}", title)
178
207
  .replaceAll("{{DATE}}", date)
179
208
  .replaceAll("{{LOCALE}}", normalizeLocale(locale))
180
209
  .replaceAll("{{TASK_BUDGET}}", budget)
210
+ .replaceAll("{{TASK_MODULE}}", moduleKey || "n/a")
211
+ .replaceAll("{{TASK_PRESET}}", preset || "none")
212
+ .replaceAll("{{TASK_PRESET_VERSION}}", presetVersion || "n/a")
213
+ .replaceAll("{{TASK_EVIDENCE_BUNDLE}}", evidenceBundle || "n/a")
214
+ .replaceAll("{{TASK_LONG_RUNNING}}", longRunning ? "yes" : "no")
215
+ .replaceAll("{{SCAFFOLD_CREATED_BY}}", provenance.createdBy)
216
+ .replaceAll("{{SCAFFOLD_COMMAND}}", provenance.command)
217
+ .replaceAll("{{SCAFFOLD_CREATED_AT}}", provenance.createdAt)
218
+ .replaceAll("{{SCAFFOLD_BUDGET}}", provenance.budget)
219
+ .replaceAll("{{SCAFFOLD_TEMPLATE_SOURCE}}", provenance.templateSource)
220
+ .replaceAll("{{SCAFFOLD_EXCEPTION_REASON}}", provenance.exceptionReason)
221
+ .replaceAll("{{TASK_AUDIT_CREATED_BY}}", taskAudit["Created By"] || provenance.createdBy)
222
+ .replaceAll("{{TASK_AUDIT_CREATED_AT}}", taskAudit["Created At"] || provenance.createdAt)
223
+ .replaceAll("{{TASK_AUDIT_COMMAND_SHAPE}}", taskAudit["Command Shape"] || provenance.command)
224
+ .replaceAll("{{TASK_AUDIT_BUDGET}}", taskAudit.Budget || provenance.budget)
225
+ .replaceAll("{{TASK_AUDIT_TEMPLATE_SOURCE}}", taskAudit["Template Source"] || provenance.templateSource)
226
+ .replaceAll("{{TASK_AUDIT_TASK_CREATOR}}", taskAudit["Task Creator"] || "n/a")
227
+ .replaceAll("{{TASK_AUDIT_TASK_CREATOR_SOURCE}}", taskAudit["Task Creator Source"] || "git-unavailable")
228
+ .replaceAll("{{TASK_AUDIT_HUMAN_REVIEW_STATUS}}", taskAudit["Human Review Status"] || "not-confirmed")
229
+ .replaceAll("{{TASK_AUDIT_CONFIRMATION_ID}}", taskAudit["Confirmation ID"] || "n/a")
230
+ .replaceAll("{{TASK_AUDIT_CONFIRMED_AT}}", taskAudit["Confirmed At"] || "n/a")
231
+ .replaceAll("{{TASK_AUDIT_REVIEWER}}", taskAudit.Reviewer || "n/a")
232
+ .replaceAll("{{TASK_AUDIT_REVIEWER_EMAIL}}", taskAudit["Reviewer Email"] || "n/a")
233
+ .replaceAll("{{TASK_AUDIT_CONFIRM_TEXT}}", taskAudit["Confirm Text"] || "n/a")
234
+ .replaceAll("{{TASK_AUDIT_EVIDENCE_CHECKED}}", taskAudit["Evidence Checked"] || "n/a")
235
+ .replaceAll("{{TASK_AUDIT_REVIEW_COMMIT_SHA}}", taskAudit["Review Commit SHA"] || "n/a")
236
+ .replaceAll("{{TASK_AUDIT_AUDIT_SOURCE}}", taskAudit["Audit Source"] || "native-index")
237
+ .replaceAll("{{TASK_AUDIT_AUDIT_STATUS}}", taskAudit["Audit Status"] || "created")
238
+ .replaceAll("{{TASK_AUDIT_EXCEPTION_REASON}}", taskAudit["Exception Reason"] || provenance.exceptionReason)
239
+ .replaceAll("{{TASK_AUDIT_MESSAGE}}", taskAudit.Message || "n/a")
240
+ .replaceAll("{{TASK_AUDIT_MIGRATION_STATUS}}", taskAudit["Migration Status"] || "native")
241
+ .replaceAll("{{TASK_AUDIT_MIGRATED_FROM}}", taskAudit["Migrated From"] || "n/a")
242
+ .replaceAll("{{TASK_AUDIT_LEGACY_EXTRA_FIELDS}}", taskAudit["Legacy Extra Fields"] || "{}")
243
+ .replaceAll("{{TASK_AUDIT_MIGRATION_NOTES}}", taskAudit["Migration Notes"] || "n/a")
181
244
  .replaceAll("[simple / standard / complex]", budget)
182
245
  .replaceAll("[simple / standard / long-running / module-parallel]", budget)
183
246
  .replaceAll("[simple / complex]", budget)
@@ -1,8 +1,12 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { spawnSync } from "node:child_process";
3
4
  import {
5
+ legacyChecker,
4
6
  repoRoot,
7
+ builtinPresetRoot,
5
8
  normalizeTarget,
9
+ projectPresetRoot,
6
10
  readFileSafe,
7
11
  sanitizeText,
8
12
  sanitizeDeep,
@@ -15,24 +19,29 @@ import {
15
19
  legacyVisualRoadmapFile,
16
20
  lessonCandidatesFile,
17
21
  longRunningTaskContractFile,
22
+ userPresetRoot,
18
23
  } from "./core-shared.mjs";
19
24
  import {
20
25
  parseAllMarkdownTables,
21
26
  getCell,
22
27
  splitDependencies,
23
28
  } from "./markdown-utils.mjs";
24
- import { readCapabilityRegistry } from "./capability-registry.mjs";
25
- import { buildStatus } from "./check-profiles.mjs";
29
+ import { readCapabilityRegistry, validateCapabilities } from "./capability-registry.mjs";
30
+ import { buildStatusData } from "./status-builder.mjs";
26
31
  import {
27
32
  listTaskPlanPaths,
28
33
  parseTaskState,
29
34
  isActiveTaskState,
30
35
  } from "./task-scanner.mjs";
31
36
  import { writeDashboardDirectory, writeDashboardFile } from "./dashboard-writer.mjs";
37
+ import { listPresetPackageLayers } from "./preset-registry.mjs";
38
+ import { validateGovernanceTableBoundaries } from "./governance-table-boundary.mjs";
39
+ import { summarizeGitState } from "./git-status-summary.mjs";
32
40
 
33
- export function collectMarkdownDocuments(target) {
34
- const docs = collectDashboardDocumentPaths(target);
35
- return docs.map((file, index) => {
41
+ export function collectMarkdownDocuments(target, options = {}) {
42
+ const docs = collectDashboardDocumentPaths(target, options);
43
+ return docs.map((entry, index) => {
44
+ const file = typeof entry === "string" ? entry : entry.file;
36
45
  const content = sanitizeText(readFileSafe(file));
37
46
  const source = prefixedPath(target, file);
38
47
  return {
@@ -41,12 +50,14 @@ export function collectMarkdownDocuments(target) {
41
50
  title: titleFromMarkdown(content, path.basename(file)),
42
51
  type: documentKind(source),
43
52
  content,
53
+ ...(entry.partial ? { partial: true, partialReason: entry.partialReason || "partial", taskId: entry.taskId || "" } : {}),
44
54
  };
45
55
  });
46
56
  }
47
57
 
48
- function collectDashboardDocumentPaths(target) {
58
+ function collectDashboardDocumentPaths(target, options = {}) {
49
59
  const selected = new Set();
60
+ const partial = new Map();
50
61
  const addDocsPath = (relativePath) => {
51
62
  const file = path.join(target.docsRoot, relativePath);
52
63
  if (fs.existsSync(file)) selected.add(file);
@@ -56,7 +67,6 @@ function collectDashboardDocumentPaths(target) {
56
67
  "09-PLANNING/Module-Registry.md",
57
68
  "05-TEST-QA/Regression-SSoT.md",
58
69
  "05-TEST-QA/Cadence-Ledger.md",
59
- "01-GOVERNANCE/Lessons-SSoT.md",
60
70
  "10-WALKTHROUGH/Closeout-SSoT.md",
61
71
  ]) {
62
72
  addDocsPath(relativePath);
@@ -67,21 +77,38 @@ function collectDashboardDocumentPaths(target) {
67
77
  if (path.basename(file).startsWith("_")) continue;
68
78
  selected.add(file);
69
79
  }
70
- for (const taskPlanPath of listTaskPlanPaths(target)) {
80
+ const tasksByPlanPath = new Map((options.tasks || []).map((task) => [
81
+ path.join(target.projectRoot, String(task.taskPlanPath || "").replace(/^TARGET:/, "")),
82
+ task,
83
+ ]));
84
+ for (const taskPlanPath of options.taskPlanPaths || listTaskPlanPaths(target)) {
71
85
  const taskDir = path.dirname(taskPlanPath);
72
86
  const progress = readFileSafe(path.join(taskDir, "progress.md"));
73
87
  const state = parseTaskState(progress);
74
88
  const active = isActiveTaskState(state);
75
- const documentNames = active
76
- ? ["brief.md", "task_plan.md", "execution_strategy.md", visualMapFile, legacyVisualRoadmapFile, lessonCandidatesFile, longRunningTaskContractFile, "progress.md", "review.md", "findings.md"]
89
+ const task = tasksByPlanPath.get(taskPlanPath);
90
+ const historicalClosed = !active && task?.closeoutStatus === "closed";
91
+ const documentNames = historicalClosed
92
+ ? ["brief.md"]
77
93
  : ["brief.md", "task_plan.md", "execution_strategy.md", visualMapFile, legacyVisualRoadmapFile, lessonCandidatesFile, longRunningTaskContractFile, "progress.md", "review.md", "findings.md"];
78
94
  for (const fileName of documentNames) {
79
95
  const file = path.join(taskDir, fileName);
80
- if (fs.existsSync(file)) selected.add(file);
96
+ if (fs.existsSync(file)) {
97
+ selected.add(file);
98
+ if (historicalClosed) {
99
+ partial.set(file, {
100
+ partial: true,
101
+ partialReason: "historical-closed",
102
+ taskId: task?.id || path.basename(taskDir),
103
+ });
104
+ }
105
+ }
81
106
  }
82
- for (const indexFile of ["references/INDEX.md", "artifacts/INDEX.md"]) {
83
- const file = path.join(taskDir, indexFile);
84
- if (fs.existsSync(file)) selected.add(file);
107
+ if (!historicalClosed) {
108
+ for (const indexFile of ["references/INDEX.md", "artifacts/INDEX.md"]) {
109
+ const file = path.join(taskDir, indexFile);
110
+ if (fs.existsSync(file)) selected.add(file);
111
+ }
85
112
  }
86
113
  }
87
114
  for (const file of walkFiles(path.join(target.docsRoot, "09-PLANNING/MODULES"))) {
@@ -95,7 +122,8 @@ function collectDashboardDocumentPaths(target) {
95
122
  .filter((file) => !file.includes(`${path.sep}_archive${path.sep}`))
96
123
  .filter((file) => !file.includes(`${path.sep}_task-template${path.sep}`))
97
124
  .filter((file) => !file.includes(`${path.sep}_optional-structures${path.sep}`))
98
- .sort();
125
+ .sort()
126
+ .map((file) => ({ file, ...(partial.get(file) || {}) }));
99
127
  }
100
128
 
101
129
  function documentKind(source) {
@@ -104,7 +132,7 @@ function documentKind(source) {
104
132
  if (lower.includes("module-registry.md")) return "module-registry";
105
133
  if (lower.includes("regression-ssot.md")) return "regression-ssot";
106
134
  if (lower.includes("cadence-ledger.md")) return "cadence-ledger";
107
- if (lower.includes("lessons-ssot.md")) return "lessons-ssot";
135
+ if (/\/01-governance\/lessons\/[^/]+\.md$/i.test(lower)) return "lesson-detail";
108
136
  if (lower.endsWith("/progress.md")) return "task-progress";
109
137
  if (lower.endsWith("/brief.md")) return "task-brief";
110
138
  if (lower.endsWith("/review.md")) return "task-review";
@@ -146,7 +174,17 @@ export function collectGraph(status, tables = { tables: [] }) {
146
174
  addNode({ id: `task:${task.id}`, type: "task", label: task.title, state: task.state, completion: task.completion });
147
175
  for (const phase of task.phases || []) {
148
176
  const phaseId = `phase:${task.id}:${phase.id}`;
149
- addNode({ id: phaseId, type: "phase", label: phase.id, state: phase.state, completion: phase.completion, taskId: task.id });
177
+ addNode({
178
+ id: phaseId,
179
+ type: "phase",
180
+ label: phase.id,
181
+ state: phase.state,
182
+ completion: phase.completion,
183
+ kind: phase.kind,
184
+ actor: phase.actor,
185
+ exitCommand: phase.exitCommand,
186
+ taskId: task.id,
187
+ });
150
188
  addEdge({ from: `task:${task.id}`, to: phaseId, type: "contains" });
151
189
  for (const dependency of phase.dependsOn || []) {
152
190
  addEdge({ from: `phase:${task.id}:${dependency}`, to: phaseId, type: "depends_on" });
@@ -200,6 +238,7 @@ export function collectGraph(status, tables = { tables: [] }) {
200
238
  }
201
239
 
202
240
  export function categorizeWarning(message) {
241
+ if (/governance-table-entropy/i.test(message)) return "Governance Table Boundary";
203
242
  if (/missing execution_strategy\.md|missing visual_(?:map|roadmap)\.md|Visual (?:Map|Roadmap)/i.test(message)) return "Plan Contract Missing";
204
243
  if (/legacy-compat|adoption-needed|legacy check/i.test(message)) return "Adoption Advice";
205
244
  if (/Evidence|evidence/i.test(message)) return "Missing Evidence";
@@ -213,6 +252,7 @@ function warningType(message) {
213
252
  if (/missing visual_map\.md|Visual Map/i.test(message)) return "missing-visual-map";
214
253
  if (/missing visual_roadmap\.md|Visual Roadmap/i.test(message)) return "missing-visual-roadmap";
215
254
  if (/Reviewer Identity|Confidence Challenge|Final Confidence Basis|Evidence Checked/i.test(message)) return "review-schema-gap";
255
+ if (/governance-table-entropy/i.test(message)) return "governance-table-entropy";
216
256
  if (/Evidence|evidence/i.test(message)) return "missing-evidence";
217
257
  if (/missing required file/i.test(message)) return "legacy-reference-gap";
218
258
  if (/legacy-compat|legacy check|adoption-needed/i.test(message)) return "capability-adoption";
@@ -231,6 +271,7 @@ function warningScope(message) {
231
271
 
232
272
  function warningPhase(type, scope) {
233
273
  if (type === "capability-adoption") return "baseline";
274
+ if (type === "governance-table-entropy") return "global-table-boundary";
234
275
  if (type === "missing-brief" || type === "missing-execution-strategy" || type === "missing-visual-map" || type === "missing-visual-roadmap") return "active-task-contracts";
235
276
  if (scope === "module") return "module-classification";
236
277
  if (type === "review-schema-gap" || type === "missing-evidence") return "review-evidence";
@@ -240,6 +281,7 @@ function warningPhase(type, scope) {
240
281
 
241
282
  function warningFixability(type, scope) {
242
283
  if (["missing-brief", "missing-execution-strategy", "missing-visual-map", "missing-visual-roadmap"].includes(type)) return "guided";
284
+ if (type === "governance-table-entropy") return "manual";
243
285
  if (type === "legacy-reference-gap" || scope === "reference") return "template";
244
286
  if (type === "capability-adoption") return "decision";
245
287
  if (type === "review-schema-gap" || type === "missing-evidence") return "human-evidence";
@@ -248,6 +290,7 @@ function warningFixability(type, scope) {
248
290
 
249
291
  function warningPriority(type, scope, message) {
250
292
  if (/fail|invalid|blocked/i.test(message) || type === "schema-drift") return "P1";
293
+ if (type === "governance-table-entropy") return /legacy-report-only/i.test(message) ? "P3" : "P2";
251
294
  if (["missing-brief", "missing-execution-strategy", "missing-visual-map", "missing-visual-roadmap"].includes(type) && scope === "task") return "P2";
252
295
  if (type === "review-schema-gap" || type === "missing-evidence") return "P2";
253
296
  if (type === "capability-adoption") return "P3";
@@ -284,19 +327,24 @@ function summarizeWarnings(warnings) {
284
327
  }
285
328
 
286
329
  export function collectAdoption(status) {
287
- const warnings = status.checkState.details.warnings.flatMap((message) => splitWarningMessage(message)).map((message, index) => {
330
+ const dashboardMessages = [
331
+ ...(status.checkState.details.warnings || []),
332
+ ...(status.checkState.details.failures || []).filter((message) => /governance-table-entropy/i.test(message)),
333
+ ];
334
+ const warnings = dashboardMessages.flatMap((message) => splitWarningMessage(message)).map((message, index) => {
288
335
  const type = warningType(message);
289
336
  const scope = warningScope(message);
290
337
  const affectedPaths = warningAffectedPaths(message);
338
+ const stableSuffix = type === "governance-table-entropy" ? `-${stableWarningIdPart(governanceWarningRowKey(message))}` : "";
291
339
  return {
292
- id: `AD-${String(index + 1).padStart(3, "0")}`,
340
+ id: `AD-${String(index + 1).padStart(3, "0")}${stableSuffix}`,
293
341
  category: categorizeWarning(message),
294
342
  type,
295
343
  scope,
296
344
  priority: warningPriority(type, scope, message),
297
345
  phase: warningPhase(type, scope),
298
346
  fixability: warningFixability(type, scope),
299
- status: "open",
347
+ status: /legacy-report-only/i.test(message) ? "legacy-report-only" : "open",
300
348
  confidence: warningConfidence(message),
301
349
  severity: status.mode === "legacy-compat" ? "advice" : "warning",
302
350
  title: warningTitle(message),
@@ -330,6 +378,18 @@ export function collectAdoption(status) {
330
378
  };
331
379
  }
332
380
 
381
+ function governanceWarningRowKey(message) {
382
+ const match = String(message || "").match(/\brow\s+([^:]+)/i);
383
+ return match ? match[1].trim() : "global-table";
384
+ }
385
+
386
+ function stableWarningIdPart(value) {
387
+ return String(value || "global-table")
388
+ .replace(/[^A-Za-z0-9_-]+/g, "-")
389
+ .replace(/^-+|-+$/g, "")
390
+ .slice(0, 80) || "global-table";
391
+ }
392
+
333
393
  export function splitWarningMessage(message) {
334
394
  return String(message || "")
335
395
  .split(/\n-\s+/)
@@ -338,6 +398,7 @@ export function splitWarningMessage(message) {
338
398
  }
339
399
 
340
400
  function warningTitle(message) {
401
+ if (/governance-table-entropy/i.test(message)) return "Global table boundary";
341
402
  if (/missing execution_strategy\.md/i.test(message)) return "Missing execution strategy";
342
403
  if (/missing visual_map\.md|Visual Map/i.test(message)) return "Missing visual map";
343
404
  if (/missing visual_roadmap\.md|Visual Roadmap/i.test(message)) return "Missing legacy visual roadmap";
@@ -354,6 +415,7 @@ function warningAffected(message) {
354
415
  }
355
416
 
356
417
  function warningAction(message) {
418
+ if (/governance-table-entropy/i.test(message)) return "Move local detail to module/task docs; keep the global row to summary, state, route, and audit result.";
357
419
  if (/execution_strategy\.md/i.test(message)) return "Add standalone execution strategy file.";
358
420
  if (/visual_map\.md|Visual Map/i.test(message)) return "Add standalone visual map file.";
359
421
  if (/visual_roadmap\.md|Visual Roadmap/i.test(message)) return "Rewrite legacy visual_roadmap.md into canonical visual_map.md.";
@@ -363,13 +425,82 @@ function warningAction(message) {
363
425
  }
364
426
 
365
427
  export function buildDashboardBundle(targetInput, options = {}) {
366
- const status = buildStatus(targetInput, options);
367
428
  const target = normalizeTarget(targetInput);
368
- const documents = { documents: collectMarkdownDocuments(target) };
429
+ const taskPlanPaths = listTaskPlanPaths(target);
430
+ const capabilityState = validateCapabilities(target);
431
+ const gitState = summarizeGitState(target);
432
+ const declaredCapabilities = new Set(capabilityState.registry.capabilities.map((capability) => capability.name));
433
+ const shouldRunLegacy = !options.skipLegacyCheck && (capabilityState.registry.mode === "legacy-compat" || declaredCapabilities.has("safe-adoption"));
434
+ const legacy = shouldRunLegacy ? runDashboardLegacyCheck(target) : { status: "skipped", code: 0, stdout: "", stderr: "" };
435
+ const legacyWarnings = legacy.status === "fail" ? [`adoption-needed: legacy check failed: ${(legacy.stderr || legacy.stdout).trim()}`] : [];
436
+ const governanceBoundaries = validateGovernanceTableBoundaries(target);
437
+ const status = buildStatusData(target, {
438
+ ...options,
439
+ capabilityState,
440
+ gitState,
441
+ taskPlanPaths,
442
+ legacy,
443
+ failures: [...capabilityState.failures, ...governanceBoundaries.failures],
444
+ warnings: [...capabilityState.warnings, ...legacyWarnings, ...governanceBoundaries.warnings, ...gitState.warnings],
445
+ });
446
+ const documents = { documents: collectMarkdownDocuments(target, { taskPlanPaths, tasks: status.tasks }) };
369
447
  const tables = collectTables(documents.documents);
370
448
  const graph = collectGraph(status, tables);
371
449
  const adoption = collectAdoption(status);
372
- return sanitizeDeep({ status, tables, documents, graph, adoption });
450
+ const presetCatalog = collectPresetCatalog(targetInput, target, options);
451
+ return sanitizeDeep({ status, tables, documents, graph, adoption, presetCatalog });
452
+ }
453
+
454
+ function runDashboardLegacyCheck(target) {
455
+ const checkTarget = target.docsOnly ? target.projectRoot : target.input;
456
+ const result = spawnSync(process.execPath, [legacyChecker, checkTarget], {
457
+ cwd: repoRoot,
458
+ encoding: "utf8",
459
+ });
460
+ return {
461
+ status: result.status === 0 ? "pass" : "fail",
462
+ code: result.status ?? 1,
463
+ stdout: result.stdout || "",
464
+ stderr: result.stderr || "",
465
+ };
466
+ }
467
+
468
+ export function collectPresetCatalog(targetInput, target = normalizeTarget(targetInput), options = {}) {
469
+ const home = options.home || "";
470
+ const presets = listPresetPackageLayers({ targetInput: target.projectRoot, home }).map((preset) => ({
471
+ key: `${preset.source}:${preset.id}`,
472
+ id: preset.id,
473
+ version: preset.version,
474
+ source: preset.source,
475
+ effective: preset.effective === true,
476
+ purpose: preset.purpose,
477
+ compatibleBudgets: preset.compatibleBudgets,
478
+ manifestPath: preset.manifestRelativePath,
479
+ manifestSha256: preset.manifestSha256,
480
+ taskKind: preset.task?.kind || "",
481
+ inputCount: Object.keys(preset.inputs || {}).length,
482
+ referenceCount: Object.keys(preset.resources?.references || {}).length,
483
+ artifactCount: Object.keys(preset.resources?.artifacts || {}).length,
484
+ writeScopeCount: Object.keys(preset.writeScopes || {}).length,
485
+ evidenceFileCount: Object.keys(preset.evidence?.files || {}).length,
486
+ requiredReadCount: Array.isArray(preset.context?.requiredReads) ? preset.context.requiredReads.length : 0,
487
+ checkStatus: "unknown",
488
+ }));
489
+ const countSource = (source) => presets.filter((preset) => preset.source === source).length;
490
+ return {
491
+ summary: {
492
+ total: presets.length,
493
+ project: countSource("project"),
494
+ user: countSource("user"),
495
+ builtin: countSource("builtin"),
496
+ },
497
+ roots: [
498
+ { source: "project", path: projectPresetRoot(target.projectRoot) },
499
+ { source: "user", path: home ? path.join(path.resolve(home), ".coding-agent-harness/presets") : userPresetRoot },
500
+ { source: "builtin", path: builtinPresetRoot },
501
+ ],
502
+ presets,
503
+ };
373
504
  }
374
505
 
375
506
  export function writeDashboardFolder(outDir, targetInput, options = {}) {
@@ -377,7 +508,7 @@ export function writeDashboardFolder(outDir, targetInput, options = {}) {
377
508
  const registry = readCapabilityRegistry(target);
378
509
  const locale = options.localeOverride || registry.locale;
379
510
  const bundle = buildDashboardBundle(targetInput, options);
380
- return writeDashboardDirectory(outDir, bundle, { repoRoot, projectRoot: target.projectRoot, docsRoot: target.docsRoot, locale, workbenchRuntime: options.workbenchRuntime === true });
511
+ return writeDashboardDirectory(outDir, bundle, { repoRoot, projectRoot: target.projectRoot, docsRoot: target.docsRoot, locale, workbenchRuntime: options.workbenchRuntime === true, recoverGeneratedDashboard: options.recoverGeneratedDashboard === true });
381
512
  }
382
513
 
383
514
  export function writeDashboardSingleFile(outFile, targetInput, options = {}) {