coding-agent-harness 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/CONTRIBUTING.md +2 -2
  2. package/README.md +63 -3
  3. package/README.zh-CN.md +52 -3
  4. package/SKILL.md +43 -43
  5. package/dist/build-dist.mjs +189 -0
  6. package/dist/check-dist-observation.mjs +428 -0
  7. package/dist/check-harness.mjs +489 -0
  8. package/dist/check-import-graph.mjs +511 -0
  9. package/dist/check-runtime-emit.mjs +304 -0
  10. package/dist/check-type-boundaries.mjs +139 -0
  11. package/dist/commands/dashboard-command.mjs +80 -0
  12. package/dist/commands/migration-command.mjs +152 -0
  13. package/dist/commands/preset-command.mjs +91 -0
  14. package/dist/commands/task-command.mjs +324 -0
  15. package/dist/harness.mjs +304 -0
  16. package/dist/lib/capability-registry.mjs +643 -0
  17. package/dist/lib/check-module-parallel.mjs +227 -0
  18. package/dist/lib/check-profiles.mjs +414 -0
  19. package/dist/lib/check-task-contracts.mjs +54 -0
  20. package/dist/lib/core-shared.mjs +254 -0
  21. package/dist/lib/dashboard-data.mjs +608 -0
  22. package/dist/lib/dashboard-workbench.mjs +334 -0
  23. package/dist/lib/dashboard-writer.mjs +200 -0
  24. package/dist/lib/git-status-summary.mjs +45 -0
  25. package/dist/lib/governance-index-generator.mjs +236 -0
  26. package/dist/lib/governance-sync.mjs +617 -0
  27. package/dist/lib/governance-table-boundary.mjs +161 -0
  28. package/{scripts → dist}/lib/harness-core.mjs +2 -0
  29. package/dist/lib/harness-paths.mjs +338 -0
  30. package/dist/lib/lesson-maintenance.mjs +139 -0
  31. package/dist/lib/markdown-utils.mjs +193 -0
  32. package/dist/lib/migration-planner.mjs +439 -0
  33. package/dist/lib/migration-support.mjs +317 -0
  34. package/dist/lib/phase-kind.mjs +46 -0
  35. package/dist/lib/preset-audit-contracts.mjs +40 -0
  36. package/dist/lib/preset-engine.mjs +516 -0
  37. package/dist/lib/preset-registry.mjs +831 -0
  38. package/dist/lib/preset-resource-contracts.mjs +83 -0
  39. package/dist/lib/review-confirm-git-gate.mjs +244 -0
  40. package/dist/lib/status-builder.mjs +87 -0
  41. package/{scripts → dist}/lib/status-dashboard-renderer.mjs +44 -46
  42. package/dist/lib/structure-migration.mjs +404 -0
  43. package/dist/lib/subagent-authorization-audit.mjs +198 -0
  44. package/dist/lib/task-audit-metadata.mjs +376 -0
  45. package/dist/lib/task-audit-migration.mjs +355 -0
  46. package/dist/lib/task-completion-consistency.mjs +29 -0
  47. package/dist/lib/task-index.mjs +133 -0
  48. package/dist/lib/task-lesson-candidates.mjs +239 -0
  49. package/dist/lib/task-lesson-sedimentation.mjs +300 -0
  50. package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
  51. package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
  52. package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
  53. package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
  54. package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
  55. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
  56. package/dist/lib/task-lifecycle/template-files.mjs +52 -0
  57. package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
  58. package/dist/lib/task-lifecycle.mjs +611 -0
  59. package/dist/lib/task-metadata.mjs +116 -0
  60. package/dist/lib/task-review-model.mjs +474 -0
  61. package/dist/lib/task-scanner.mjs +439 -0
  62. package/dist/lib/task-tombstone-commands.mjs +125 -0
  63. package/dist/postinstall.mjs +14 -0
  64. package/dist/run-built-tests.mjs +84 -0
  65. package/docs-release/README.md +1 -0
  66. package/docs-release/architecture/overview.md +12 -12
  67. package/docs-release/architecture/overview.zh-CN.md +12 -12
  68. package/docs-release/architecture/system-explainer/01-system-overview.md +15 -14
  69. package/docs-release/architecture/system-explainer/02-module-dependency.md +8 -8
  70. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +3 -3
  71. package/docs-release/architecture/system-explainer/04-check-and-governance.md +9 -7
  72. package/docs-release/architecture/system-explainer/05-data-flow.md +5 -5
  73. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +1 -4
  74. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +15 -14
  75. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +8 -8
  76. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +3 -3
  77. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +10 -8
  78. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
  79. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +1 -4
  80. package/docs-release/guides/agent-installation.en-US.md +14 -8
  81. package/docs-release/guides/agent-installation.md +14 -8
  82. package/docs-release/guides/contributing.md +3 -3
  83. package/docs-release/guides/contributing.zh-CN.md +3 -3
  84. package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
  85. package/docs-release/guides/document-audience-and-surfaces.md +10 -10
  86. package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
  87. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
  88. package/docs-release/guides/migration-playbook.en-US.md +63 -1
  89. package/docs-release/guides/migration-playbook.md +59 -1
  90. package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
  91. package/docs-release/guides/parent-control-repository-pattern.md +25 -25
  92. package/docs-release/guides/preset-development.md +2 -2
  93. package/docs-release/guides/repository-operating-models.en-US.md +21 -21
  94. package/docs-release/guides/repository-operating-models.md +21 -21
  95. package/docs-release/guides/task-state-machine.en-US.md +5 -5
  96. package/docs-release/guides/task-state-machine.md +5 -5
  97. package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
  98. package/examples/minimal-project/AGENTS.md +2 -2
  99. package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
  100. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
  101. package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
  102. package/package.json +20 -12
  103. package/presets/legacy-migration/preset.yaml +5 -5
  104. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  105. package/presets/lesson-sedimentation/preset.yaml +3 -3
  106. package/presets/module/preset.yaml +2 -2
  107. package/presets/module/templates/execution_strategy.append.md +1 -1
  108. package/presets/module/templates/task_plan.append.md +3 -3
  109. package/presets/standard-task/preset.yaml +2 -2
  110. package/references/adversarial-review-standard.md +2 -2
  111. package/references/agents-md-pattern.md +14 -14
  112. package/references/cadence-ledger.md +1 -1
  113. package/references/ci-cd-standard.md +1 -1
  114. package/references/delivery-operating-model-standard.md +4 -4
  115. package/references/docs-directory-standard.md +65 -159
  116. package/references/external-source-intake-standard.md +10 -10
  117. package/references/harness-ledger.md +5 -5
  118. package/references/legacy-12-phase-bootstrap.md +2 -2
  119. package/references/lessons-governance.md +15 -15
  120. package/references/long-running-task-standard.md +6 -6
  121. package/references/module-parallel-standard.md +34 -34
  122. package/references/planning-loop.md +6 -6
  123. package/references/project-onboarding-audit.md +4 -4
  124. package/references/regression-system.md +2 -2
  125. package/references/repo-governance-standard.md +4 -4
  126. package/references/review-routing-standard.md +1 -1
  127. package/references/ssot-governance.md +19 -19
  128. package/references/taskr-gap-analysis.md +5 -5
  129. package/references/walkthrough-closeout.md +14 -14
  130. package/references/worktree-parallel.md +3 -3
  131. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
  132. package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
  133. package/templates/AGENTS.md.template +26 -26
  134. package/templates/architecture/README.md +4 -4
  135. package/templates/architecture/service-catalog.md +2 -2
  136. package/templates/architecture/services/service-template.md +1 -1
  137. package/templates/dashboard/assets/app-src/20-overview.js +11 -5
  138. package/templates/dashboard/assets/app-src/40-modules.js +1 -1
  139. package/templates/dashboard/assets/app.js +12 -6
  140. package/templates/dashboard/assets/i18n.js +4 -2
  141. package/templates/development/README.md +10 -10
  142. package/templates/development/cross-repo-debugging.md +3 -3
  143. package/templates/development/external-context/service-template.md +2 -2
  144. package/templates/development/external-source-packs/README.md +4 -4
  145. package/templates/integrations/README.md +4 -4
  146. package/templates/integrations/api-contract.md +2 -2
  147. package/templates/integrations/event-contract.md +2 -2
  148. package/templates/integrations/third-party/vendor-template.md +2 -2
  149. package/templates/integrations/webhook-contract.md +2 -2
  150. package/templates/ledger/Harness-Ledger.md +1 -1
  151. package/templates/planning/INDEX.md +1 -0
  152. package/templates/planning/module_session_prompt.md +1 -1
  153. package/templates/planning/task_plan.md +1 -1
  154. package/templates/planning/walkthrough.md +47 -0
  155. package/templates/reference/docs-library-standard.md +8 -8
  156. package/templates/reference/external-source-intake-standard.md +15 -15
  157. package/templates/reference/repo-governance-standard.md +1 -1
  158. package/templates/ssot/Module-Registry.md +1 -1
  159. package/templates/walkthrough/walkthrough-template.md +2 -2
  160. package/templates-zh-CN/AGENTS.md.template +26 -26
  161. package/templates-zh-CN/CLAUDE.md.template +1 -1
  162. package/templates-zh-CN/architecture/README.md +4 -4
  163. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  164. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  165. package/templates-zh-CN/development/README.md +10 -10
  166. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  167. package/templates-zh-CN/development/external-context/service-template.md +2 -2
  168. package/templates-zh-CN/development/external-source-packs/README.md +4 -4
  169. package/templates-zh-CN/integrations/README.md +4 -4
  170. package/templates-zh-CN/integrations/api-contract.md +2 -2
  171. package/templates-zh-CN/integrations/event-contract.md +2 -2
  172. package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
  173. package/templates-zh-CN/integrations/webhook-contract.md +2 -2
  174. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  175. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  176. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  177. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  178. package/templates-zh-CN/planning/module_session_prompt.md +11 -11
  179. package/templates-zh-CN/planning/walkthrough.md +47 -0
  180. package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
  181. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  182. package/templates-zh-CN/reference/docs-library-standard.md +28 -28
  183. package/templates-zh-CN/reference/execution-workflow-standard.md +1 -1
  184. package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
  185. package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
  186. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  187. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  188. package/templates-zh-CN/reference/review-routing-standard.md +1 -1
  189. package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
  190. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  191. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  192. package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
  193. package/templates-zh-CN/ssot/Module-Registry.md +3 -3
  194. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  195. package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
  196. package/tsconfig.dist.json +16 -0
  197. package/tsconfig.json +25 -0
  198. package/tsconfig.runtime.json +24 -0
  199. package/examples/minimal-project/.harness-capabilities.json +0 -8
  200. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
  201. package/scripts/check-harness.mjs +0 -508
  202. package/scripts/commands/dashboard-command.mjs +0 -67
  203. package/scripts/commands/migration-command.mjs +0 -126
  204. package/scripts/commands/preset-command.mjs +0 -73
  205. package/scripts/commands/task-command.mjs +0 -328
  206. package/scripts/harness.mjs +0 -291
  207. package/scripts/lib/capability-registry.mjs +0 -587
  208. package/scripts/lib/check-module-parallel.mjs +0 -230
  209. package/scripts/lib/check-profiles.mjs +0 -372
  210. package/scripts/lib/check-task-contracts.mjs +0 -55
  211. package/scripts/lib/core-shared.mjs +0 -249
  212. package/scripts/lib/dashboard-data.mjs +0 -520
  213. package/scripts/lib/dashboard-workbench.mjs +0 -336
  214. package/scripts/lib/dashboard-writer.mjs +0 -202
  215. package/scripts/lib/git-status-summary.mjs +0 -46
  216. package/scripts/lib/governance-index-generator.mjs +0 -174
  217. package/scripts/lib/governance-sync.mjs +0 -611
  218. package/scripts/lib/governance-table-boundary.mjs +0 -175
  219. package/scripts/lib/lesson-maintenance.mjs +0 -152
  220. package/scripts/lib/markdown-utils.mjs +0 -191
  221. package/scripts/lib/migration-planner.mjs +0 -476
  222. package/scripts/lib/migration-support.mjs +0 -312
  223. package/scripts/lib/phase-kind.mjs +0 -50
  224. package/scripts/lib/preset-audit-contracts.mjs +0 -37
  225. package/scripts/lib/preset-engine.mjs +0 -494
  226. package/scripts/lib/preset-registry.mjs +0 -776
  227. package/scripts/lib/preset-resource-contracts.mjs +0 -83
  228. package/scripts/lib/review-confirm-git-gate.mjs +0 -248
  229. package/scripts/lib/status-builder.mjs +0 -88
  230. package/scripts/lib/subagent-authorization-audit.mjs +0 -196
  231. package/scripts/lib/task-audit-metadata.mjs +0 -385
  232. package/scripts/lib/task-audit-migration.mjs +0 -350
  233. package/scripts/lib/task-completion-consistency.mjs +0 -26
  234. package/scripts/lib/task-index.mjs +0 -93
  235. package/scripts/lib/task-lesson-candidates.mjs +0 -242
  236. package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
  237. package/scripts/lib/task-lifecycle/create-task-helpers.mjs +0 -67
  238. package/scripts/lib/task-lifecycle/phase-sync.mjs +0 -88
  239. package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -112
  240. package/scripts/lib/task-lifecycle/review-gates.mjs +0 -73
  241. package/scripts/lib/task-lifecycle/review-submission.mjs +0 -63
  242. package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +0 -49
  243. package/scripts/lib/task-lifecycle/template-files.mjs +0 -53
  244. package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
  245. package/scripts/lib/task-lifecycle.mjs +0 -616
  246. package/scripts/lib/task-metadata.mjs +0 -118
  247. package/scripts/lib/task-review-model.mjs +0 -455
  248. package/scripts/lib/task-scanner.mjs +0 -503
  249. package/scripts/lib/task-tombstone-commands.mjs +0 -140
  250. package/scripts/postinstall.mjs +0 -14
  251. package/templates/walkthrough/Closeout-SSoT.md +0 -43
  252. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
  253. /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
  254. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/INDEX.md +0 -0
  255. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
  256. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
  257. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
  258. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
  259. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
  260. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/visual_map.md +0 -0
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { spawnSync } from "node:child_process";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
+ const defaultProjectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
+ export function checkDistObservation({ projectRoot = defaultProjectRoot, runPack = true, runInstallSmoke = true, runCommandMatrix = true, } = {}) {
10
+ const root = path.resolve(projectRoot);
11
+ const failures = [];
12
+ const observations = {
13
+ packageRuntime: {},
14
+ inventory: {},
15
+ package: {},
16
+ installSmoke: {},
17
+ commandMatrix: [],
18
+ };
19
+ const pkg = readJson(path.join(root, "package.json"), failures, "package-json");
20
+ if (!pkg)
21
+ return { ok: false, failures, observations };
22
+ expectEqual(failures, "package-bin-not-dist", pkg.bin?.harness, "dist/harness.mjs", "package bin.harness must resolve to dist/harness.mjs");
23
+ const distRuntimeScripts = {
24
+ check: "node dist/harness.mjs check --profile source-package .",
25
+ "check:private": "node dist/harness.mjs check --profile private-harness .harness-private",
26
+ status: "node dist/harness.mjs status --json .",
27
+ dashboard: "node dist/harness.mjs dashboard --out tmp/harness-dashboard.html examples/minimal-project",
28
+ "dashboard:folder": "node dist/harness.mjs dashboard --out-dir tmp/harness-dashboard examples/minimal-project",
29
+ postinstall: "node dist/postinstall.mjs",
30
+ "observe:dist": "node dist/check-dist-observation.mjs --skip-pack --skip-install-smoke",
31
+ };
32
+ for (const [name, expected] of Object.entries(distRuntimeScripts)) {
33
+ expectEqual(failures, `package-script-${name}-not-dist`, pkg.scripts?.[name], expected, `package script ${name} must run from dist`);
34
+ }
35
+ observations.packageRuntime = {
36
+ bin: pkg.bin?.harness,
37
+ scripts: Object.fromEntries(Object.keys(distRuntimeScripts).map((name) => [name, pkg.scripts?.[name]])),
38
+ };
39
+ // `npm pack --dry-run` runs `prepack`, which refreshes committed `dist/`.
40
+ // Run it before inspecting dist so the observation is from one stable build.
41
+ if (runPack) {
42
+ const pack = spawnSync("npm", ["pack", "--dry-run", "--json"], {
43
+ cwd: root,
44
+ encoding: "utf8",
45
+ maxBuffer: 32 * 1024 * 1024,
46
+ });
47
+ if (pack.status !== 0) {
48
+ failures.push({ code: "pack-dry-run-failed", message: `npm pack dry-run failed\nSTDOUT:\n${pack.stdout}\nSTDERR:\n${pack.stderr}` });
49
+ }
50
+ else {
51
+ const packed = JSON.parse(pack.stdout)[0].files.map((file) => file.path).sort();
52
+ observations.package = {
53
+ entryCount: packed.length,
54
+ hasDistHarness: packed.includes("dist/harness.mjs"),
55
+ hasDistPostinstall: packed.includes("dist/postinstall.mjs"),
56
+ hasDistObservationGate: packed.includes("dist/check-dist-observation.mjs"),
57
+ hasScriptsHarness: packed.includes("scripts/harness.mjs"),
58
+ hasScripts: packed.some((file) => file.startsWith("scripts/")),
59
+ hasTests: packed.some((file) => file.startsWith("tests/")),
60
+ };
61
+ for (const required of ["dist/harness.mjs", "dist/postinstall.mjs", "dist/check-dist-observation.mjs"]) {
62
+ if (!packed.includes(required))
63
+ failures.push({ code: "packed-file-missing", file: required, message: `package missing ${required}` });
64
+ }
65
+ if (observations.package.hasScripts)
66
+ failures.push({ code: "package-includes-scripts", message: "package must not include scripts/** after historical shim deletion" });
67
+ if (observations.package.hasTests)
68
+ failures.push({ code: "package-includes-tests", message: "package must not include tests/**" });
69
+ }
70
+ }
71
+ const requiredDist = ["dist/harness.mjs", "dist/postinstall.mjs", "dist/check-dist-observation.mjs"];
72
+ for (const relative of requiredDist) {
73
+ if (!fs.existsSync(path.join(root, relative))) {
74
+ failures.push({ code: "missing-dist-runtime", message: `missing dist runtime artifact: ${relative}` });
75
+ }
76
+ }
77
+ const distFiles = collectFiles(path.join(root, "dist")).filter((file) => file.endsWith(".mjs"));
78
+ for (const file of distFiles) {
79
+ const relative = toPosix(path.relative(root, file));
80
+ const content = fs.readFileSync(file, "utf8");
81
+ for (const specifier of parseImportSpecifiers(content)) {
82
+ if (/\.(?:ts|mts)$/.test(specifier)) {
83
+ failures.push({ code: "dist-imports-typescript-source", file: relative, message: `${relative} imports TypeScript source ${specifier}` });
84
+ }
85
+ if (specifier.includes("scripts/") && specifier.endsWith(".mjs")) {
86
+ failures.push({ code: "dist-imports-scripts-shim", file: relative, message: `${relative} imports historical scripts shim ${specifier}` });
87
+ }
88
+ }
89
+ }
90
+ const scriptShims = collectFiles(path.join(root, "scripts")).filter((file) => file.endsWith(".mjs"));
91
+ const testShims = collectFiles(path.join(root, "tests")).filter((file) => file.endsWith(".mjs"));
92
+ const unpairedScriptShims = scriptShims.filter((file) => !fs.existsSync(file.replace(/\.mjs$/, ".mts")));
93
+ const unpairedTestShims = testShims.filter((file) => !fs.existsSync(file.replace(/\.mjs$/, ".mts")));
94
+ for (const file of [...unpairedScriptShims, ...unpairedTestShims]) {
95
+ failures.push({
96
+ code: "historical-shim-without-typescript-source",
97
+ file: toPosix(path.relative(root, file)),
98
+ message: `${toPosix(path.relative(root, file))} has no adjacent .mts source twin`,
99
+ });
100
+ }
101
+ observations.inventory = {
102
+ distMjs: distFiles.length,
103
+ scriptShims: scriptShims.length,
104
+ testShims: testShims.length,
105
+ unpairedScriptShims: unpairedScriptShims.length,
106
+ unpairedTestShims: unpairedTestShims.length,
107
+ };
108
+ if (scriptShims.length > 0) {
109
+ failures.push({ code: "historical-script-shims-remain", message: `PR-28 final inventory must have 0 scripts/**/*.mjs files; found ${scriptShims.length}` });
110
+ }
111
+ if (testShims.length > 0) {
112
+ failures.push({ code: "historical-test-shims-remain", message: `PR-28 final inventory must have 0 tests/**/*.mjs files; found ${testShims.length}` });
113
+ }
114
+ if (runCommandMatrix) {
115
+ runMatrix(root, failures, observations.commandMatrix);
116
+ }
117
+ if (runInstallSmoke) {
118
+ runInstalledPackageSmoke(root, failures, observations);
119
+ }
120
+ return {
121
+ ok: failures.length === 0,
122
+ failures,
123
+ observations,
124
+ };
125
+ }
126
+ function runMatrix(root, failures, commandMatrix) {
127
+ const distHarness = path.join(root, "dist/harness.mjs");
128
+ const matrix = [
129
+ { id: "help", args: ["--help"] },
130
+ { id: "status", args: ["status", "--json", "."] },
131
+ { id: "task-list", args: ["task-list", "--json", "."] },
132
+ { id: "preset-list", args: ["preset", "list", "--json", "."] },
133
+ { id: "source-check", args: ["check", "--profile", "source-package", "."] },
134
+ { id: "target-check", args: ["check", "--profile", "target-project", "examples/minimal-project"] },
135
+ { id: "migrate-plan", args: ["migrate-plan", "--json", "--limit", "20", "examples/minimal-project"] },
136
+ { id: "dashboard", args: ["dashboard", "--out-dir", path.join("tmp", `pr-27-observation-dashboard-${process.pid}`), "examples/minimal-project"] },
137
+ ];
138
+ for (const entry of matrix) {
139
+ const result = spawnSync(process.execPath, [distHarness, ...entry.args], {
140
+ cwd: root,
141
+ encoding: "utf8",
142
+ maxBuffer: 16 * 1024 * 1024,
143
+ });
144
+ commandMatrix.push({ id: entry.id, status: result.status });
145
+ if (result.status !== 0) {
146
+ failures.push({
147
+ code: "dist-command-failed",
148
+ command: entry.id,
149
+ message: `dist command ${entry.id} failed\nSTDOUT:\n${result.stdout}\nSTDERR:\n${result.stderr}`,
150
+ });
151
+ }
152
+ }
153
+ const postinstall = spawnSync(process.execPath, [path.join(root, "dist/postinstall.mjs")], {
154
+ cwd: root,
155
+ encoding: "utf8",
156
+ env: { ...process.env, CODING_AGENT_HARNESS_SKIP_POSTINSTALL: "1" },
157
+ });
158
+ commandMatrix.push({ id: "postinstall-skip", status: postinstall.status });
159
+ if (postinstall.status !== 0) {
160
+ failures.push({
161
+ code: "dist-postinstall-failed",
162
+ message: `dist postinstall failed\nSTDOUT:\n${postinstall.stdout}\nSTDERR:\n${postinstall.stderr}`,
163
+ });
164
+ }
165
+ }
166
+ function runInstalledPackageSmoke(root, failures, observations) {
167
+ const node24 = findNode24();
168
+ if (!node24) {
169
+ failures.push({ code: "node24-not-found", message: "install smoke requires a Node 24 executable" });
170
+ return;
171
+ }
172
+ const nodeBin = path.dirname(node24);
173
+ const nodeVersion = spawnSync(node24, ["--version"], { encoding: "utf8" }).stdout.trim();
174
+ if (!nodeVersion.startsWith("v24.")) {
175
+ failures.push({ code: "node24-version-mismatch", actual: nodeVersion, message: `install smoke must run on Node 24, got ${nodeVersion}` });
176
+ return;
177
+ }
178
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "harness-dist-observation-install-"));
179
+ const packDir = path.join(tempRoot, "pack");
180
+ const consumer = path.join(tempRoot, "consumer");
181
+ const home = path.join(tempRoot, "home");
182
+ fs.mkdirSync(packDir, { recursive: true });
183
+ fs.mkdirSync(consumer, { recursive: true });
184
+ fs.mkdirSync(home, { recursive: true });
185
+ const npmEnv = isolatedEnv({ nodeBin, home });
186
+ const pack = spawnSync("npm", ["pack", "--silent", "--pack-destination", packDir], {
187
+ cwd: root,
188
+ encoding: "utf8",
189
+ env: npmEnv,
190
+ maxBuffer: 32 * 1024 * 1024,
191
+ });
192
+ if (pack.status !== 0) {
193
+ failures.push({ code: "install-smoke-pack-failed", message: `npm pack failed\nSTDOUT:\n${pack.stdout}\nSTDERR:\n${pack.stderr}` });
194
+ return;
195
+ }
196
+ const tarball = path.join(packDir, pack.stdout.trim().split(/\r?\n/).at(-1));
197
+ fs.writeFileSync(path.join(consumer, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2));
198
+ const install = spawnSync("npm", ["install", "--silent", "--no-audit", "--no-fund", tarball], {
199
+ cwd: consumer,
200
+ encoding: "utf8",
201
+ env: npmEnv,
202
+ maxBuffer: 32 * 1024 * 1024,
203
+ });
204
+ if (install.status !== 0) {
205
+ failures.push({ code: "install-smoke-install-failed", message: `npm install packed tarball failed\nSTDOUT:\n${install.stdout}\nSTDERR:\n${install.stderr}` });
206
+ return;
207
+ }
208
+ const packageRoot = path.join(consumer, "node_modules/coding-agent-harness");
209
+ const bin = path.join(consumer, "node_modules/.bin/harness");
210
+ const pkg = readJson(path.join(packageRoot, "package.json"), failures, "installed-package-json");
211
+ if (!pkg)
212
+ return;
213
+ const binTarget = fs.existsSync(bin) ? fs.readlinkSync(bin) : "";
214
+ observations.installSmoke = {
215
+ nodeVersion,
216
+ tempRoot,
217
+ binTarget,
218
+ bin: pkg.bin?.harness,
219
+ postinstall: pkg.scripts?.postinstall,
220
+ observeDist: pkg.scripts?.["observe:dist"],
221
+ hasTests: fs.existsSync(path.join(packageRoot, "tests")),
222
+ hasScripts: fs.existsSync(path.join(packageRoot, "scripts")),
223
+ scriptsDisabled: [],
224
+ steps: [],
225
+ };
226
+ expectEqual(failures, "installed-bin-not-dist", pkg.bin?.harness, "dist/harness.mjs", "installed package bin.harness must resolve to dist/harness.mjs");
227
+ expectEqual(failures, "installed-postinstall-not-dist", pkg.scripts?.postinstall, "node dist/postinstall.mjs", "installed package postinstall must resolve to dist/postinstall.mjs");
228
+ expectEqual(failures, "installed-observe-dist-not-dist", pkg.scripts?.["observe:dist"], "node dist/check-dist-observation.mjs --skip-pack --skip-install-smoke", "installed observe:dist must resolve to dist/check-dist-observation.mjs");
229
+ if (!binTarget.includes("dist/harness.mjs")) {
230
+ failures.push({ code: "installed-bin-link-not-dist", message: `installed bin link does not target dist/harness.mjs: ${binTarget}` });
231
+ }
232
+ for (const relative of ["dist/harness.mjs", "dist/postinstall.mjs", "dist/check-dist-observation.mjs"]) {
233
+ if (!fs.existsSync(path.join(packageRoot, relative)))
234
+ failures.push({ code: "installed-file-missing", file: relative, message: `installed package missing ${relative}` });
235
+ }
236
+ if (observations.installSmoke.hasTests)
237
+ failures.push({ code: "installed-package-includes-tests", message: "installed package must not include tests/**" });
238
+ if (observations.installSmoke.hasScripts)
239
+ failures.push({ code: "installed-package-includes-scripts", message: "installed package must not include scripts/** after historical shim deletion" });
240
+ const installedScripts = path.join(packageRoot, "scripts");
241
+ if (fs.existsSync(installedScripts)) {
242
+ fs.renameSync(installedScripts, `${installedScripts}.disabled-by-dist-observation`);
243
+ observations.installSmoke.scriptsDisabled.push("scripts/");
244
+ }
245
+ const runtimeEnv = isolatedEnv({ nodeBin, home, extraPath: [path.join(consumer, "node_modules", ".bin")] });
246
+ runInstalledMatrix(root, runtimeEnv, failures, observations.installSmoke.steps);
247
+ const postinstall = spawnSync(node24, [path.join(packageRoot, "dist/postinstall.mjs")], {
248
+ cwd: packageRoot,
249
+ encoding: "utf8",
250
+ env: { ...runtimeEnv, CODING_AGENT_HARNESS_SKIP_POSTINSTALL: "1" },
251
+ });
252
+ observations.installSmoke.steps.push({ id: "installed-dist-postinstall", status: postinstall.status });
253
+ if (postinstall.status !== 0)
254
+ failures.push({ code: "installed-postinstall-failed", message: `installed dist postinstall failed\nSTDOUT:\n${postinstall.stdout}\nSTDERR:\n${postinstall.stderr}` });
255
+ const installedObservation = spawnSync(node24, [path.join(packageRoot, "dist/check-dist-observation.mjs"), "--project-root", packageRoot, "--skip-pack", "--skip-install-smoke", "--skip-command-matrix", "--json"], {
256
+ cwd: packageRoot,
257
+ encoding: "utf8",
258
+ env: runtimeEnv,
259
+ maxBuffer: 32 * 1024 * 1024,
260
+ });
261
+ observations.installSmoke.steps.push({ id: "installed-observation", status: installedObservation.status });
262
+ if (installedObservation.status !== 0) {
263
+ failures.push({ code: "installed-observation-failed", message: `installed observation failed\nSTDOUT:\n${installedObservation.stdout}\nSTDERR:\n${installedObservation.stderr}` });
264
+ }
265
+ else {
266
+ const installedResult = JSON.parse(installedObservation.stdout);
267
+ observations.installSmoke.observationOk = installedResult.ok;
268
+ if (!installedResult.ok)
269
+ failures.push({ code: "installed-observation-not-ok", message: JSON.stringify(installedResult.failures, null, 2) });
270
+ }
271
+ }
272
+ function runInstalledMatrix(root, runtimeEnv, failures, steps) {
273
+ const matrix = [
274
+ { id: "installed-help", cwd: root, args: ["--help"] },
275
+ { id: "installed-status", cwd: root, args: ["status", "--json", "."] },
276
+ { id: "installed-task-list", cwd: root, args: ["task-list", "--json", "."] },
277
+ { id: "installed-preset-list", cwd: root, args: ["preset", "list", "--json", "."] },
278
+ { id: "installed-source-check", cwd: root, args: ["check", "--profile", "source-package", "."] },
279
+ { id: "installed-target-check", cwd: root, args: ["check", "--profile", "target-project", "examples/minimal-project"] },
280
+ { id: "installed-migrate-plan", cwd: root, args: ["migrate-plan", "--json", "--limit", "20", "examples/minimal-project"] },
281
+ { id: "installed-dashboard", cwd: root, args: ["dashboard", "--out-dir", path.join("tmp", `pr-27-installed-observation-dashboard-${process.pid}`), "examples/minimal-project"] },
282
+ ];
283
+ for (const entry of matrix) {
284
+ const result = spawnSync("harness", entry.args, {
285
+ cwd: entry.cwd,
286
+ encoding: "utf8",
287
+ env: runtimeEnv,
288
+ maxBuffer: 16 * 1024 * 1024,
289
+ });
290
+ steps.push({ id: entry.id, status: result.status });
291
+ if (result.status !== 0) {
292
+ failures.push({
293
+ code: "installed-command-failed",
294
+ command: entry.id,
295
+ message: `installed command ${entry.id} failed after scripts/ isolation\nSTDOUT:\n${result.stdout}\nSTDERR:\n${result.stderr}`,
296
+ });
297
+ }
298
+ }
299
+ }
300
+ function findNode24() {
301
+ const candidates = [
302
+ process.env.NODE24,
303
+ process.env.NODE24_PATH,
304
+ process.execPath,
305
+ path.join(os.homedir(), ".nvm", "versions", "node", "v24.16.0", "bin", "node"),
306
+ path.join(os.homedir(), ".nvm", "versions", "node", "v24.13.1", "bin", "node"),
307
+ "/opt/homebrew/opt/node@24/bin/node",
308
+ "/usr/local/opt/node@24/bin/node",
309
+ ].filter(Boolean);
310
+ for (const candidate of candidates) {
311
+ if (!fs.existsSync(candidate))
312
+ continue;
313
+ const version = spawnSync(candidate, ["--version"], { encoding: "utf8" });
314
+ if (version.status === 0 && version.stdout.trim().startsWith("v24."))
315
+ return candidate;
316
+ }
317
+ return undefined;
318
+ }
319
+ function isolatedEnv({ nodeBin, home = process.env.HOME, extraPath = [] }) {
320
+ return {
321
+ ...process.env,
322
+ HOME: home,
323
+ npm_config_cache: path.join(home, ".npm"),
324
+ PATH: [...extraPath, nodeBin, "/usr/bin", "/bin"].join(path.delimiter),
325
+ };
326
+ }
327
+ function readJson(file, failures, code) {
328
+ try {
329
+ return JSON.parse(fs.readFileSync(file, "utf8"));
330
+ }
331
+ catch (error) {
332
+ failures.push({ code, message: `failed to read ${file}: ${error.message}` });
333
+ return undefined;
334
+ }
335
+ }
336
+ function expectEqual(failures, code, actual, expected, message) {
337
+ if (actual !== expected)
338
+ failures.push({ code, actual, expected, message });
339
+ }
340
+ function parseImportSpecifiers(content) {
341
+ const specifiers = [];
342
+ for (const match of content.matchAll(/\bfrom\s*["']([^"']+)["']/g))
343
+ specifiers.push(match[1]);
344
+ for (const match of content.matchAll(/\bimport\s*["']([^"']+)["']/g))
345
+ specifiers.push(match[1]);
346
+ for (const match of content.matchAll(/\bimport\s*\(\s*["']([^"']+)["']/g))
347
+ specifiers.push(match[1]);
348
+ return specifiers;
349
+ }
350
+ function collectFiles(directory) {
351
+ const files = [];
352
+ if (!fs.existsSync(directory))
353
+ return files;
354
+ walk(directory, files);
355
+ return files.sort();
356
+ }
357
+ function walk(current, files) {
358
+ const stat = fs.lstatSync(current);
359
+ if (stat.isSymbolicLink())
360
+ return;
361
+ if (stat.isDirectory()) {
362
+ for (const entry of fs.readdirSync(current))
363
+ walk(path.join(current, entry), files);
364
+ return;
365
+ }
366
+ if (stat.isFile())
367
+ files.push(current);
368
+ }
369
+ function toPosix(value) {
370
+ return value.split(path.sep).join("/");
371
+ }
372
+ function parseArgs(argv) {
373
+ const options = { json: false, runPack: true, runInstallSmoke: true, runCommandMatrix: true, projectRoot: defaultProjectRoot };
374
+ for (let index = 0; index < argv.length; index += 1) {
375
+ const arg = argv[index];
376
+ if (arg === "--json")
377
+ options.json = true;
378
+ else if (arg === "--skip-pack")
379
+ options.runPack = false;
380
+ else if (arg === "--skip-install-smoke")
381
+ options.runInstallSmoke = false;
382
+ else if (arg === "--skip-command-matrix")
383
+ options.runCommandMatrix = false;
384
+ else if (arg === "--project-root") {
385
+ options.projectRoot = path.resolve(requireValue(argv, index, arg));
386
+ index += 1;
387
+ }
388
+ else {
389
+ throw new Error(`Unknown check-dist-observation option: ${arg}`);
390
+ }
391
+ }
392
+ return options;
393
+ }
394
+ function requireValue(argv, index, option) {
395
+ const value = argv[index + 1];
396
+ if (!value)
397
+ throw new Error(`${option} requires a value`);
398
+ return value;
399
+ }
400
+ function isMainModule() {
401
+ if (!process.argv[1])
402
+ return false;
403
+ try {
404
+ return fs.realpathSync.native(fileURLToPath(import.meta.url)) === fs.realpathSync.native(process.argv[1]);
405
+ }
406
+ catch {
407
+ return import.meta.url === pathToFileURL(process.argv[1]).href;
408
+ }
409
+ }
410
+ if (isMainModule()) {
411
+ let options;
412
+ try {
413
+ options = parseArgs(process.argv.slice(2));
414
+ }
415
+ catch (error) {
416
+ console.error(error.message);
417
+ process.exit(1);
418
+ }
419
+ const result = checkDistObservation(options);
420
+ if (options.json)
421
+ console.log(JSON.stringify(result, null, 2));
422
+ else if (result.ok)
423
+ console.log(`Dist observation gate passed: ${options.projectRoot}`);
424
+ else
425
+ console.error(result.failures.map((failure) => failure.message).join("\n"));
426
+ if (!result.ok)
427
+ process.exit(1);
428
+ }