coding-agent-harness 1.0.7 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/CONTRIBUTING.md +9 -5
  3. package/README.md +12 -2
  4. package/README.zh-CN.md +10 -2
  5. package/SKILL.md +14 -3
  6. package/dist/build-dist.mjs +32 -6
  7. package/dist/check-dist-observation.mjs +73 -28
  8. package/dist/check-harness.mjs +0 -1
  9. package/dist/check-import-graph.mjs +44 -27
  10. package/dist/check-lite-forbidden-surfaces.mjs +121 -0
  11. package/dist/check-no-ts-nocheck.mjs +88 -0
  12. package/dist/check-runtime-emit.mjs +10 -3
  13. package/dist/check-type-boundaries.mjs +67 -8
  14. package/dist/commands/dashboard-command.mjs +52 -14
  15. package/dist/commands/migration-command.mjs +18 -8
  16. package/dist/commands/module-command.mjs +142 -0
  17. package/dist/commands/preset-command.mjs +65 -4
  18. package/dist/commands/registry.mjs +483 -0
  19. package/dist/commands/task-command.mjs +111 -53
  20. package/dist/harness.mjs +6 -303
  21. package/dist/lib/capability-registry.mjs +229 -53
  22. package/dist/lib/check-module-parallel.mjs +1 -6
  23. package/dist/lib/check-profiles.mjs +39 -46
  24. package/dist/lib/check-task-contracts.mjs +6 -4
  25. package/dist/lib/command-registry.mjs +248 -0
  26. package/dist/lib/core-shared.mjs +78 -3
  27. package/dist/lib/dashboard-data.mjs +203 -22
  28. package/dist/lib/dashboard-workbench.mjs +245 -21
  29. package/dist/lib/dashboard-writer.mjs +4 -1
  30. package/dist/lib/git-status-summary.mjs +0 -1
  31. package/dist/lib/governance-index-generator.mjs +7 -5
  32. package/dist/lib/governance-sync.mjs +46 -121
  33. package/dist/lib/governance-table-boundary.mjs +1 -14
  34. package/dist/lib/harness-core.mjs +5 -1
  35. package/dist/lib/harness-paths.mjs +115 -1
  36. package/dist/lib/impact-classifier.mjs +420 -0
  37. package/dist/lib/lesson-maintenance.mjs +1 -2
  38. package/dist/lib/markdown-utils.mjs +50 -1
  39. package/dist/lib/migration-planner.mjs +31 -16
  40. package/dist/lib/migration-support.mjs +5 -4
  41. package/dist/lib/module-registry.mjs +296 -0
  42. package/dist/lib/preset-audit-contracts.mjs +24 -1
  43. package/dist/lib/preset-engine.mjs +68 -29
  44. package/dist/lib/preset-registry.mjs +374 -72
  45. package/dist/lib/preset-runner.mjs +560 -0
  46. package/dist/lib/review-confirm-git-gate.mjs +73 -19
  47. package/dist/lib/status-builder.mjs +23 -8
  48. package/dist/lib/structure-migration.mjs +6 -4
  49. package/dist/lib/subagent-authorization-audit.mjs +8 -2
  50. package/dist/lib/task-archive-eligibility.mjs +65 -0
  51. package/dist/lib/task-audit-metadata.mjs +25 -11
  52. package/dist/lib/task-audit-migration.mjs +21 -14
  53. package/dist/lib/task-discovery-contract.mjs +32 -0
  54. package/dist/lib/task-index.mjs +4 -2
  55. package/dist/lib/task-lesson-candidates.mjs +1 -2
  56. package/dist/lib/task-lesson-sedimentation.mjs +310 -9
  57. package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
  58. package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
  59. package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
  60. package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
  61. package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
  62. package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
  63. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
  64. package/dist/lib/task-lifecycle/template-files.mjs +2 -5
  65. package/dist/lib/task-lifecycle.mjs +117 -159
  66. package/dist/lib/task-metadata.mjs +10 -5
  67. package/dist/lib/task-preset-contract-drift.mjs +45 -0
  68. package/dist/lib/task-repository.mjs +192 -0
  69. package/dist/lib/task-review-model.mjs +38 -17
  70. package/dist/lib/task-scanner.mjs +75 -23
  71. package/dist/lib/task-template-materials.mjs +131 -0
  72. package/dist/lib/task-tombstone-commands.mjs +187 -18
  73. package/dist/lib/types/check-profiles.js +1 -0
  74. package/dist/lib/types/impact.js +1 -0
  75. package/dist/lib/types/preset.js +1 -0
  76. package/dist/lib/types/task-lifecycle.js +1 -0
  77. package/dist/lib/types/task-scanner.js +1 -0
  78. package/dist/postinstall.mjs +2 -2
  79. package/dist/run-built-tests.mjs +10 -3
  80. package/docs-release/README.md +2 -1
  81. package/docs-release/architecture/document-contract-kernel/README.md +150 -0
  82. package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
  83. package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
  84. package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
  85. package/docs-release/architecture/overview.md +2 -2
  86. package/docs-release/architecture/overview.zh-CN.md +2 -2
  87. package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
  88. package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
  89. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
  90. package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
  91. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
  92. package/docs-release/architecture/system-explainer/README.md +1 -1
  93. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
  94. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
  95. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
  96. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
  97. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +2 -2
  98. package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
  99. package/docs-release/guides/agent-installation.en-US.md +4 -6
  100. package/docs-release/guides/agent-installation.md +11 -8
  101. package/docs-release/guides/contributing.md +10 -3
  102. package/docs-release/guides/contributing.zh-CN.md +10 -3
  103. package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
  104. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
  105. package/docs-release/guides/migration-playbook.en-US.md +9 -6
  106. package/docs-release/guides/migration-playbook.md +9 -6
  107. package/docs-release/guides/preset-development.md +68 -2
  108. package/docs-release/guides/task-state-machine.en-US.md +8 -8
  109. package/docs-release/guides/task-state-machine.md +7 -7
  110. package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
  111. package/package.json +19 -11
  112. package/postinstall.mjs +37 -0
  113. package/presets/legacy-migration/preset.yaml +5 -5
  114. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  115. package/presets/lesson-sedimentation/preset.yaml +3 -3
  116. package/presets/module/preset.yaml +2 -2
  117. package/presets/module/templates/execution_strategy.append.md +1 -1
  118. package/presets/module/templates/task_plan.append.md +3 -3
  119. package/presets/release-closeout/checks/check-release-package.mjs +29 -0
  120. package/presets/release-closeout/preset.yaml +100 -0
  121. package/presets/release-closeout/scripts/generate-release-package.mjs +572 -0
  122. package/presets/release-closeout/templates/execution_strategy.append.md +7 -0
  123. package/presets/release-closeout/templates/findings.seed.md +5 -0
  124. package/presets/release-closeout/templates/review.seed.md +3 -0
  125. package/presets/release-closeout/templates/task_plan.append.md +24 -0
  126. package/presets/standard-task/preset.yaml +2 -2
  127. package/references/agents-md-pattern.md +23 -17
  128. package/references/lessons-governance.md +2 -2
  129. package/references/module-parallel-standard.md +3 -6
  130. package/references/pull-request-standard.md +2 -2
  131. package/references/ssot-governance.md +2 -2
  132. package/references/taskr-gap-analysis.md +3 -3
  133. package/run-dist.mjs +34 -0
  134. package/skills/preset-creator/SKILL.md +40 -8
  135. package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
  136. package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
  137. package/skills/preset-creator/references/structure-aware-paths.md +112 -0
  138. package/templates/AGENTS.md.template +28 -26
  139. package/templates/architecture/README.md +2 -2
  140. package/templates/architecture/service-catalog.md +2 -2
  141. package/templates/architecture/services/service-template.md +1 -1
  142. package/templates/dashboard/assets/app-src/00-state.js +5 -1
  143. package/templates/dashboard/assets/app-src/10-router.js +7 -0
  144. package/templates/dashboard/assets/app-src/20-overview.js +8 -8
  145. package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
  146. package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
  147. package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
  148. package/templates/dashboard/assets/app-src/40-modules.js +257 -41
  149. package/templates/dashboard/assets/app-src/45-review.js +127 -1
  150. package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
  151. package/templates/dashboard/assets/app.css +928 -53
  152. package/templates/dashboard/assets/app.css.manifest.json +2 -0
  153. package/templates/dashboard/assets/app.js +1071 -98
  154. package/templates/dashboard/assets/app.manifest.json +1 -0
  155. package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
  156. package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
  157. package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
  158. package/templates/dashboard/assets/css-src/31-archive.css +94 -0
  159. package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
  160. package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
  161. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
  162. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
  163. package/templates/dashboard/assets/i18n.js +166 -2
  164. package/templates/development/README.md +9 -9
  165. package/templates/development/cross-repo-debugging.md +3 -3
  166. package/templates/development/external-context/service-template.md +1 -1
  167. package/templates/development/external-source-packs/README.md +2 -2
  168. package/templates/integrations/README.md +4 -4
  169. package/templates/integrations/api-contract.md +1 -1
  170. package/templates/integrations/event-contract.md +1 -1
  171. package/templates/integrations/third-party/vendor-template.md +1 -1
  172. package/templates/integrations/webhook-contract.md +1 -1
  173. package/templates/ledger/Harness-Ledger.md +1 -1
  174. package/templates/modules/module_brief.md +50 -0
  175. package/templates/modules/module_plan.md +49 -0
  176. package/templates/modules/registry_view.md +9 -0
  177. package/templates/modules/session_prompt_pack.md +55 -0
  178. package/templates/planning/brief.md +32 -8
  179. package/templates/planning/module_brief.md +28 -3
  180. package/templates/planning/module_plan.md +26 -11
  181. package/templates/planning/module_session_prompt.md +11 -2
  182. package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
  183. package/templates/planning/review.md +1 -1
  184. package/templates/planning/visual_map.md +1 -1
  185. package/templates/reference/docs-library-standard.md +7 -7
  186. package/templates/reference/execution-workflow-standard.md +13 -0
  187. package/templates/reference/external-source-intake-standard.md +10 -10
  188. package/templates/reference/pull-request-standard.md +2 -2
  189. package/templates/reference/repo-governance-standard.md +1 -1
  190. package/templates/reference/review-routing-standard.md +4 -0
  191. package/templates/ssot/Module-Registry.md +4 -38
  192. package/templates/walkthrough/walkthrough-template.md +1 -1
  193. package/templates-zh-CN/AGENTS.md.template +27 -25
  194. package/templates-zh-CN/CLAUDE.md.template +1 -1
  195. package/templates-zh-CN/architecture/README.md +2 -2
  196. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  197. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  198. package/templates-zh-CN/development/README.md +9 -9
  199. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  200. package/templates-zh-CN/development/external-context/service-template.md +1 -1
  201. package/templates-zh-CN/development/external-source-packs/README.md +2 -2
  202. package/templates-zh-CN/integrations/README.md +4 -4
  203. package/templates-zh-CN/integrations/api-contract.md +1 -1
  204. package/templates-zh-CN/integrations/event-contract.md +1 -1
  205. package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
  206. package/templates-zh-CN/integrations/webhook-contract.md +1 -1
  207. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  208. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  209. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  210. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  211. package/templates-zh-CN/modules/module_brief.md +47 -0
  212. package/templates-zh-CN/modules/module_plan.md +48 -0
  213. package/templates-zh-CN/modules/registry_view.md +9 -0
  214. package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
  215. package/templates-zh-CN/planning/INDEX.md +1 -0
  216. package/templates-zh-CN/planning/brief.md +26 -7
  217. package/templates-zh-CN/planning/module_brief.md +24 -2
  218. package/templates-zh-CN/planning/module_plan.md +35 -29
  219. package/templates-zh-CN/planning/module_session_prompt.md +15 -11
  220. package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
  221. package/templates-zh-CN/planning/review.md +1 -1
  222. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  223. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  224. package/templates-zh-CN/reference/docs-library-standard.md +27 -27
  225. package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
  226. package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
  227. package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
  228. package/templates-zh-CN/reference/pull-request-standard.md +1 -1
  229. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  230. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  231. package/templates-zh-CN/reference/review-routing-standard.md +3 -0
  232. package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
  233. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  234. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  235. package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
  236. package/templates-zh-CN/ssot/Module-Registry.md +5 -44
  237. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  238. package/templates-zh-CN/walkthrough/walkthrough-template.md +4 -4
