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,15 +1,18 @@
1
- // @ts-nocheck
2
1
  // Governance sync spans dynamic target metadata and Git porcelain until the governance domain model PR.
3
2
  import fs from "node:fs";
4
3
  import os from "node:os";
5
4
  import path from "node:path";
6
5
  import crypto from "node:crypto";
7
6
  import { spawnSync } from "node:child_process";
8
- import { readBundledTemplate, readFileSafe, readJsonSafe, repoRoot, todayDate, toPosix, visualMapFile } from "./core-shared.mjs";
7
+ import { readBundledTemplate, readFileSafe, readJsonSafe, repoRoot, todayDate, toPosix } from "./core-shared.mjs";
9
8
  import { collectTasks } from "./task-scanner.mjs";
10
9
  import { appendMarkdownTableRow, firstColumn, fitMarkdownTableRow, splitMarkdownRow, upsertMarkdownTableRow } from "./markdown-utils.mjs";
11
10
  import { resolveHarnessPaths } from "./harness-paths.mjs";
11
+ import { moduleRegistryViewPath, renderModuleRegistryView } from "./module-registry.mjs";
12
12
  export class GovernanceSyncError extends Error {
13
+ code;
14
+ details;
15
+ recovery;
13
16
  constructor(message, { code = "governance-sync-failed", details = {}, recovery = [] } = {}) {
14
17
  super(message);
15
18
  this.name = "GovernanceSyncError";
@@ -18,7 +21,7 @@ export class GovernanceSyncError extends Error {
18
21
  this.recovery = recovery;
19
22
  }
20
23
  }
21
- export function beginGovernanceSync(target, { operation = "governance-sync", dryRun = false, allowDirtyWorktree = false, allowedRelativePaths = [] } = {}) {
24
+ export function beginGovernanceSync(target, { operation = "governance-sync", dryRun = false, allowDirtyWorktree = false, allowedRelativePaths = [], allowDirtyWriteScope = false } = {}) {
22
25
  if (dryRun)
23
26
  return { target, dryRun, operation, git: inspectGit(target.projectRoot), lockPath: "", active: false };
24
27
  const lockPath = path.join(target.projectRoot, ".harness/locks/governance-sync.lock");
@@ -45,7 +48,7 @@ export function beginGovernanceSync(target, { operation = "governance-sync", dry
45
48
  }
46
49
  if (gitState.entries.length > 0 && allowDirtyWorktree) {
47
50
  try {
48
- assertDirtyCompatibleWithWriteScope(gitState.entries, allowed);
51
+ assertDirtyCompatibleWithWriteScope(gitState.entries, allowed, { allowDirtyWriteScope });
49
52
  }
50
53
  catch (error) {
51
54
  releaseGovernanceSync({ lockPath, active: true });
@@ -79,7 +82,7 @@ function acquireGovernanceSyncLock(lockPath, target, { operation }) {
79
82
  catch (error) {
80
83
  if (fd !== null)
81
84
  fs.closeSync(fd);
82
- if (error?.code === "EEXIST" && attempt === 0 && removeStaleGovernanceSyncLock(lockPath))
85
+ if (isNodeError(error) && error.code === "EEXIST" && attempt === 0 && removeStaleGovernanceSyncLock(lockPath))
83
86
  continue;
84
87
  throw governanceLockExistsError(lockPath, error);
85
88
  }
@@ -92,14 +95,15 @@ function removeStaleGovernanceSyncLock(lockPath) {
92
95
  return false;
93
96
  if (lock.host !== governanceLockHost())
94
97
  return false;
95
- if (!Number.isInteger(lock?.pid) || lock.pid <= 0)
98
+ const pid = lock.pid;
99
+ if (!Number.isInteger(pid) || !pid || pid <= 0)
96
100
  return false;
97
101
  try {
98
- process.kill(lock.pid, 0);
102
+ process.kill(pid, 0);
99
103
  return false;
100
104
  }
101
105
  catch (error) {
102
- if (error?.code !== "ESRCH")
106
+ if (!isNodeError(error) || error.code !== "ESRCH")
103
107
  return false;
104
108
  }
105
109
  if (readFileSafe(lockPath) !== lockContent)
@@ -113,7 +117,7 @@ function governanceLockHost() {
113
117
  function governanceLockExistsError(lockPath, error) {
114
118
  return new GovernanceSyncError("Governance sync lock already exists; refusing concurrent registry writes.", {
115
119
  code: "governance-lock-exists",
116
- details: { lockPath, error: error?.message || String(error) },
120
+ details: { lockPath, error: errorMessage(error) },
117
121
  recovery: [
118
122
  `Inspect ${lockPath}.`,
119
123
  "If no process owns the lock, remove it manually and retry.",
@@ -149,7 +153,7 @@ export function commitGovernanceSync(context, allowedRelativePaths, { message =
149
153
  assertNoUnexpectedOutsideChanges(context.target.projectRoot, allowed, context.initialDirtyEntries || []);
150
154
  }
151
155
  catch (error) {
152
- outsideChanges = error.details || null;
156
+ outsideChanges = error instanceof GovernanceSyncError ? error.details : null;
153
157
  }
154
158
  throw new GovernanceSyncError("Governance sync wrote files but Git commit failed.", {
155
159
  code: "governance-git-commit-failed",
@@ -172,15 +176,17 @@ export function syncTaskGovernance(target, task, { event = "new-task", state = "
172
176
  const ledger = syncLedgerRow(target, task, { event, state, message, planPath, reviewPath, dryRun });
173
177
  if (ledger)
174
178
  changes.push(ledger);
175
- if (task.module) {
176
- const moduleRegistry = syncModuleRegistryRow(target, task, { state, planPath, dryRun });
179
+ const moduleKey = task.module;
180
+ if (moduleKey) {
181
+ const taskWithModule = { ...task, module: moduleKey };
182
+ const moduleRegistry = syncModuleRegistryView(target, { dryRun });
177
183
  if (moduleRegistry)
178
184
  changes.push(moduleRegistry);
179
- changes.push(...syncModuleGeneratedIndexes(target, task.module, { task, dryRun }).changes);
185
+ changes.push(...syncModuleGeneratedIndexes(target, moduleKey, { task: taskWithModule, dryRun }).changes);
180
186
  }
181
187
  return { changes };
182
188
  }
183
- export function syncModuleStepGovernance(target, { moduleKey, stepId, state, dryRun = false } = {}) {
189
+ export function syncModuleStepGovernance(target, { moduleKey, stepId, state, dryRun = false }) {
184
190
  const changes = [];
185
191
  const harnessPaths = activeHarnessPaths(target);
186
192
  const ledgerPath = harnessPaths.ledgerPath;
@@ -235,31 +241,12 @@ function syncLedgerRow(target, task, { event, state, message, planPath, reviewPa
235
241
  }
236
242
  return { destination: relative, action: dryRun ? "would-sync-governance" : "sync-governance", surface: "harness-ledger" };
237
243
  }
238
- function syncModuleRegistryRow(target, task, { state, planPath, dryRun }) {
239
- const harnessPaths = activeHarnessPaths(target);
240
- const registryPath = path.join(harnessPaths.modulesRoot, "Module-Registry.md");
241
- ensureFileFromTemplate(registryPath, "templates/ssot/Module-Registry.md", { dryRun });
244
+ function syncModuleRegistryView(target, { dryRun }) {
245
+ const registryPath = moduleRegistryViewPath(target);
242
246
  const relative = toPosix(path.relative(target.projectRoot, registryPath));
243
247
  if (!dryRun) {
244
- const content = readFileSafe(registryPath);
245
- const moduleKey = task.module;
246
- const moduleDirRelative = toPosix(path.relative(target.projectRoot, path.join(harnessPaths.modulesRoot, moduleKey)));
247
- const modulePlan = `${moduleDirRelative}/module_plan.md`;
248
- const row = [
249
- `M-${moduleKey.toUpperCase().replace(/[^A-Z0-9]+/g, "-")}`,
250
- moduleKey,
251
- `${moduleDirRelative}/**`,
252
- "coordinator",
253
- state === "planned" ? "reserved" : mapModuleState(state),
254
- `codex/${moduleKey}`,
255
- modulePlan,
256
- "none",
257
- "none",
258
- planPath,
259
- "none",
260
- todayDate(),
261
- ];
262
- fs.writeFileSync(registryPath, upsertMarkdownTableRow(content, /^ID$/i, (header, existing) => rowMatchesModule(header, existing, moduleKey, modulePlan), row));
248
+ fs.mkdirSync(path.dirname(registryPath), { recursive: true });
249
+ fs.writeFileSync(registryPath, renderModuleRegistryView(target));
263
250
  }
264
251
  return { destination: relative, action: dryRun ? "would-sync-governance" : "sync-governance", surface: "module-registry" };
265
252
  }
@@ -282,7 +269,7 @@ function syncModuleGeneratedIndexes(target, moduleKey, { task = null, dryRun = f
282
269
  }
283
270
  export function moduleGeneratedIndexSurfaces(target, tasks = collectTasks(target)) {
284
271
  const harnessPaths = activeHarnessPaths(target);
285
- const modules = [...new Set((tasks || []).map((task) => task.module).filter(Boolean))].sort();
272
+ const modules = [...new Set((tasks || []).map((task) => task.module).filter(isNonEmptyString))].sort();
286
273
  const surfaces = [];
287
274
  for (const moduleKey of modules) {
288
275
  const moduleTasks = (tasks || [])
@@ -290,11 +277,11 @@ export function moduleGeneratedIndexSurfaces(target, tasks = collectTasks(target
290
277
  .sort((a, b) => String(stripDatePrefix(a.shortId || a.id)).localeCompare(String(stripDatePrefix(b.shortId || b.id))));
291
278
  const moduleDir = path.join(harnessPaths.modulesRoot, moduleKey);
292
279
  const modulePlanPath = path.join(moduleDir, "module_plan.md");
293
- const moduleVisualPath = path.join(moduleDir, visualMapFile);
294
280
  const stepRows = moduleTasks.map((task, index) => {
295
281
  const stepId = moduleStepId(task);
296
- const previous = index === 0 ? "none" : moduleStepId(moduleTasks[index - 1]);
297
- return [stepId, task.title || task.shortId || task.id, mapModuleState(task.state), stripTargetPrefix(task.taskPlanPath || `${stripTargetPrefix(task.path)}/task_plan.md`), previous];
282
+ const previousTask = moduleTasks[index - 1];
283
+ const previous = index === 0 || !previousTask ? "none" : moduleStepId(previousTask);
284
+ return [stepId, task.title || task.shortId || task.id || "task", mapModuleState(task.state), stripTargetPrefix(task.taskPlanPath || `${stripTargetPrefix(task.path)}/task_plan.md`), previous];
298
285
  });
299
286
  surfaces.push({
300
287
  surface: "module-plan-index",
@@ -303,20 +290,14 @@ export function moduleGeneratedIndexSurfaces(target, tasks = collectTasks(target
303
290
  rows: stepRows,
304
291
  content: replaceTableRows(existingOrTemplate(modulePlanPath, "templates/planning/module_plan.md"), /^Step ID$/i, stepRows),
305
292
  });
306
- surfaces.push({
307
- surface: "module-visual-index",
308
- absolute: moduleVisualPath,
309
- relative: toPosix(path.relative(target.projectRoot, moduleVisualPath)),
310
- rows: stepRows,
311
- content: renderModuleVisualMap(moduleKey, moduleTasks),
312
- });
313
293
  }
314
294
  return surfaces;
315
295
  }
316
296
  function collectModuleTasks(target, moduleKey, task) {
317
- const tasks = collectTasks(target).filter((candidate) => candidate.module === moduleKey);
318
- if (task && !tasks.some((candidate) => stripTargetPrefix(candidate.taskPlanPath) === `${stripTargetPrefix(task.path)}/task_plan.md`)) {
319
- tasks.push({
297
+ const tasks = collectTasks(target);
298
+ const moduleTasks = tasks.filter((candidate) => candidate.module === moduleKey);
299
+ if (task && !moduleTasks.some((candidate) => stripTargetPrefix(candidate.taskPlanPath) === `${stripTargetPrefix(task.path)}/task_plan.md`)) {
300
+ moduleTasks.push({
320
301
  ...task,
321
302
  module: moduleKey,
322
303
  state: task.state || "planned",
@@ -324,61 +305,7 @@ function collectModuleTasks(target, moduleKey, task) {
324
305
  completion: 0,
325
306
  });
326
307
  }
327
- return tasks;
328
- }
329
- function renderModuleVisualMap(moduleKey, tasks) {
330
- const rows = tasks.map((task, index) => {
331
- const stepId = moduleStepId(task);
332
- const previous = index === 0 ? "none" : moduleStepId(tasks[index - 1]);
333
- const state = mapPhaseState(task.state);
334
- const completion = Number.isInteger(task.completion) ? task.completion : state === "done" ? 100 : 0;
335
- return [
336
- stepId,
337
- previous,
338
- state,
339
- completion,
340
- task.title || task.shortId || task.id,
341
- stripTargetPrefix(task.taskPlanPath || `${stripTargetPrefix(task.path)}/task_plan.md`),
342
- task.materialsReady ? "present" : "missing",
343
- previous === "none" ? "none" : `depends on ${previous}`,
344
- "coordinator",
345
- ];
346
- });
347
- const graphLines = tasks.map((task, index) => {
348
- const stepId = moduleStepId(task);
349
- const label = fitMarkdownTableRow([task.title || task.shortId || task.id], 1)[0].replace(/"/g, "'");
350
- if (index === 0)
351
- return ` ${stepId}["${label}"]`;
352
- const previous = moduleStepId(tasks[index - 1]);
353
- return ` ${previous} --> ${stepId}["${label}"]`;
354
- });
355
- return `# ${moduleKey} - Visual Map
356
-
357
- Visual Map Contract: v1.0
358
-
359
- Generated by \`harness new-task --module\` and \`harness governance rebuild\`.
360
-
361
- ## Map Index
362
-
363
- | ID | Type | Purpose | Required For Understanding | Source Evidence | Promotion Candidate |
364
- | --- | --- | --- | --- | --- | --- |
365
- | MAP-01 | topology | Show module task sequence generated from task files | yes | task scan | no |
366
-
367
- ## Phase Graph
368
-
369
- \`\`\`mermaid
370
- flowchart LR
371
- ${graphLines.length ? graphLines.join("\n") : " EMPTY[\"No module tasks\"]"}
372
- \`\`\`
373
-
374
- ## Phase Table
375
-
376
- | Phase ID | Depends On | State | Completion | Output | Required Evidence | Evidence Status | Blocking Risk | Owner / Handoff |
377
- | --- | --- | --- | ---: | --- | --- | --- | --- | --- |
378
- ${rows.map((row) => `| ${fitMarkdownTableRow(row, 9).join(" | ")} |`).join("\n")}
379
-
380
- Allowed Evidence Status: missing, partial, present, waived.
381
- `;
308
+ return moduleTasks;
382
309
  }
383
310
  function replaceTableRows(content, headerPattern, rows) {
384
311
  const lines = String(content || "").split(/\r?\n/);
@@ -408,17 +335,6 @@ function moduleStepId(task) {
408
335
  function stripDatePrefix(value) {
409
336
  return String(value || "").replace(/^(?:TASKS\/|MODULES\/[^/]+\/)?\d{4}-\d{2}-\d{2}-/, "");
410
337
  }
411
- function mapPhaseState(state) {
412
- if (state === "in_progress")
413
- return "in_progress";
414
- if (state === "review")
415
- return "review";
416
- if (state === "done")
417
- return "done";
418
- if (state === "blocked")
419
- return "blocked";
420
- return "planned";
421
- }
422
338
  function ensureFileFromTemplate(destinationPath, templateSource, { dryRun = false } = {}) {
423
339
  if (fs.existsSync(destinationPath) || dryRun)
424
340
  return;
@@ -487,10 +403,10 @@ function assertCommitIdentity(root) {
487
403
  });
488
404
  }
489
405
  }
490
- function assertDirtyCompatibleWithWriteScope(entries, allowedPaths) {
406
+ function assertDirtyCompatibleWithWriteScope(entries, allowedPaths, { allowDirtyWriteScope = false } = {}) {
491
407
  const allowed = new Set(allowedPaths);
492
408
  const overlapping = entries.filter((entry) => allowed.has(entry.path));
493
- if (overlapping.length > 0) {
409
+ if (overlapping.length > 0 && !allowDirtyWriteScope) {
494
410
  throw new GovernanceSyncError("Governance sync write scope overlaps existing dirty files; refusing to overwrite user-owned changes.", {
495
411
  code: "governance-write-scope-dirty",
496
412
  details: { overlapping, allowedPaths },
@@ -575,7 +491,7 @@ function fingerprintEntry(root, entry) {
575
491
  if (stat.isSymbolicLink())
576
492
  return `symlink:${fs.readlinkSync(absolute)}`;
577
493
  if (stat.isFile()) {
578
- return `file:${stat.size}:${crypto.createHash("sha256").update(fs.readFileSync(absolute)).digest("hex")}`;
494
+ return `file:${stat.size}:${crypto.createHash("sha256").update(new Uint8Array(fs.readFileSync(absolute))).digest("hex")}`;
579
495
  }
580
496
  if (stat.isDirectory())
581
497
  return "directory";
@@ -599,7 +515,7 @@ function statusEntries(root) {
599
515
  }
600
516
  function parseStatusPath(value) {
601
517
  const unquoted = value.replace(/^"|"$/g, "");
602
- return unquoted.includes(" -> ") ? unquoted.split(" -> ").pop() : unquoted;
518
+ return unquoted.includes(" -> ") ? unquoted.split(" -> ").pop() ?? unquoted : unquoted;
603
519
  }
604
520
  function git(cwd, args, { allowFailure = false } = {}) {
605
521
  const result = spawnSync("git", args, { cwd, encoding: "utf8" });
@@ -615,3 +531,12 @@ function git(cwd, args, { allowFailure = false } = {}) {
615
531
  function real(filePath) {
616
532
  return fs.realpathSync(filePath);
617
533
  }
534
+ function isNodeError(error) {
535
+ return error instanceof Error && "code" in error;
536
+ }
537
+ function errorMessage(error) {
538
+ return error instanceof Error ? error.message : String(error);
539
+ }
540
+ function isNonEmptyString(value) {
541
+ return typeof value === "string" && value.length > 0;
542
+ }
@@ -1,10 +1,8 @@
1
- // @ts-nocheck
2
1
  // Governance table parsing remains behavior-first until target/table domain types are modeled.
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
4
  import { readFileSafe, toPosix } from "./core-shared.mjs";
6
5
  import { getCell, parseAllMarkdownTables } from "./markdown-utils.mjs";
7
- const newRuleCutoff = "2026-05-24";
8
6
  const globalTableSpecs = [
9
7
  { key: "feature-ssot", pathFor: (target) => path.join(target.harness.planningRoot, "Feature-SSoT.md"), allowed: "index-state-route-summary", evaluate: evaluateFeatureRow },
10
8
  { key: "harness-ledger", pathFor: (target) => target.harness.ledgerPath, allowed: "task-audit-summary-route", evaluate: evaluateLedgerRow },
@@ -34,10 +32,7 @@ export function validateGovernanceTableBoundaries(target) {
34
32
  `allowed=${spec.allowed}`,
35
33
  `route=${finding.route}`,
36
34
  ].join(": ");
37
- if (isLegacyRow(rowUpdatedDate(row)))
38
- warnings.push(`${message}: legacy-report-only`);
39
- else
40
- failures.push(message);
35
+ failures.push(message);
41
36
  }
42
37
  }
43
38
  }
@@ -132,14 +127,6 @@ function governanceRowKey(row) {
132
127
  function rowText(row) {
133
128
  return Object.values(row.cells || {}).join(" ");
134
129
  }
135
- function rowUpdatedDate(row) {
136
- const value = getCell(row.cells || {}, ["Updated", "Date", "日期", "Last Verified", "最近验证"], "");
137
- const match = String(value).match(/\d{4}-\d{2}-\d{2}/);
138
- return match ? match[0] : "";
139
- }
140
- function isLegacyRow(updated) {
141
- return updated && updated < newRuleCutoff;
142
- }
143
130
  function isPlaceholderRow(row) {
144
131
  const text = rowText(row);
145
132
  return /\b(?:YYYY|MM|DD|NNN)\b|L-YYYY|HL-YYYY|\[[^\]]+\]|\.\.\.md|\bowner\b|\bShort lesson title\b/i.test(text);
@@ -1,8 +1,8 @@
1
- // @ts-nocheck
2
1
  export * from "./core-shared.mjs";
3
2
  export * from "./markdown-utils.mjs";
4
3
  export * from "./capability-registry.mjs";
5
4
  export * from "./task-scanner.mjs";
5
+ export * from "./task-discovery-contract.mjs";
6
6
  export * from "./status-builder.mjs";
7
7
  export * from "./check-profiles.mjs";
8
8
  export * from "./dashboard-data.mjs";
@@ -10,9 +10,13 @@ export * from "./dashboard-workbench.mjs";
10
10
  export * from "./migration-planner.mjs";
11
11
  export * from "./structure-migration.mjs";
12
12
  export * from "./preset-registry.mjs";
13
+ export * from "./preset-runner.mjs";
13
14
  export * from "./governance-index-generator.mjs";
14
15
  export * from "./task-lifecycle.mjs";
16
+ export * from "./module-registry.mjs";
15
17
  export * from "./task-lesson-sedimentation.mjs";
16
18
  export * from "./lesson-maintenance.mjs";
17
19
  export * from "./task-index.mjs";
18
20
  export * from "./task-tombstone-commands.mjs";
21
+ export * from "./task-archive-eligibility.mjs";
22
+ export * from "./impact-classifier.mjs";
@@ -48,7 +48,7 @@ export function resolveHarnessPaths(targetInput = ".") {
48
48
  planningRoot: resolved.planningRoot,
49
49
  tasksRoot: resolved.tasksRoot,
50
50
  modulesRoot: resolved.modulesRoot,
51
- taskRoots: [resolved.tasksRoot, resolved.modulesRoot],
51
+ taskRoots: [resolved.tasksRoot, resolved.modulesRoot, resolved.externalRoot].filter(Boolean),
52
52
  externalRoot: resolved.externalRoot,
53
53
  governanceRoot: resolved.governanceRoot,
54
54
  generatedRoot: resolved.generatedRoot,
@@ -281,6 +281,9 @@ function readHarnessManifest(manifestPath) {
281
281
  return null;
282
282
  const manifest = { version: 2, locale: "en-US", capabilities: [], structure: {} };
283
283
  let section = "";
284
+ let inModuleItems = false;
285
+ let currentModuleKey = "";
286
+ let currentModuleListField = "";
284
287
  for (const rawLine of fs.readFileSync(manifestPath, "utf8").split(/\r?\n/)) {
285
288
  const line = rawLine.replace(/\s+#.*$/, "");
286
289
  if (!line.trim())
@@ -288,10 +291,15 @@ function readHarnessManifest(manifestPath) {
288
291
  const top = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
289
292
  if (top) {
290
293
  section = top[1];
294
+ inModuleItems = false;
295
+ currentModuleKey = "";
296
+ currentModuleListField = "";
291
297
  if (section === "version")
292
298
  manifest.version = Number(top[2]) || 2;
293
299
  else if (section === "locale")
294
300
  manifest.locale = top[2] || "en-US";
301
+ else if (section === "modules")
302
+ manifest.modules = { items: {} };
295
303
  else if (section !== "structure" && section !== "capabilities")
296
304
  manifest[section] = top[2];
297
305
  continue;
@@ -304,6 +312,54 @@ function readHarnessManifest(manifestPath) {
304
312
  const nested = line.match(/^\s+([A-Za-z][A-Za-z0-9_-]*):\s*(.+)$/);
305
313
  if (section === "structure" && nested)
306
314
  manifest.structure[nested[1]] = nested[2].trim();
315
+ if (section === "modules") {
316
+ if (!manifest.modules)
317
+ manifest.modules = { items: {} };
318
+ const moduleTop = line.match(/^ ([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
319
+ if (moduleTop) {
320
+ currentModuleKey = "";
321
+ currentModuleListField = "";
322
+ if (moduleTop[1] === "items")
323
+ inModuleItems = true;
324
+ else if (moduleTop[1] === "schema")
325
+ manifest.modules.schema = moduleTop[2].trim();
326
+ else if (moduleTop[1] === "generatedView")
327
+ manifest.modules.generatedView = moduleTop[2].trim();
328
+ else
329
+ manifest.modules[moduleTop[1]] = moduleTop[2].trim();
330
+ continue;
331
+ }
332
+ const moduleItem = line.match(/^ ([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
333
+ if (inModuleItems && moduleItem) {
334
+ currentModuleKey = moduleItem[1];
335
+ currentModuleListField = "";
336
+ if (!manifest.modules.items[currentModuleKey])
337
+ manifest.modules.items[currentModuleKey] = {};
338
+ continue;
339
+ }
340
+ const moduleField = line.match(/^ ([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
341
+ if (inModuleItems && currentModuleKey && moduleField) {
342
+ const field = moduleField[1];
343
+ const raw = moduleField[2].trim();
344
+ if (["scope", "shared", "dependsOn"].includes(field)) {
345
+ manifest.modules.items[currentModuleKey][field] = raw === "[]" ? [] : raw ? [raw] : [];
346
+ currentModuleListField = field;
347
+ }
348
+ else {
349
+ manifest.modules.items[currentModuleKey][field] = raw;
350
+ currentModuleListField = "";
351
+ }
352
+ continue;
353
+ }
354
+ const moduleListItem = line.match(/^ -\s*(.+)$/);
355
+ if (inModuleItems && currentModuleKey && currentModuleListField && moduleListItem) {
356
+ const existing = manifest.modules.items[currentModuleKey][currentModuleListField];
357
+ manifest.modules.items[currentModuleKey][currentModuleListField] = [
358
+ ...(Array.isArray(existing) ? existing : []),
359
+ moduleListItem[1].trim(),
360
+ ];
361
+ }
362
+ }
307
363
  }
308
364
  if (!manifest.structure.harnessRoot && manifest.harnessRoot)
309
365
  manifest.structure.harnessRoot = manifest.harnessRoot;
@@ -311,6 +367,64 @@ function readHarnessManifest(manifestPath) {
311
367
  manifest.structure.planningRoot = `${manifest.harnessRoot}/planning`;
312
368
  return manifest;
313
369
  }
370
+ export function renderHarnessManifest({ locale, capabilities, structure = null, modules = null }) {
371
+ const manifestStructure = structure || {
372
+ harnessRoot: v2HarnessRoot,
373
+ planningRoot: `${v2HarnessRoot}/planning`,
374
+ tasksRoot: `${v2HarnessRoot}/planning/tasks`,
375
+ modulesRoot: `${v2HarnessRoot}/planning/modules`,
376
+ externalRoot: `${v2HarnessRoot}/planning/external`,
377
+ governanceRoot: `${v2HarnessRoot}/governance`,
378
+ generatedRoot: `${v2HarnessRoot}/governance/generated`,
379
+ };
380
+ const lines = [
381
+ "version: 2",
382
+ `locale: ${locale}`,
383
+ "capabilities:",
384
+ ...capabilities.map((capability) => ` - ${capability}`),
385
+ "structure:",
386
+ ...Object.entries(manifestStructure).map(([key, value]) => ` ${key}: ${value}`),
387
+ ];
388
+ if (modules && (modules.schema || modules.generatedView || Object.keys(modules.items || {}).length > 0)) {
389
+ lines.push("modules:");
390
+ if (modules.schema)
391
+ lines.push(` schema: ${yamlScalar(modules.schema)}`);
392
+ if (modules.generatedView)
393
+ lines.push(` generatedView: ${yamlScalar(modules.generatedView)}`);
394
+ lines.push(" items:");
395
+ for (const [key, module] of Object.entries(modules.items || {}).sort(([left], [right]) => left.localeCompare(right))) {
396
+ lines.push(` ${key}:`);
397
+ for (const field of ["title", "prefix", "status", "branch", "owner", "currentStep", "scope", "shared", "dependsOn", "plan", "brief", "updated"]) {
398
+ const value = module[field];
399
+ if (Array.isArray(value)) {
400
+ lines.push(` ${field}:${value.length ? "" : " []"}`);
401
+ for (const item of value)
402
+ lines.push(` - ${yamlScalar(String(item))}`);
403
+ }
404
+ else if (value !== undefined && value !== null && String(value) !== "") {
405
+ lines.push(` ${field}: ${yamlScalar(String(value))}`);
406
+ }
407
+ }
408
+ }
409
+ }
410
+ return `${lines.join("\n")}\n`;
411
+ }
412
+ export function assertRenderableHarnessManifest(manifest) {
413
+ if (!manifest)
414
+ return;
415
+ const allowed = new Set(["version", "locale", "capabilities", "structure", "modules", "harnessRoot"]);
416
+ const unknown = Object.keys(manifest).filter((key) => !allowed.has(key));
417
+ if (unknown.length > 0)
418
+ throw new Error(`Cannot rewrite harness.yaml with unknown top-level fields: ${unknown.join(", ")}`);
419
+ }
420
+ function yamlScalar(value) {
421
+ const raw = String(value || "");
422
+ if (!raw)
423
+ return "''";
424
+ if (/[:#\n\r]|^\s|\s$|^(?:true|false|null|\d+(?:\.\d+)?)$/i.test(raw))
425
+ return JSON.stringify(raw);
426
+ return raw;
427
+ }
314
428
  function resolveManifestStructurePath(projectRoot, fieldName, relativePath) {
315
429
  const raw = String(relativePath || "").trim();
316
430
  if (!raw)