@@ -1,11 +1,14 @@
1
- // @ts-nocheck
2
1
  import fs from "node:fs";
3
2
  import os from "node:os";
4
3
  import path from "node:path";
4
+ import crypto from "node:crypto";
5
5
  import { spawnSync } from "node:child_process";
6
- import { repoRoot, visualMapFile, normalizeTarget, toPosix, exists, existsInDocs, readFileSafe, readJsonSafe, readBundledTemplate, walkFiles, normalizeLocale, localizedTemplateSource, userPresetRootForHome, } from "./core-shared.mjs";
6
+ import { repoRoot, visualMapFile, normalizeTarget, toPosix, exists, existsInDocs, readFileSafe, readJsonSafe, readBundledTemplate, renderHarnessTemplate, walkFiles, normalizeLocale, localizedTemplateSource, userPresetRootForHome, } from "./core-shared.mjs";
7
7
  import { listBundledPresetIds, seedBundledPresets } from "./preset-registry.mjs";
8
- import { legacyCloseoutFile, legacyCompatMode, legacyLedgerFile, legacyModuleRoot, legacyPath, legacyPlanningRoot, legacyTaskRoot, legacyWalkthroughRoot, safeAdoptionCapability, v2HarnessRoot, } from "./harness-paths.mjs";
8
+ import { legacyCloseoutFile, legacyCompatMode, legacyLedgerFile, legacyModuleRoot, legacyPath, legacyPlanningRoot, legacyTaskRoot, legacyWalkthroughRoot, safeAdoptionCapability, assertRenderableHarnessManifest, renderHarnessManifest, v2HarnessRoot, } from "./harness-paths.mjs";
9
+ function errorMessage(error) {
10
+ return error instanceof Error ? error.message : String(error || "unknown error");
11
+ }
9
12
  export const capabilityDefinitions = {
10
13
  core: {
11
14
  description: "Planning loop and task execution records.",
@@ -15,7 +18,7 @@ export const capabilityDefinitions = {
15
18
  artifacts: [legacyPath(legacyPlanningRoot)],
16
19
  },
17
20
  "module-parallel": {
18
- description: "Module registry, module plans, session prompts, and worker handoff.",
21
+ description: "YAML-backed module registry, module briefs/plans, and global worker handoff prompt pack.",
19
22
  selectWhen: "Use only when the project has two or more independent modules that need parallel ownership.",
20
23
  default: false,
21
24
  dependencies: ["core"],
@@ -72,7 +75,7 @@ export function readCapabilityRegistry(target) {
72
75
  if (target.harness?.version === 2 && target.harness.manifest) {
73
76
  return {
74
77
  mode: "v2-manifest",
75
- path: target.harness.manifestPath,
78
+ path: target.harness.manifestPath || target.manifestPath || "",
76
79
  capabilities: (target.harness.manifest.capabilities || ["core"]).map((name) => ({
77
80
  name: normalizeCapabilityName(name),
78
81
  state: "configured",
@@ -96,18 +99,19 @@ export function readCapabilityRegistry(target) {
96
99
  let readError = null;
97
100
  const raw = readJsonSafe(registryPath, null, { onError: (error) => { readError = error; } });
98
101
  if (raw) {
99
- const locale = normalizeLocale(raw.locale);
102
+ const locale = normalizeLocale(String(raw.locale || ""));
100
103
  const capabilities = Array.isArray(raw.capabilities)
101
104
  ? raw.capabilities.map((entry) => typeof entry === "string"
102
105
  ? { name: normalizeCapabilityName(entry), state: "scaffolded" }
103
- : { name: normalizeCapabilityName(entry.name), state: entry.state || "scaffolded" })
106
+ : { name: normalizeCapabilityName(entry.name), state: String(entry.state || "scaffolded") })
104
107
  : [];
105
108
  return { mode: "declared-capability", path: registryPath, capabilities, raw, locale, errors: [] };
106
109
  }
107
- return { mode: "declared-capability", path: registryPath, capabilities: [], raw: null, errors: [readError?.message || "invalid .harness-capabilities.json"] };
110
+ return { mode: "declared-capability", path: registryPath, capabilities: [], locale: "en-US", raw: null, errors: [errorMessage(readError) || "invalid .harness-capabilities.json"] };
108
111
  }
109
112
  export function normalizeCapabilityName(name) {
110
- return capabilityAliases[name] || name;
113
+ const normalized = String(name || "");
114
+ return capabilityAliases[normalized] || normalized;
111
115
  }
112
116
  export function validateSourcePackageBoundary(targetInput = ".") {
113
117
  const root = path.resolve(targetInput || ".");
@@ -165,17 +169,17 @@ function validateDashboardAssetAssembly(root, manifestName, assetName, driftMess
165
169
  return trackedAsset === assembled ? [] : [driftMessage];
166
170
  }
167
171
  catch (error) {
168
- return [`could not validate dashboard asset assembly (${assetName}): ${error.message}`];
172
+ return [`could not validate dashboard asset assembly (${assetName}): ${errorMessage(error)}`];
169
173
  }
170
174
  }
171
175
  export function detectCapabilities(target) {
172
176
  const detected = new Set(["core"]);
173
177
  if (target.harness?.version === 2) {
174
- if (fs.existsSync(path.join(target.harness.modulesRoot, "Module-Registry.md")))
178
+ if (fs.existsSync(path.join(target.harness.modulesRoot || "", "Module-Registry.md")))
175
179
  detected.add("module-parallel");
176
- if (fs.existsSync(path.join(target.harness.governanceRoot, "standards/adversarial-review-standard.md")))
180
+ if (fs.existsSync(path.join(target.harness.governanceRoot || "", "standards/adversarial-review-standard.md")))
177
181
  detected.add("adversarial-review");
178
- if (fs.existsSync(path.join(target.harness.tasksRoot, "_task-template/long-running-task-contract.md")))
182
+ if (fs.existsSync(path.join(target.harness.tasksRoot || "", "_task-template/long-running-task-contract.md")))
179
183
  detected.add("long-running-task");
180
184
  return [...detected];
181
185
  }
@@ -224,7 +228,7 @@ export function buildInstallReport({ target, locale, capabilities, changes, dryR
224
228
  function packageVersion() {
225
229
  try {
226
230
  const pkg = readJsonSafe(path.join(repoRoot, "package.json"), {});
227
- return pkg.version || "";
231
+ return String(pkg.version || "");
228
232
  }
229
233
  catch {
230
234
  return "";
@@ -319,7 +323,7 @@ export function installUserSkill({ agent = "codex", home = "", dryRun = false, f
319
323
  function readInstalledVersion(targetRoot) {
320
324
  try {
321
325
  const pkg = readJsonSafe(path.join(targetRoot, "package.json"), {});
322
- return pkg.version || "";
326
+ return String(pkg.version || "");
323
327
  }
324
328
  catch {
325
329
  return "";
@@ -422,13 +426,13 @@ function capabilityArtifactsForTarget(target, capabilityName) {
422
426
  const paths = target.harness;
423
427
  switch (capabilityName) {
424
428
  case "core":
425
- return [relative(paths.planningRoot)];
429
+ return [relative(paths.planningRoot || "")];
426
430
  case "module-parallel":
427
- return [relative(path.join(paths.modulesRoot, "Module-Registry.md")), relative(paths.modulesRoot)];
431
+ return [relative(path.join(paths.modulesRoot || "", "Module-Registry.md")), relative(paths.modulesRoot || "")];
428
432
  case "subagent-worker":
429
- return [relative(paths.modulesRoot)];
433
+ return [relative(paths.modulesRoot || "")];
430
434
  case "adversarial-review":
431
- return [relative(paths.tasksRoot)];
435
+ return [relative(paths.tasksRoot || "")];
432
436
  case "long-running-task":
433
437
  return [];
434
438
  default:
@@ -436,11 +440,11 @@ function capabilityArtifactsForTarget(target, capabilityName) {
436
440
  }
437
441
  }
438
442
  export function plannedInitFiles(capabilities = ["core"], { locale = "en-US", paths = null } = {}) {
439
- const root = paths ? toPosix(path.relative(paths.projectRoot, paths.harnessRoot)) : v2HarnessRoot;
440
- const modulesRoot = paths ? toPosix(path.relative(paths.projectRoot, paths.modulesRoot)) : `${root}/planning/modules`;
441
- const regressionRoot = paths ? toPosix(path.relative(paths.projectRoot, paths.regressionRoot)) : `${root}/governance/regression`;
443
+ const root = paths ? toPosix(path.relative(paths.projectRoot || "", paths.harnessRoot || "")) : v2HarnessRoot;
444
+ const modulesRoot = paths ? toPosix(path.relative(paths.projectRoot || "", paths.modulesRoot || "")) : `${root}/planning/modules`;
445
+ const regressionRoot = paths ? toPosix(path.relative(paths.projectRoot || "", paths.regressionRoot || "")) : `${root}/governance/regression`;
442
446
  const contextRoot = `${root}/context`;
443
- const governanceRoot = paths ? toPosix(path.relative(paths.projectRoot, paths.governanceRoot)) : `${root}/governance`;
447
+ const governanceRoot = paths ? toPosix(path.relative(paths.projectRoot || "", paths.governanceRoot || "")) : `${root}/governance`;
444
448
  const files = [
445
449
  ["AGENTS.md", "templates/AGENTS.md.template"],
446
450
  ["CLAUDE.md", "templates/CLAUDE.md.template"],
@@ -470,16 +474,16 @@ export function plannedInitFiles(capabilities = ["core"], { locale = "en-US", pa
470
474
  [`${governanceRoot}/standards/external-source-intake-standard.md`, "templates/reference/external-source-intake-standard.md"],
471
475
  ];
472
476
  if (capabilities.includes("module-parallel")) {
473
- files.push([`${modulesRoot}/Module-Registry.md`, "templates/ssot/Module-Registry.md"]);
474
- files.push([`${modulesRoot}/Session-Prompt-Pack.md`, "templates/planning/module_session_prompt.md"]);
477
+ files.push([`${modulesRoot}/Module-Registry.md`, "templates/modules/registry_view.md"]);
478
+ files.push([`${modulesRoot}/Session-Prompt-Pack.md`, "templates/modules/session_prompt_pack.md"]);
475
479
  }
476
480
  return files.map(([destination, source]) => [destination, localizedTemplateSource(source, locale)]);
477
481
  }
478
482
  function plannedInitDirectories(capabilities = ["core"], { paths = null } = {}) {
479
- const planningRoot = paths ? toPosix(path.relative(paths.projectRoot, paths.planningRoot)) : `${v2HarnessRoot}/planning`;
480
- const tasksRoot = paths ? toPosix(path.relative(paths.projectRoot, paths.tasksRoot)) : `${v2HarnessRoot}/planning/tasks`;
481
- const modulesRoot = paths ? toPosix(path.relative(paths.projectRoot, paths.modulesRoot)) : `${v2HarnessRoot}/planning/modules`;
482
- const generatedRoot = paths ? toPosix(path.relative(paths.projectRoot, paths.generatedRoot)) : `${v2HarnessRoot}/governance/generated`;
483
+ const planningRoot = paths ? toPosix(path.relative(paths.projectRoot || "", paths.planningRoot || "")) : `${v2HarnessRoot}/planning`;
484
+ const tasksRoot = paths ? toPosix(path.relative(paths.projectRoot || "", paths.tasksRoot || "")) : `${v2HarnessRoot}/planning/tasks`;
485
+ const modulesRoot = paths ? toPosix(path.relative(paths.projectRoot || "", paths.modulesRoot || "")) : `${v2HarnessRoot}/planning/modules`;
486
+ const generatedRoot = paths ? toPosix(path.relative(paths.projectRoot || "", paths.generatedRoot || "")) : `${v2HarnessRoot}/governance/generated`;
483
487
  const directories = [
484
488
  planningRoot,
485
489
  tasksRoot,
@@ -505,6 +509,7 @@ export function writeInitFiles(targetInput, capabilities, { dryRun = true, local
505
509
  }
506
510
  const planned = plannedInitFiles(normalizedCapabilities, { locale: normalizedLocale });
507
511
  const changes = [];
512
+ const projectionEntries = [];
508
513
  const manifestDestination = `${v2HarnessRoot}/harness.yaml`;
509
514
  const manifestPath = path.join(target.projectRoot, manifestDestination);
510
515
  const manifestExists = fs.existsSync(manifestPath);
@@ -528,9 +533,13 @@ export function writeInitFiles(targetInput, capabilities, { dryRun = true, local
528
533
  changes.push({ destination, source, action: existsAlready ? "skip-existing" : dryRun ? "would-create" : "create" });
529
534
  if (!dryRun && !existsAlready) {
530
535
  fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
531
- fs.copyFileSync(sourcePath, destinationPath);
536
+ const rendered = renderInstallTemplate(source, target);
537
+ fs.writeFileSync(destinationPath, rendered);
538
+ projectionEntries.push(templateProjectionEntry({ destination, source, target, rendered }));
532
539
  }
533
540
  }
541
+ if (!dryRun)
542
+ writeTemplateProjectionManifest(target, projectionEntries);
534
543
  if (addNpmScripts) {
535
544
  changes.push(...writeNpmScripts(target, { dryRun }));
536
545
  }
@@ -538,26 +547,6 @@ export function writeInitFiles(targetInput, capabilities, { dryRun = true, local
538
547
  const report = buildInstallReport({ target, locale: normalizedLocale, capabilities: normalizedCapabilities, changes, dryRun, operation: "init" });
539
548
  return { target, capabilities: normalizedCapabilities, locale: normalizedLocale, changes, presetSeed, nextCommands: initNextCommands(), report };
540
549
  }
541
- function renderHarnessManifest({ locale, capabilities, structure = null }) {
542
- const manifestStructure = structure || {
543
- harnessRoot: v2HarnessRoot,
544
- planningRoot: `${v2HarnessRoot}/planning`,
545
- tasksRoot: `${v2HarnessRoot}/planning/tasks`,
546
- modulesRoot: `${v2HarnessRoot}/planning/modules`,
547
- externalRoot: `${v2HarnessRoot}/planning/external`,
548
- governanceRoot: `${v2HarnessRoot}/governance`,
549
- generatedRoot: `${v2HarnessRoot}/governance/generated`,
550
- };
551
- return [
552
- "version: 2",
553
- `locale: ${locale}`,
554
- "capabilities:",
555
- ...capabilities.map((capability) => ` - ${capability}`),
556
- "structure:",
557
- ...Object.entries(manifestStructure).map(([key, value]) => ` ${key}: ${value}`),
558
- "",
559
- ].join("\n");
560
- }
561
550
  function initNextCommands() {
562
551
  return [
563
552
  "npx --yes coding-agent-harness dev .",
@@ -591,6 +580,102 @@ function writeNpmScripts(target, { dryRun = true } = {}) {
591
580
  }
592
581
  return [{ destination: "package.json", source: "npm-scripts", action: dryRun ? "would-update-scripts" : "update-scripts" }, ...scriptChanges];
593
582
  }
583
+ function renderInstallTemplate(source, target) {
584
+ return renderHarnessTemplate(readBundledTemplate(source), { paths: targetPathContext(target) });
585
+ }
586
+ function targetPathContext(target) {
587
+ const paths = target.harness || {};
588
+ const projectRoot = String(paths.projectRoot || target.projectRoot);
589
+ const fields = [
590
+ "harnessRoot",
591
+ "planningRoot",
592
+ "tasksRoot",
593
+ "modulesRoot",
594
+ "externalRoot",
595
+ "governanceRoot",
596
+ "generatedRoot",
597
+ "regressionRoot",
598
+ "ledgerPath",
599
+ "closeoutIndexPath",
600
+ ];
601
+ return Object.fromEntries(fields.map((field) => {
602
+ const value = String(paths[field] || "");
603
+ const rendered = value && path.isAbsolute(value) ? toPosix(path.relative(projectRoot, value)) : toPosix(String(value || ""));
604
+ return [field, rendered];
605
+ }));
606
+ }
607
+ function templateProjectionManifestPath(target) {
608
+ const effectiveTarget = target.harness?.version === 2 || !fs.existsSync(path.join(target.projectRoot, v2HarnessRoot, "harness.yaml"))
609
+ ? target
610
+ : normalizeTarget(target.projectRoot);
611
+ const generatedRoot = effectiveTarget.harness?.generatedRoot || path.join(effectiveTarget.projectRoot, v2HarnessRoot, "governance/generated");
612
+ return path.join(generatedRoot, "Template-Projections.json");
613
+ }
614
+ function readTemplateProjectionManifest(target) {
615
+ const currentPath = templateProjectionManifestPath(target);
616
+ if (fs.existsSync(currentPath))
617
+ return readJsonSafe(currentPath, { schemaVersion: "template-projections/v1", entries: [] });
618
+ const fallback = walkFiles(target.projectRoot)
619
+ .find((filePath) => path.basename(filePath) === "Template-Projections.json");
620
+ return fallback ? readJsonSafe(fallback, { schemaVersion: "template-projections/v1", entries: [] }) : { schemaVersion: "template-projections/v1", entries: [] };
621
+ }
622
+ function writeTemplateProjectionManifest(target, entries) {
623
+ if (!entries.length)
624
+ return;
625
+ const manifestPath = templateProjectionManifestPath(target);
626
+ const current = readTemplateProjectionManifest(target);
627
+ const byDestination = new Map((current.entries || []).map((entry) => [entry.destination, entry]));
628
+ for (const entry of entries)
629
+ byDestination.set(entry.destination, entry);
630
+ const next = {
631
+ schemaVersion: "template-projections/v1",
632
+ generatedAt: new Date().toISOString(),
633
+ entries: [...byDestination.values()].sort((a, b) => a.destination.localeCompare(b.destination)),
634
+ };
635
+ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
636
+ fs.writeFileSync(manifestPath, `${JSON.stringify(next, null, 2)}\n`);
637
+ }
638
+ function templateProjectionEntry({ destination, source, target, rendered }) {
639
+ return {
640
+ destination,
641
+ source,
642
+ ownership: "package-template-pristine",
643
+ renderedSha256: sha256(rendered),
644
+ sourceSha256: sha256(readBundledTemplate(source)),
645
+ paths: targetPathContext(target),
646
+ updatedAt: new Date().toISOString(),
647
+ };
648
+ }
649
+ function pathFindings(content, target) {
650
+ const findings = [];
651
+ const text = String(content || "");
652
+ if (text.includes("{{paths."))
653
+ findings.push("unresolved-path-token");
654
+ const defaultRootPattern = /\bcoding-agent-harness\/(?:planning|governance|context)\//g;
655
+ if (target.harness?.version === 2 && targetPathContext(target).harnessRoot !== v2HarnessRoot && defaultRootPattern.test(text)) {
656
+ findings.push("default-root-literal");
657
+ }
658
+ if (/docs\/09-PLANNING|docs\/10-WALKTHROUGH|docs\/Harness-Ledger\.md/.test(text))
659
+ findings.push("legacy-path-literal");
660
+ return [...new Set(findings)];
661
+ }
662
+ function scanProjectAuthoredPathFindings(target, plannedDestinations) {
663
+ const root = target.harness?.harnessRoot || target.docsRoot;
664
+ if (!root || !fs.existsSync(root))
665
+ return [];
666
+ return walkFiles(root)
667
+ .map((filePath) => toPosix(path.relative(target.projectRoot, filePath)))
668
+ .filter((relative) => !plannedDestinations.has(relative))
669
+ .filter((relative) => !relative.includes("/governance/archive/") && !relative.includes("/planning/tasks/") && !relative.includes("/governance/generated/"))
670
+ .map((relative) => {
671
+ const findings = pathFindings(readFileSafe(path.join(target.projectRoot, relative)), target);
672
+ return findings.length ? { destination: relative, ownership: "project-authored", action: "report-only", pathFindings: findings } : null;
673
+ })
674
+ .filter((change) => Boolean(change));
675
+ }
676
+ function sha256(content) {
677
+ return crypto.createHash("sha256").update(String(content)).digest("hex");
678
+ }
594
679
  export function addCapability(targetInput, capabilityName, { dryRun = true, locale = "" } = {}) {
595
680
  const target = normalizeTarget(targetInput);
596
681
  const normalizedCapability = normalizeCapabilityName(capabilityName);
@@ -608,6 +693,7 @@ export function addCapability(targetInput, capabilityName, { dryRun = true, loca
608
693
  const nextCapabilities = [...capabilityMap.keys()];
609
694
  const scaffold = plannedInitFiles([...capabilityMap.keys()], { locale: normalizedLocale, paths: target.harness?.version === 2 ? target.harness : null });
610
695
  const changes = [];
696
+ const projectionEntries = [];
611
697
  for (const directory of plannedInitDirectories(nextCapabilities, { paths: target.harness?.version === 2 ? target.harness : null })) {
612
698
  const destinationPath = path.join(target.projectRoot, directory);
613
699
  const existsAlready = fs.existsSync(destinationPath);
@@ -622,15 +708,19 @@ export function addCapability(targetInput, capabilityName, { dryRun = true, loca
622
708
  changes.push({ destination, source, action: existsAlready ? "skip-existing" : dryRun ? "would-create" : "create" });
623
709
  if (!dryRun && !existsAlready) {
624
710
  fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
625
- fs.copyFileSync(sourcePath, destinationPath);
711
+ const rendered = renderInstallTemplate(source, target);
712
+ fs.writeFileSync(destinationPath, rendered);
713
+ projectionEntries.push(templateProjectionEntry({ destination, source, target, rendered }));
626
714
  }
627
715
  }
628
716
  if (!dryRun) {
629
- const manifestPath = target.harness.version === 2
717
+ const manifestPath = target.harness?.version === 2 && target.manifestPath
630
718
  ? target.manifestPath
631
719
  : path.join(target.projectRoot, v2HarnessRoot, "harness.yaml");
720
+ assertRenderableHarnessManifest(target.harness?.manifest);
632
721
  fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
633
- fs.writeFileSync(manifestPath, renderHarnessManifest({ locale: normalizedLocale, capabilities: nextCapabilities, structure: target.harness.manifest?.structure }));
722
+ fs.writeFileSync(manifestPath, renderHarnessManifest({ locale: normalizedLocale, capabilities: nextCapabilities, structure: target.harness?.manifest?.structure, modules: target.harness?.manifest?.modules || null }));
723
+ writeTemplateProjectionManifest(target, projectionEntries);
634
724
  }
635
725
  const report = buildInstallReport({ target, locale: normalizedLocale, capabilities: [...capabilityMap.keys()], changes, dryRun, operation: "add-capability" });
636
726
  return {
@@ -641,3 +731,89 @@ export function addCapability(targetInput, capabilityName, { dryRun = true, loca
641
731
  report,
642
732
  };
643
733
  }
734
+ export function auditTemplateProjections(targetInput = ".") {
735
+ const target = normalizeTarget(targetInput);
736
+ const registry = readCapabilityRegistry(target);
737
+ const capabilities = registry.capabilities.map((capability) => capability.name);
738
+ const planned = plannedInitFiles(capabilities, { locale: registry.locale, paths: target.harness?.version === 2 ? target.harness : null });
739
+ const manifest = readTemplateProjectionManifest(target);
740
+ const entriesByDestination = new Map((manifest.entries || []).map((entry) => [entry.destination, entry]));
741
+ const plannedDestinations = new Set(planned.map(([destination]) => destination));
742
+ const projections = planned.map(([destination, source]) => {
743
+ const destinationPath = path.join(target.projectRoot, destination);
744
+ const rendered = renderInstallTemplate(source, target);
745
+ const renderedSha256 = sha256(rendered);
746
+ const existsAlready = fs.existsSync(destinationPath);
747
+ const current = existsAlready ? fs.readFileSync(destinationPath, "utf8") : "";
748
+ const currentSha256 = existsAlready ? sha256(current) : "";
749
+ const recorded = entriesByDestination.get(destination);
750
+ const ownership = !existsAlready
751
+ ? "missing"
752
+ : currentSha256 === renderedSha256
753
+ ? "package-template-pristine"
754
+ : recorded && currentSha256 === recorded.renderedSha256
755
+ ? "package-template-pristine"
756
+ : recorded
757
+ ? "package-template-modified"
758
+ : "project-authored";
759
+ const action = !existsAlready
760
+ ? "would-create"
761
+ : currentSha256 === renderedSha256
762
+ ? "no-op"
763
+ : ownership === "package-template-pristine"
764
+ ? "would-refresh"
765
+ : "report-only";
766
+ return {
767
+ destination,
768
+ source,
769
+ exists: existsAlready,
770
+ ownership,
771
+ action,
772
+ currentSha256: currentSha256 || null,
773
+ expectedSha256: renderedSha256,
774
+ recordedSha256: recorded?.renderedSha256 || null,
775
+ pathFindings: pathFindings(current, target),
776
+ };
777
+ });
778
+ const authoredFindings = scanProjectAuthoredPathFindings(target, plannedDestinations);
779
+ return {
780
+ operation: "template-projection-audit",
781
+ target: target.projectRoot,
782
+ manifestPath: templateProjectionManifestPath(target),
783
+ projections,
784
+ projectAuthoredFindings: authoredFindings,
785
+ summary: {
786
+ total: projections.length,
787
+ missing: projections.filter((item) => item.ownership === "missing").length,
788
+ refreshable: projections.filter((item) => item.action === "would-refresh").length,
789
+ reportOnly: projections.filter((item) => item.action === "report-only").length + authoredFindings.length,
790
+ },
791
+ };
792
+ }
793
+ export function refreshTemplateProjections(targetInput = ".", { apply = false } = {}) {
794
+ const target = normalizeTarget(targetInput);
795
+ const audit = auditTemplateProjections(targetInput);
796
+ const changes = [];
797
+ const entries = [];
798
+ for (const item of audit.projections) {
799
+ if (!["would-create", "would-refresh"].includes(item.action))
800
+ continue;
801
+ const rendered = renderInstallTemplate(item.source, target);
802
+ changes.push({ destination: item.destination, source: item.source, action: apply ? item.action.replace("would-", "") : item.action });
803
+ entries.push(templateProjectionEntry({ destination: item.destination, source: item.source, target, rendered }));
804
+ if (!apply)
805
+ continue;
806
+ const destinationPath = path.join(target.projectRoot, item.destination);
807
+ fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
808
+ fs.writeFileSync(destinationPath, rendered);
809
+ }
810
+ if (apply)
811
+ writeTemplateProjectionManifest(target, entries);
812
+ return {
813
+ operation: "template-projection-refresh",
814
+ dryRun: !apply,
815
+ target: target.projectRoot,
816
+ changes,
817
+ audit,
818
+ };
819
+ }
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  // Module-parallel private harness checks stay behavior-first until legacy module context types are modeled.
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
@@ -197,12 +196,8 @@ export function checkModuleParallelStructure(context) {
197
196
  fail(`${moduleRegistryPath} row ${key} branch must use codex/ prefix: ${branch}`);
198
197
  }
199
198
  const block = modulePromptBlock(promptPack, key);
200
- if (!block) {
201
- if (!exists(legacyPath(legacyModuleRoot, key, "session_prompt.md"))) {
202
- fail(`missing module session prompt for ${key}`);
203
- }
199
+ if (!block)
204
200
  continue;
205
- }
206
201
  for (const term of [
207
202
  "Current Step",
208
203
  branchName,
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
3
  import { spawnSync } from "node:child_process";
@@ -6,9 +5,9 @@ import { repoRoot, bundledCheckScript, visualMapFile, legacyVisualRoadmapFile, a
6
5
  import { tableAfterHeading, getColumn, getColumnAny, splitList, firstColumn, contentHasAny, } from "./markdown-utils.mjs";
7
6
  import { validateCapabilities } from "./capability-registry.mjs";
8
7
  import { readPresetPackage } from "./preset-registry.mjs";
9
- import { validateTaskPresetAuditSnapshot } from "./preset-audit-contracts.mjs";
10
- import { validatePresetResourcesForTask } from "./preset-resource-contracts.mjs";
11
- import { collectTasks, listTaskPlanPaths, parseTaskBudget, readVisualMapContractFile, parsePhases } from "./task-scanner.mjs";
8
+ import { validateRegularTaskPresetContract } from "./task-preset-contract-drift.mjs";
9
+ import { parseTaskBudget } from "./task-metadata.mjs";
10
+ import { createScannerTaskRepository, parsePhases, readVisualMapContractFile, taskPlanPathFromRecord } from "./task-repository.mjs";
12
11
  import { normalizeReviewBoolean, reviewFindingColumns } from "./task-review-model.mjs";
13
12
  import { allowedPhaseActors, allowedPhaseKinds } from "./phase-kind.mjs";
14
13
  import { validateTaskCompletionConsistency } from "./task-completion-consistency.mjs";
@@ -19,6 +18,9 @@ import { summarizeGitState } from "./git-status-summary.mjs";
19
18
  import { buildStatusData } from "./status-builder.mjs";
20
19
  import { legacyCloseoutFile, legacyCompatMode, legacyLedgerFile, legacyPath, legacyPlanningRoot, legacyWalkthroughRoot, safeAdoptionCapability, } from "./harness-paths.mjs";
21
20
  export { renderDashboard } from "./status-dashboard-renderer.mjs";
21
+ function errorMessage(error) {
22
+ return error instanceof Error ? error.message : String(error);
23
+ }
22
24
  export function runCompatibilityCheck(target) {
23
25
  const checkTarget = target.docsOnly ? target.projectRoot : target.input;
24
26
  const result = spawnSync(process.execPath, [bundledCheckScript, checkTarget], {
@@ -125,10 +127,12 @@ export function validateReviewSchema(target, { strict = true } = {}) {
125
127
  }
126
128
  return { failures, warnings };
127
129
  }
128
- export function validateVisualMaps(target, { taskPlanPaths } = {}) {
130
+ export function validateVisualMaps(target, { tasks } = {}) {
129
131
  const failures = [];
130
132
  const warnings = [];
131
- for (const taskPlanPath of taskPlanPaths || listTaskPlanPaths(target)) {
133
+ const taskRecords = tasks || createScannerTaskRepository(target).list();
134
+ for (const task of taskRecords) {
135
+ const taskPlanPath = taskPlanPathFromRecord(target, task);
132
136
  const taskDir = path.dirname(taskPlanPath);
133
137
  const visualMapPath = path.join(taskDir, visualMapFile);
134
138
  const legacyPath = path.join(taskDir, legacyVisualRoadmapFile);
@@ -180,14 +184,14 @@ export function validateVisualMaps(target, { taskPlanPaths } = {}) {
180
184
  return { failures, warnings };
181
185
  }
182
186
  export function validateTaskPresetContracts(target, { tasks } = {}) {
183
- const failures = [];
187
+ const failures = [], warnings = [];
184
188
  const allowedMigrationLevels = new Set([
185
189
  "migration-baseline",
186
190
  "migration-current-cutover",
187
191
  "migration-full-cutover",
188
192
  "migration-deferred",
189
193
  ]);
190
- for (const task of tasks || collectTasks(target)) {
194
+ for (const task of tasks || createScannerTaskRepository(target).list()) {
191
195
  if (!task.taskPreset || task.taskPreset === "none")
192
196
  continue;
193
197
  let presetPackage = null;
@@ -195,26 +199,14 @@ export function validateTaskPresetContracts(target, { tasks } = {}) {
195
199
  presetPackage = readPresetPackage(task.taskPreset, { targetInput: target.projectRoot });
196
200
  }
197
201
  catch (error) {
198
- failures.push(`${task.path} unsupported Task Preset: ${task.taskPreset} (${error.message})`);
202
+ failures.push(`${task.path} unsupported Task Preset: ${task.taskPreset} (${errorMessage(error)})`);
199
203
  continue;
200
204
  }
201
- if (presetPackage?.task?.kind && task.taskKind !== presetPackage.task.kind) {
202
- failures.push(`${task.path} ${task.taskPreset} preset Task Kind mismatch: expected ${presetPackage.task.kind}, got ${task.taskKind || "(missing)"}`);
203
- }
204
- if (String(task.presetVersion || "") !== String(presetPackage.version)) {
205
- failures.push(`${task.path} ${task.taskPreset} preset missing Preset Version ${presetPackage.version}`);
206
- }
207
- if (task.taskPreset !== "lesson-sedimentation" && (presetPackage.evidence?.bundleDir || presetPackage.audit?.evidenceFiles?.length || Object.keys(presetPackage.evidence?.files || {}).length)) {
208
- if (!task.evidenceBundle)
209
- failures.push(`${task.path} ${task.taskPreset} preset missing Evidence Bundle`);
210
- else if (!fs.existsSync(path.join(target.projectRoot, String(task.evidenceBundle).replace(/^TARGET:/, "").replace(/^\/+/, "")))) {
211
- failures.push(`${task.path} ${task.taskPreset} preset Evidence Bundle missing: ${task.evidenceBundle}`);
212
- }
213
- }
214
205
  if (task.taskPreset !== "lesson-sedimentation") {
215
- failures.push(...validateTaskPresetAuditSnapshot(target, task, presetPackage));
206
+ const regularPreset = validateRegularTaskPresetContract(target, task, presetPackage);
207
+ failures.push(...regularPreset.failures);
208
+ warnings.push(...regularPreset.warnings);
216
209
  }
217
- failures.push(...validatePresetResourcesForTask(target, task, presetPackage));
218
210
  if (task.taskPreset === "lesson-sedimentation") {
219
211
  if (!["standard", "complex"].includes(task.budget))
220
212
  failures.push(`${task.path} lesson-sedimentation preset requires Selected budget: standard or complex`);
@@ -222,9 +214,8 @@ export function validateTaskPresetContracts(target, { tasks } = {}) {
222
214
  failures.push(`${task.path} lesson-sedimentation preset missing task plan`);
223
215
  continue;
224
216
  }
225
- if (task.taskPreset !== "legacy-migration") {
217
+ if (task.taskPreset !== "legacy-migration")
226
218
  continue;
227
- }
228
219
  if (task.budget !== "complex")
229
220
  failures.push(`${task.path} legacy-migration preset requires Selected budget: complex`);
230
221
  if (!allowedMigrationLevels.has(task.migrationTargetLevel)) {
@@ -241,34 +232,34 @@ export function validateTaskPresetContracts(target, { tasks } = {}) {
241
232
  failures.push(`${task.path} legacy-migration preset Evidence Bundle missing session.json`);
242
233
  }
243
234
  if (achievedLevel === "migration-full-cutover") {
244
- const snapshot = task.migrationSnapshot || {};
235
+ const snapshot = task.migrationSnapshot;
245
236
  const blockers = [];
246
- if (!snapshot.sessionPresent)
237
+ if (!snapshot?.sessionPresent)
247
238
  blockers.push("missing session evidence");
248
- if (snapshot.sessionResult !== "complete")
249
- blockers.push(`session result is ${snapshot.sessionResult || "(missing)"}`);
250
- if (snapshot.strictDeferred)
239
+ if (snapshot?.sessionResult !== "complete")
240
+ blockers.push(`session result is ${snapshot?.sessionResult || "(missing)"}`);
241
+ if (snapshot?.strictDeferred)
251
242
  blockers.push("strictDeferred is present");
252
- if (snapshot.strictStatus !== "pass")
253
- blockers.push(`strict status is ${snapshot.strictStatus || "(missing)"}`);
243
+ if (snapshot?.strictStatus !== "pass")
244
+ blockers.push(`strict status is ${snapshot?.strictStatus || "(missing)"}`);
254
245
  for (const [field, value] of [
255
- ["warnings", snapshot.warnings],
256
- ["taskActions", snapshot.taskActions],
257
- ["reviewSchemaGaps", snapshot.reviewSchemaGaps],
258
- ["legacyReferenceGaps", snapshot.legacyReferenceGaps],
259
- ["legacyResiduals", snapshot.legacyResiduals],
246
+ ["warnings", snapshot?.warnings],
247
+ ["taskActions", snapshot?.taskActions],
248
+ ["reviewSchemaGaps", snapshot?.reviewSchemaGaps],
249
+ ["legacyReferenceGaps", snapshot?.legacyReferenceGaps],
250
+ ["legacyResiduals", snapshot?.legacyResiduals],
260
251
  ]) {
261
252
  if (Number(value || 0) !== 0)
262
253
  blockers.push(`${field}=${value}`);
263
254
  }
264
- if (snapshot.fullCutoverEligible !== true)
255
+ if (snapshot?.fullCutoverEligible !== true)
265
256
  blockers.push("fullCutoverEligible is not true");
266
257
  if (blockers.length) {
267
258
  failures.push(`${task.path} migration-full-cutover is not proven: ${blockers.join("; ")}`);
268
259
  }
269
260
  }
270
261
  }
271
- return { failures, warnings: [] };
262
+ return { failures, warnings };
272
263
  }
273
264
  export function validateContextDocs(target, { strict = true } = {}) {
274
265
  const failures = [];
@@ -351,12 +342,11 @@ export function buildStatus(targetInput, options = {}) {
351
342
  const shouldRunLegacy = target.harness?.version !== 2 && !options.skipLegacyCheck && (capabilityState.registry.mode === legacyCompatMode || safeAdoptionMode);
352
343
  const legacy = shouldRunLegacy ? runCompatibilityCheck(target) : { status: "skipped", code: 0, stdout: "", stderr: "" };
353
344
  const contractStrict = Boolean(options.strict) || (capabilityState.registry.mode !== legacyCompatMode && !safeAdoptionMode);
354
- const taskPlanPaths = listTaskPlanPaths(target);
355
345
  const closeoutContent = target.harness?.version === 2 ? "" : readFileSafe(path.join(target.projectRoot, legacyPath(legacyCloseoutFile)));
356
- const tasks = collectTasks(target, { requireGeneratedScaffoldProvenance: contractStrict, taskPlanPaths, closeoutContent });
346
+ const tasks = createScannerTaskRepository(target, { requireGeneratedScaffoldProvenance: contractStrict, closeoutContent }).list();
357
347
  const reviews = validateReviewSchema(target, { strict: contractStrict });
358
- const visualMaps = validateVisualMaps(target, { taskPlanPaths });
359
- const planContracts = validatePlanContracts(target, { strict: contractStrict, taskPlanPaths });
348
+ const visualMaps = validateVisualMaps(target, { tasks });
349
+ const planContracts = validatePlanContracts(target, { strict: contractStrict, tasks });
360
350
  const presetContracts = validateTaskPresetContracts(target, { tasks });
361
351
  const contextDocs = validateContextDocs(target, { strict: contractStrict });
362
352
  const governanceBoundaries = validateGovernanceTableBoundaries(target);
@@ -379,10 +369,13 @@ export function buildStatus(targetInput, options = {}) {
379
369
  const briefMissing = tasks.length - briefReady;
380
370
  for (const task of tasks) {
381
371
  for (const issue of task.materialIssues || []) {
382
- if (!String(issue.code || "").startsWith("missing-task-audit") && !String(issue.code || "").startsWith("legacy-"))
372
+ const forceFailure = Boolean(issue.enforceFailure);
373
+ if (!String(issue.code || "").startsWith("missing-task-audit") &&
374
+ !String(issue.code || "").startsWith("legacy-") &&
375
+ issue.code !== "unedited-template-material")
383
376
  continue;
384
377
  const message = `${String(issue.sourcePath || task.path).replace(/^TARGET:/, "")} ${issue.message}`;
385
- if (contractStrict || options.strictLegacy)
378
+ if (forceFailure || contractStrict || options.strictLegacy)
386
379
  failures.push(message);
387
380
  else
388
381
  warnings.push(`adoption-needed: ${message}`);