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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0
4
+
5
+ - Document the Node.js 24+ runtime baseline as a release-significant change and
6
+ move the next publish line to `1.1.0` instead of another patch release.
7
+ - Add a `prepublishOnly` release gate that uses the source-safe dist bootstrap
8
+ to rebuild `dist/` and run the dist observation checker before `npm publish`.
9
+ - Make the source-safe `postinstall.mjs` bootstrap fail with the package-level
10
+ missing-dist message when installed package contents are incomplete, instead
11
+ of attempting a source checkout build script that is not shipped.
12
+
13
+ ## 1.0.8
14
+
15
+ - Preserve the executable bit for the packaged `dist/harness.mjs` npm bin
16
+ entry during dist builds and prepack.
17
+ - Extend release observation checks to fail if the packed or installed
18
+ `harness` bin is not executable.
19
+ - Add `migrate-structure --plan` to the dist and installed-package command
20
+ smoke matrix.
21
+
22
+ ## 1.0.7
23
+
24
+ - Generate `harness init --add-npm-scripts` commands with
25
+ `npx --yes coding-agent-harness ...` so target projects can run Harness
26
+ scripts without adding a project dependency or relying on a global install.
27
+ - Isolate the dist observation command matrix from local ignored docs by using
28
+ the minimal target-project fixture for target-facing commands.
29
+ - Preserve the executable bit on the committed `dist/harness.mjs` CLI entry.
30
+
31
+ ## 1.0.6
32
+
33
+ - Bump the npm package version after the 1.0.5 publication so the TypeScript
34
+ runtime-source migration can be published again.
35
+
3
36
  ## 1.0.5
4
37
 
5
38
  - Relicense the public package from MIT to AGPL-3.0-or-later.
package/CONTRIBUTING.md CHANGED
@@ -4,8 +4,8 @@ Thanks for helping improve Coding Agent Harness. This repository contains the pu
4
4
 
5
5
  ## Before You Start
6
6
 
7
- - Use Node.js 18 or newer. CI currently runs on Node.js 20.
8
- - Install root dependencies with `npm install` from the repository root.
7
+ - Use Node.js 24 or newer. CI should run on the minimum supported line.
8
+ - Install root dependencies with `npm install` from the repository root. The install lifecycle generates the local `dist/` runtime; `dist/` is not tracked in Git.
9
9
  - If you change `harness-gui`, also run `npm ci` inside `harness-gui/`.
10
10
  - Keep pull requests focused. Separate documentation, CLI/runtime, template, preset, and GUI work when the changes are independent.
11
11
 
@@ -14,6 +14,7 @@ Thanks for helping improve Coding Agent Harness. This repository contains the pu
14
14
  | Path | Purpose |
15
15
  | --- | --- |
16
16
  | `scripts/` | Public CLI and implementation modules. |
17
+ | `dist/` | Generated runtime output. It is published to npm but ignored in Git. |
17
18
  | `tests/` | Root package tests and dashboard smoke tests. |
18
19
  | `templates/`, `templates-zh-CN/` | Harness templates installed into target projects. |
19
20
  | `presets/` | Bundled Harness preset packages. |
@@ -39,8 +40,8 @@ docs row. For larger PRs or when you are unsure, run the full root suite.
39
40
  | Change type | Minimum local checks |
40
41
  | --- | --- |
41
42
  | Docs only | `git diff --check` |
42
- | CLI/runtime | `npm test`, `npm run check`, `git diff --check` |
43
- | Templates or examples | `npm test`, `node dist/harness.mjs check --profile target-project examples/minimal-project`, `git diff --check` |
43
+ | CLI/runtime | `npm run typecheck`, `npm run typecheck:guards`, `npm test`, `npm run check`, `git diff --check` |
44
+ | Templates or examples | `npm test`, `npm run build:runtime`, `node dist/harness.mjs check --profile target-project examples/minimal-project`, `git diff --check` |
44
45
  | Dashboard | `npm test`, `npm run smoke:dashboard`, `git diff --check` |
45
46
  | Package surface | `npm test`, `npm run pack:dry-run`, `git diff --check` |
46
47
  | GUI submodule | `cd harness-gui && npm ci && npm run typecheck && npm test && npm run build` |
@@ -49,6 +50,9 @@ Full root suite:
49
50
 
50
51
  ```bash
51
52
  npm install
53
+ npm run build:runtime
54
+ npm run typecheck
55
+ npm run typecheck:guards
52
56
  npm test
53
57
  npm run smoke:dashboard
54
58
  npm run check
@@ -93,6 +97,6 @@ Use draft PRs for work that still needs design review, incomplete checks, or fol
93
97
 
94
98
  ## CI Expectations
95
99
 
96
- GitHub Actions validates the root package, source/package boundary, minimal target project, dashboard smoke path, npm package dry run, and GUI submodule typecheck/test/build path. A local run should match the CI commands closely enough that failures are reproducible.
100
+ GitHub Actions validates the root TypeScript integrity gate (`npm run typecheck` and `npm run typecheck:guards`), root package tests, source/package boundary, minimal target project, dashboard smoke path, npm package dry run, and GUI submodule typecheck/test/build path. A local run should match the CI commands closely enough that failures are reproducible.
97
101
 
98
102
  Repository owners may configure branch protection and required checks separately in GitHub. Contributors do not need to manage those settings.
package/README.md CHANGED
@@ -14,6 +14,8 @@ English | [简体中文](README.zh-CN.md) | [日本語](docs-release/intl/ja-JP.
14
14
 
15
15
  Coding Agent Harness is not another collection of chat prompts. It turns the durable facts that coding agents need into repository files: entry agreements, task plans, execution evidence, regression results, dashboards, and closeout records.
16
16
 
17
+ Requires Node.js 24 or newer.
18
+
17
19
  The smallest loop is:
18
20
 
19
21
  - A human states the goal, and the agent reads the repository Harness first.
@@ -96,6 +98,14 @@ runtime. Target projects should not treat `planning/**/_task-template` or
96
98
  migration removes those generated legacy template directories when it finds
97
99
  them.
98
100
 
101
+ Modules are registered in the root `harness.yaml` under `modules.items`.
102
+ `harness module register` creates only the module-owned `brief.md` and
103
+ `module_plan.md`, then regenerates `planning/modules/Module-Registry.md` as a
104
+ read-only view. Task execution files such as `execution_strategy.md`,
105
+ `visual_map.md`, `review.md`, and `walkthrough.md` are created under concrete
106
+ project or module task directories, including
107
+ `planning/modules/<key>/tasks/<task-id>/`.
108
+
99
109
  ### Safe Migration For Existing Projects
100
110
 
101
111
  Legacy project migration starts with a scan, a migration plan, a recommended migration mode, and user confirmation. Only then should the agent write files. Final status is proven with a dashboard and checks.
@@ -340,7 +350,7 @@ When the migration is complete, report the dynamic workbench URL or static dashb
340
350
 
341
351
  ## Contributing
342
352
 
343
- External contributors should start with [`CONTRIBUTING.md`](CONTRIBUTING.md). It covers repository layout, pull request expectations, root package checks, Dashboard smoke tests, npm package dry runs, and GUI submodule validation. The detailed public workflow also lives in [`docs-release/guides/contributing.md`](docs-release/guides/contributing.md).
353
+ External contributors should start with [`CONTRIBUTING.md`](CONTRIBUTING.md). It covers repository layout, pull request expectations, enforced TypeScript integrity checks, root package checks, Dashboard smoke tests, npm package dry runs, and GUI submodule validation. The detailed public workflow also lives in [`docs-release/guides/contributing.md`](docs-release/guides/contributing.md).
344
354
 
345
355
  If you want your coding agent to make a contribution, send it this prompt:
346
356
 
@@ -351,7 +361,7 @@ Start from the latest main branch and create a new feature branch. Read README.m
351
361
 
352
362
  Keep the change scoped. Use only public repository files and do not rely on maintainer-local state, hidden workflows, credentials, generated dashboards, temporary files, or ignored local-only files.
353
363
 
354
- Run the checks that match the change. For docs-only changes, run git diff --check. For root package changes, run npm install, npm test, npm run smoke:dashboard, npm run check, node dist/harness.mjs check --profile target-project examples/minimal-project, npm run pack:dry-run, and git diff --check as relevant. If the change touches harness-gui, also run cd harness-gui && npm ci && npm run typecheck && npm test && npm run build.
364
+ Run the checks that match the change. For docs-only changes, run git diff --check. For root package changes, run npm install, npm run build:runtime, npm run typecheck, npm run typecheck:guards, npm test, npm run smoke:dashboard, npm run check, node dist/harness.mjs check --profile target-project examples/minimal-project, npm run pack:dry-run, and git diff --check as relevant. If the change touches harness-gui, also run cd harness-gui && npm ci && npm run typecheck && npm test && npm run build. The source repository ignores `dist/`; npm install, prepare, prepack, and the root npm scripts regenerate it when needed.
355
365
 
356
366
  When done, summarize what changed, list verification results, call out any skipped checks with reasons, and prepare the PR using the repository template.
357
367
  ```
package/README.zh-CN.md CHANGED
@@ -14,6 +14,8 @@
14
14
 
15
15
  Coding Agent Harness 不是另一个聊天提示词集合。它把 Agent 长程开发需要依赖的事实沉淀到仓库:入口协议、任务计划、执行证据、回归结果、Dashboard 和收口记录。
16
16
 
17
+ 需要 Node.js 24 或更新版本。
18
+
17
19
  最小闭环是:
18
20
 
19
21
  - 人提出目标,Agent 先读仓库里的 Harness 协议。
@@ -86,6 +88,12 @@ Harness 自带内置 Preset,`harness init` 会把它们 seed 到目标项目
86
88
  默认任务模板和模块模板来自当前安装的 npm 包,在命令运行时读取。目标项目不应该把
87
89
  `planning/**/_task-template` 或 `planning/**/_module-template` 当作活跃状态;v2 结构迁移发现这些旧生成模板目录时会直接清理。
88
90
 
91
+ 模块统一注册在根 `harness.yaml` 的 `modules.items` 里。`harness module register`
92
+ 只创建模块根目录拥有的 `brief.md` 和 `module_plan.md`,然后把
93
+ `planning/modules/Module-Registry.md` 重新生成为只读视图。`execution_strategy.md`、
94
+ `visual_map.md`、`review.md`、`walkthrough.md` 等执行合同属于具体任务目录,例如
95
+ `planning/modules/<key>/tasks/<task-id>/`。
96
+
89
97
  ### 旧项目也能迁移
90
98
 
91
99
  旧项目迁移不是直接套模板。标准流程是:先扫描项目,生成迁移计划,推荐迁移模式,向用户提问确认,再执行迁移,最后用 Dashboard 和检查结果证明迁移状态。
@@ -320,7 +328,7 @@ npx --yes coding-agent-harness new-task --budget complex --preset legacy-migrati
320
328
 
321
329
  ## 参与贡献
322
330
 
323
- 外部贡献者请先阅读 [`CONTRIBUTING.md`](CONTRIBUTING.md)。它说明仓库结构、PR 要求、根包检查、Dashboard smoke test、npm package dry run 和 GUI 子模块验证。中文详细流程见 [`docs-release/guides/contributing.zh-CN.md`](docs-release/guides/contributing.zh-CN.md)。
331
+ 外部贡献者请先阅读 [`CONTRIBUTING.md`](CONTRIBUTING.md)。它说明仓库结构、PR 要求、enforced TypeScript integrity 检查、根包检查、Dashboard smoke test、npm package dry run 和 GUI 子模块验证。中文详细流程见 [`docs-release/guides/contributing.zh-CN.md`](docs-release/guides/contributing.zh-CN.md)。
324
332
 
325
333
  如果你想让自己的 Coding Agent 帮你改这个仓库,可以把下面这段发给它:
326
334
 
@@ -331,7 +339,7 @@ npx --yes coding-agent-harness new-task --budget complex --preset legacy-migrati
331
339
 
332
340
  改动要保持聚焦。只使用公开仓库文件;不要依赖维护者本地状态、隐藏工作流、凭据、生成的 Dashboard、临时文件或被 ignore 的本地专用文件。
333
341
 
334
- 根据改动范围运行检查。仅文档改动至少运行 git diff --check。根包相关改动按需运行 npm install、npm test、npm run smoke:dashboard、npm run check、node dist/harness.mjs check --profile target-project examples/minimal-project、npm run pack:dry-run 和 git diff --check。如果改到 harness-gui,还要运行 cd harness-gui && npm ci && npm run typecheck && npm test && npm run build
342
+ 根据改动范围运行检查。仅文档改动至少运行 git diff --check。根包相关改动按需运行 npm install、npm run build:runtime、npm run typecheck、npm run typecheck:guards、npm test、npm run smoke:dashboard、npm run check、node dist/harness.mjs check --profile target-project examples/minimal-project、npm run pack:dry-run 和 git diff --check。如果改到 harness-gui,还要运行 cd harness-gui && npm ci && npm run typecheck && npm test && npm run build。源码仓不跟踪 `dist/`;npm install、prepare、prepack 和根仓 npm scripts 会按需重新生成。
335
343
 
336
344
  完成后,请总结改了什么,列出验证结果,说明任何未运行检查及原因,并按仓库 PR 模板准备 PR。
337
345
  ```
package/SKILL.md CHANGED
@@ -31,6 +31,16 @@ description: >
31
31
  - **严肃项目用顶级模型。** 便宜模型的返工成本远高于差价。
32
32
  - **强制流程优于口头约定。** 每个步骤都应该是 agent 可自主执行的。
33
33
 
34
+ ## Product Contract
35
+
36
+ Full Harness 与后续 Lite Skill 的共享任务语义由 Document Contract Kernel
37
+ 约束,公开源位于 `docs-release/architecture/document-contract-kernel/README.md`。
38
+ 凡是改变 AGENTS、context、task package、progress、walkthrough、review、
39
+ regression 或 lessons 基础语义的改动,必须按该 contract 同步 Full/Lite
40
+ overlay 与 compatibility matrix;Full-only 的 CLI、Dashboard、Preset、
41
+ generated ledger、Module Registry、lifecycle command、runtime requirement
42
+ 不得泄漏进 Lite source。
43
+
34
44
  ---
35
45
 
36
46
  ## 主执行 SOP
@@ -64,7 +74,8 @@ CLI 示例默认使用目标项目可调用的 `harness` 命令。执行前先
64
74
  `command -v harness`;如果没有,不要静默全局安装,按安装指南询问用户是否
65
75
  允许 `npm install -g coding-agent-harness`。未获明确同意时,用
66
76
  `npx --yes coding-agent-harness <command>` 执行同一条命令。只有维护本源码
67
- checkout 时,才把 `harness` 替换为 `node dist/harness.mjs`。
77
+ checkout 时,才把 `harness` 替换为 `node dist/harness.mjs`;源码仓的
78
+ `dist/` 是生成物,若不存在先运行 `npm install` 或 `npm run build:runtime`。
68
79
 
69
80
  ### Agent 安装合同
70
81
 
@@ -252,7 +263,7 @@ harness bootstrap 完成后,项目中至少应存在以下文件:
252
263
  - [ ] `coding-agent-harness/governance/standards/walkthrough-template.md`
253
264
  - [ ] `coding-agent-harness/governance/generated/Closeout-Index.md`
254
265
  - [ ] `coding-agent-harness/governance/lessons/`(空目录 + .gitkeep)
255
- - [ ] `coding-agent-harness/governance/_archive/`(空目录 + .gitkeep)
266
+ - [ ] `coding-agent-harness/governance/archive/`(空目录 + .gitkeep)
256
267
  - [ ] `coding-agent-harness/governance/generated/Harness-Ledger.md`
257
268
  - [ ] `coding-agent-harness/governance/standards/external-source-intake-standard.md`
258
269
  - [ ] `coding-agent-harness/governance/standards/harness-ledger-standard.md`
@@ -339,7 +350,7 @@ harness 搭建完成后,每个 feature 从想法到代码的标准流程:
339
350
  | Long-Running Task Contract | `templates/planning/long-running-task-contract.md` | 长程任务授权、review loop 和停止条件 |
340
351
  | Module Session Prompt | `templates/planning/module_session_prompt.md` | 模块并行开发会话冷启动 |
341
352
  | Walkthrough | `templates/walkthrough/walkthrough-template.md` | 任务收口记录 |
342
- | Closeout Index | `templates/walkthrough/walkthrough-template.md` | closed task 索引和收口证据 |
353
+ | Closeout Index | `governance rebuild` generated output | `governance/generated/Closeout-Index.md` task closeout metadata 重建,无独立模板 |
343
354
  | Testing Standard | `templates/reference/testing-standard.md` | 测试、冒烟和回归规范 |
344
355
  | Execution Workflow | `templates/reference/execution-workflow-standard.md` | 执行、提交、PR 和证据记录 |
345
356
  | Delivery Operating Model Standard | `templates/reference/delivery-operating-model-standard.md` | 交付组织模型选择 |
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from "node:fs";
4
3
  import os from "node:os";
5
4
  import path from "node:path";
6
5
  import { spawnSync } from "node:child_process";
7
- import { fileURLToPath } from "node:url";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
7
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
8
  const typescriptVersion = "5.9.3";
10
9
  export function buildRuntimeDist({ projectRoot = repoRoot, configPath = path.join(projectRoot, "tsconfig.dist.json"), outDir = path.join(projectRoot, "dist"), } = {}) {
@@ -28,14 +27,18 @@ export function buildRuntimeDist({ projectRoot = repoRoot, configPath = path.joi
28
27
  };
29
28
  }
30
29
  fs.rmSync(buildOutDir, { recursive: true, force: true });
31
- const emit = spawnSync("npm", ["exec", "--yes", "--package", `typescript@${typescriptVersion}`, "--", "tsc", "-p", absoluteConfig, "--outDir", buildOutDir, "--noCheck"], {
30
+ const npmArgs = ["exec", "--yes", "--package", `typescript@${typescriptVersion}`, "--", "tsc", "-p", absoluteConfig, "--outDir", buildOutDir, "--noCheck"];
31
+ const npmExecPath = process.env.npm_execpath;
32
+ const command = npmExecPath ? process.execPath : process.platform === "win32" ? "npm.cmd" : "npm";
33
+ const commandArgs = npmExecPath ? [npmExecPath, ...npmArgs] : npmArgs;
34
+ const emit = spawnSync(command, commandArgs, {
32
35
  cwd: absoluteProjectRoot,
33
36
  encoding: "utf8",
34
37
  });
35
38
  if (emit.status !== 0) {
36
39
  return {
37
40
  ok: false,
38
- error: `TypeScript dist build failed\nSTDOUT:\n${emit.stdout}\nSTDERR:\n${emit.stderr}`,
41
+ error: `TypeScript dist build failed${emit.error ? `\nERROR:\n${emit.error.message}` : ""}\nSTDOUT:\n${emit.stdout ?? ""}\nSTDERR:\n${emit.stderr ?? ""}`,
39
42
  status: emit.status,
40
43
  };
41
44
  }
@@ -43,6 +46,10 @@ export function buildRuntimeDist({ projectRoot = repoRoot, configPath = path.joi
43
46
  syncDirectory(buildOutDir, absoluteOutDir);
44
47
  fs.rmSync(buildOutDir, { recursive: true, force: true });
45
48
  }
49
+ restoreExecutableEntrypoints({
50
+ outDir: absoluteOutDir,
51
+ binRelativePaths: ["harness.mjs"],
52
+ });
46
53
  const files = collectFiles(absoluteOutDir).filter((file) => file.endsWith(".mjs")).sort();
47
54
  const relativeFiles = files.map((file) => toPosix(path.relative(absoluteOutDir, file)));
48
55
  const requiredFiles = [
@@ -126,6 +133,15 @@ function syncDirectory(sourceDir, targetDir) {
126
133
  fs.rmSync(path.join(targetDir, entry), { recursive: true, force: true });
127
134
  }
128
135
  }
136
+ function restoreExecutableEntrypoints({ outDir, binRelativePaths }) {
137
+ for (const relativePath of binRelativePaths) {
138
+ const file = path.join(outDir, relativePath);
139
+ if (!fs.existsSync(file))
140
+ continue;
141
+ const stat = fs.statSync(file);
142
+ fs.chmodSync(file, stat.mode | 0o755);
143
+ }
144
+ }
129
145
  function toPosix(value) {
130
146
  return value.split(path.sep).join("/");
131
147
  }
@@ -165,13 +181,23 @@ function requireValue(argv, index, option) {
165
181
  throw new Error(`${option} requires a value`);
166
182
  return value;
167
183
  }
168
- if (import.meta.url === `file://${process.argv[1]}`) {
184
+ function isMainModule() {
185
+ if (!process.argv[1])
186
+ return false;
187
+ try {
188
+ return fs.realpathSync.native(fileURLToPath(import.meta.url)) === fs.realpathSync.native(process.argv[1]);
189
+ }
190
+ catch {
191
+ return import.meta.url === pathToFileURL(process.argv[1]).href;
192
+ }
193
+ }
194
+ if (isMainModule()) {
169
195
  let options;
170
196
  try {
171
197
  options = parseArgs(process.argv.slice(2));
172
198
  }
173
199
  catch (error) {
174
- console.error(error.message);
200
+ console.error(error instanceof Error ? error.message : String(error));
175
201
  process.exit(1);
176
202
  }
177
203
  const result = buildRuntimeDist(options);
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from "node:fs";
4
3
  import os from "node:os";
5
4
  import path from "node:path";
@@ -21,16 +20,18 @@ export function checkDistObservation({ projectRoot = defaultProjectRoot, runPack
21
20
  return { ok: false, failures, observations };
22
21
  expectEqual(failures, "package-bin-not-dist", pkg.bin?.harness, "dist/harness.mjs", "package bin.harness must resolve to dist/harness.mjs");
23
22
  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",
23
+ check: ["run-dist.mjs", "harness.mjs", "check", "--profile", "source-package"],
24
+ "check:private": ["run-dist.mjs", "harness.mjs", "check", "--profile", "private-harness"],
25
+ status: ["run-dist.mjs", "harness.mjs", "status", "--json"],
26
+ dashboard: ["run-dist.mjs", "harness.mjs", "dashboard", "--out"],
27
+ "dashboard:folder": ["run-dist.mjs", "harness.mjs", "dashboard", "--out-dir"],
28
+ postinstall: ["postinstall.mjs"],
29
+ prepare: ["postinstall.mjs", "--build-only"],
30
+ prepublishOnly: ["run-dist.mjs", "check-dist-observation.mjs", "--skip-install-smoke"],
31
+ "observe:dist": ["run-dist.mjs", "check-dist-observation.mjs", "--skip-pack", "--skip-install-smoke"],
31
32
  };
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`);
33
+ for (const [name, tokens] of Object.entries(distRuntimeScripts)) {
34
+ expectScriptIncludes(failures, `package-script-${name}-not-source-safe-dist`, pkg.scripts?.[name], tokens, `package script ${name} must run through the source-safe dist bootstrap`);
34
35
  }
35
36
  observations.packageRuntime = {
36
37
  bin: pkg.bin?.harness,
@@ -48,20 +49,30 @@ export function checkDistObservation({ projectRoot = defaultProjectRoot, runPack
48
49
  failures.push({ code: "pack-dry-run-failed", message: `npm pack dry-run failed\nSTDOUT:\n${pack.stdout}\nSTDERR:\n${pack.stderr}` });
49
50
  }
50
51
  else {
51
- const packed = JSON.parse(pack.stdout)[0].files.map((file) => file.path).sort();
52
+ const packedEntries = JSON.parse(pack.stdout)[0]?.files ?? [];
53
+ const packed = packedEntries.map((file) => file.path).sort();
54
+ const packedModeByPath = new Map(packedEntries.map((file) => [file.path, file.mode]));
55
+ const distHarnessMode = packedModeByPath.get("dist/harness.mjs");
52
56
  observations.package = {
53
57
  entryCount: packed.length,
54
58
  hasDistHarness: packed.includes("dist/harness.mjs"),
55
59
  hasDistPostinstall: packed.includes("dist/postinstall.mjs"),
56
60
  hasDistObservationGate: packed.includes("dist/check-dist-observation.mjs"),
61
+ hasPostinstallBootstrap: packed.includes("postinstall.mjs"),
62
+ hasRunDistBootstrap: packed.includes("run-dist.mjs"),
57
63
  hasScriptsHarness: packed.includes("scripts/harness.mjs"),
58
64
  hasScripts: packed.some((file) => file.startsWith("scripts/")),
59
65
  hasTests: packed.some((file) => file.startsWith("tests/")),
66
+ distHarnessMode,
67
+ distHarnessExecutable: typeof distHarnessMode === "number" && Boolean(distHarnessMode & 0o111),
60
68
  };
61
- for (const required of ["dist/harness.mjs", "dist/postinstall.mjs", "dist/check-dist-observation.mjs"]) {
69
+ for (const required of ["postinstall.mjs", "run-dist.mjs", "dist/harness.mjs", "dist/postinstall.mjs", "dist/check-dist-observation.mjs"]) {
62
70
  if (!packed.includes(required))
63
71
  failures.push({ code: "packed-file-missing", file: required, message: `package missing ${required}` });
64
72
  }
73
+ if (!observations.package.distHarnessExecutable) {
74
+ failures.push({ code: "packed-bin-not-executable", file: "dist/harness.mjs", mode: distHarnessMode, message: "package bin dist/harness.mjs must be executable" });
75
+ }
65
76
  if (observations.package.hasScripts)
66
77
  failures.push({ code: "package-includes-scripts", message: "package must not include scripts/** after historical shim deletion" });
67
78
  if (observations.package.hasTests)
@@ -133,6 +144,7 @@ function runMatrix(root, failures, commandMatrix) {
133
144
  { id: "source-check", args: ["check", "--profile", "source-package", "."] },
134
145
  { id: "target-check", args: ["check", "--profile", "target-project", "examples/minimal-project"] },
135
146
  { id: "migrate-plan", args: ["migrate-plan", "--json", "--limit", "20", "examples/minimal-project"] },
147
+ { id: "migrate-structure-plan", args: ["migrate-structure", "--plan", "--json", "examples/minimal-project"] },
136
148
  { id: "dashboard", args: ["dashboard", "--out-dir", path.join("tmp", `pr-27-observation-dashboard-${process.pid}`), "examples/minimal-project"] },
137
149
  ];
138
150
  for (const entry of matrix) {
@@ -193,7 +205,12 @@ function runInstalledPackageSmoke(root, failures, observations) {
193
205
  failures.push({ code: "install-smoke-pack-failed", message: `npm pack failed\nSTDOUT:\n${pack.stdout}\nSTDERR:\n${pack.stderr}` });
194
206
  return;
195
207
  }
196
- const tarball = path.join(packDir, pack.stdout.trim().split(/\r?\n/).at(-1));
208
+ const tarballName = pack.stdout.trim().split(/\r?\n/).at(-1);
209
+ if (!tarballName) {
210
+ failures.push({ code: "install-smoke-pack-empty", message: "npm pack did not print a tarball name" });
211
+ return;
212
+ }
213
+ const tarball = path.join(packDir, tarballName);
197
214
  fs.writeFileSync(path.join(consumer, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2));
198
215
  const install = spawnSync("npm", ["install", "--silent", "--no-audit", "--no-fund", tarball], {
199
216
  cwd: consumer,
@@ -211,11 +228,15 @@ function runInstalledPackageSmoke(root, failures, observations) {
211
228
  if (!pkg)
212
229
  return;
213
230
  const binTarget = fs.existsSync(bin) ? fs.readlinkSync(bin) : "";
214
- observations.installSmoke = {
231
+ const installedBinFile = path.join(packageRoot, "dist/harness.mjs");
232
+ const installedBinMode = fs.existsSync(installedBinFile) ? fs.statSync(installedBinFile).mode : undefined;
233
+ const installSmoke = {
215
234
  nodeVersion,
216
235
  tempRoot,
217
236
  binTarget,
218
237
  bin: pkg.bin?.harness,
238
+ binMode: installedBinMode,
239
+ binExecutable: typeof installedBinMode === "number" && Boolean(installedBinMode & 0o111),
219
240
  postinstall: pkg.scripts?.postinstall,
220
241
  observeDist: pkg.scripts?.["observe:dist"],
221
242
  hasTests: fs.existsSync(path.join(packageRoot, "tests")),
@@ -223,48 +244,60 @@ function runInstalledPackageSmoke(root, failures, observations) {
223
244
  scriptsDisabled: [],
224
245
  steps: [],
225
246
  };
247
+ observations.installSmoke = installSmoke;
226
248
  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");
249
+ expectScriptIncludes(failures, "installed-postinstall-not-source-safe", pkg.scripts?.postinstall, ["postinstall.mjs"], "installed package postinstall must use the source-safe bootstrap");
250
+ expectScriptIncludes(failures, "installed-observe-dist-not-source-safe", pkg.scripts?.["observe:dist"], ["run-dist.mjs", "check-dist-observation.mjs"], "installed observe:dist must use the source-safe dist bootstrap");
229
251
  if (!binTarget.includes("dist/harness.mjs")) {
230
252
  failures.push({ code: "installed-bin-link-not-dist", message: `installed bin link does not target dist/harness.mjs: ${binTarget}` });
231
253
  }
232
- for (const relative of ["dist/harness.mjs", "dist/postinstall.mjs", "dist/check-dist-observation.mjs"]) {
254
+ if (!installSmoke.binExecutable) {
255
+ failures.push({ code: "installed-bin-not-executable", file: "dist/harness.mjs", mode: installedBinMode, message: "installed package bin dist/harness.mjs must be executable" });
256
+ }
257
+ for (const relative of ["postinstall.mjs", "run-dist.mjs", "dist/harness.mjs", "dist/postinstall.mjs", "dist/check-dist-observation.mjs"]) {
233
258
  if (!fs.existsSync(path.join(packageRoot, relative)))
234
259
  failures.push({ code: "installed-file-missing", file: relative, message: `installed package missing ${relative}` });
235
260
  }
236
- if (observations.installSmoke.hasTests)
261
+ if (installSmoke.hasTests)
237
262
  failures.push({ code: "installed-package-includes-tests", message: "installed package must not include tests/**" });
238
- if (observations.installSmoke.hasScripts)
263
+ if (installSmoke.hasScripts)
239
264
  failures.push({ code: "installed-package-includes-scripts", message: "installed package must not include scripts/** after historical shim deletion" });
240
265
  const installedScripts = path.join(packageRoot, "scripts");
241
266
  if (fs.existsSync(installedScripts)) {
242
267
  fs.renameSync(installedScripts, `${installedScripts}.disabled-by-dist-observation`);
243
- observations.installSmoke.scriptsDisabled.push("scripts/");
268
+ installSmoke.scriptsDisabled.push("scripts/");
244
269
  }
245
270
  const runtimeEnv = isolatedEnv({ nodeBin, home, extraPath: [path.join(consumer, "node_modules", ".bin")] });
246
- runInstalledMatrix(root, runtimeEnv, failures, observations.installSmoke.steps);
271
+ runInstalledMatrix(root, runtimeEnv, failures, installSmoke.steps);
247
272
  const postinstall = spawnSync(node24, [path.join(packageRoot, "dist/postinstall.mjs")], {
248
273
  cwd: packageRoot,
249
274
  encoding: "utf8",
250
275
  env: { ...runtimeEnv, CODING_AGENT_HARNESS_SKIP_POSTINSTALL: "1" },
251
276
  });
252
- observations.installSmoke.steps.push({ id: "installed-dist-postinstall", status: postinstall.status });
277
+ installSmoke.steps.push({ id: "installed-dist-postinstall", status: postinstall.status });
253
278
  if (postinstall.status !== 0)
254
279
  failures.push({ code: "installed-postinstall-failed", message: `installed dist postinstall failed\nSTDOUT:\n${postinstall.stdout}\nSTDERR:\n${postinstall.stderr}` });
280
+ const postinstallBootstrap = spawnSync(node24, [path.join(packageRoot, "postinstall.mjs")], {
281
+ cwd: packageRoot,
282
+ encoding: "utf8",
283
+ env: { ...runtimeEnv, CODING_AGENT_HARNESS_SKIP_POSTINSTALL: "1" },
284
+ });
285
+ installSmoke.steps.push({ id: "installed-postinstall-bootstrap", status: postinstallBootstrap.status });
286
+ if (postinstallBootstrap.status !== 0)
287
+ failures.push({ code: "installed-postinstall-bootstrap-failed", message: `installed postinstall bootstrap failed\nSTDOUT:\n${postinstallBootstrap.stdout}\nSTDERR:\n${postinstallBootstrap.stderr}` });
255
288
  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
289
  cwd: packageRoot,
257
290
  encoding: "utf8",
258
291
  env: runtimeEnv,
259
292
  maxBuffer: 32 * 1024 * 1024,
260
293
  });
261
- observations.installSmoke.steps.push({ id: "installed-observation", status: installedObservation.status });
294
+ installSmoke.steps.push({ id: "installed-observation", status: installedObservation.status });
262
295
  if (installedObservation.status !== 0) {
263
296
  failures.push({ code: "installed-observation-failed", message: `installed observation failed\nSTDOUT:\n${installedObservation.stdout}\nSTDERR:\n${installedObservation.stderr}` });
264
297
  }
265
298
  else {
266
299
  const installedResult = JSON.parse(installedObservation.stdout);
267
- observations.installSmoke.observationOk = installedResult.ok;
300
+ installSmoke.observationOk = installedResult.ok;
268
301
  if (!installedResult.ok)
269
302
  failures.push({ code: "installed-observation-not-ok", message: JSON.stringify(installedResult.failures, null, 2) });
270
303
  }
@@ -278,6 +311,7 @@ function runInstalledMatrix(root, runtimeEnv, failures, steps) {
278
311
  { id: "installed-source-check", cwd: root, args: ["check", "--profile", "source-package", "."] },
279
312
  { id: "installed-target-check", cwd: root, args: ["check", "--profile", "target-project", "examples/minimal-project"] },
280
313
  { id: "installed-migrate-plan", cwd: root, args: ["migrate-plan", "--json", "--limit", "20", "examples/minimal-project"] },
314
+ { id: "installed-migrate-structure-plan", cwd: root, args: ["migrate-structure", "--plan", "--json", "examples/minimal-project"] },
281
315
  { id: "installed-dashboard", cwd: root, args: ["dashboard", "--out-dir", path.join("tmp", `pr-27-installed-observation-dashboard-${process.pid}`), "examples/minimal-project"] },
282
316
  ];
283
317
  for (const entry of matrix) {
@@ -306,7 +340,7 @@ function findNode24() {
306
340
  path.join(os.homedir(), ".nvm", "versions", "node", "v24.13.1", "bin", "node"),
307
341
  "/opt/homebrew/opt/node@24/bin/node",
308
342
  "/usr/local/opt/node@24/bin/node",
309
- ].filter(Boolean);
343
+ ].filter(isNonEmptyString);
310
344
  for (const candidate of candidates) {
311
345
  if (!fs.existsSync(candidate))
312
346
  continue;
@@ -316,7 +350,10 @@ function findNode24() {
316
350
  }
317
351
  return undefined;
318
352
  }
319
- function isolatedEnv({ nodeBin, home = process.env.HOME, extraPath = [] }) {
353
+ function isNonEmptyString(value) {
354
+ return typeof value === "string" && value.length > 0;
355
+ }
356
+ function isolatedEnv({ nodeBin, home = process.env.HOME || os.homedir(), extraPath = [], }) {
320
357
  return {
321
358
  ...process.env,
322
359
  HOME: home,
@@ -329,14 +366,22 @@ function readJson(file, failures, code) {
329
366
  return JSON.parse(fs.readFileSync(file, "utf8"));
330
367
  }
331
368
  catch (error) {
332
- failures.push({ code, message: `failed to read ${file}: ${error.message}` });
369
+ failures.push({ code, message: `failed to read ${file}: ${errorMessage(error)}` });
333
370
  return undefined;
334
371
  }
335
372
  }
373
+ function errorMessage(error) {
374
+ return error instanceof Error ? error.message : String(error);
375
+ }
336
376
  function expectEqual(failures, code, actual, expected, message) {
337
377
  if (actual !== expected)
338
378
  failures.push({ code, actual, expected, message });
339
379
  }
380
+ function expectScriptIncludes(failures, code, script, tokens, message) {
381
+ if (typeof script !== "string" || tokens.some((token) => !script.includes(token)) || script.includes("scripts/")) {
382
+ failures.push({ code, actual: script, expectedTokens: tokens, message });
383
+ }
384
+ }
340
385
  function parseImportSpecifiers(content) {
341
386
  const specifiers = [];
342
387
  for (const match of content.matchAll(/\bfrom\s*["']([^"']+)["']/g))
@@ -413,7 +458,7 @@ if (isMainModule()) {
413
458
  options = parseArgs(process.argv.slice(2));
414
459
  }
415
460
  catch (error) {
416
- console.error(error.message);
461
+ console.error(errorMessage(error));
417
462
  process.exit(1);
418
463
  }
419
464
  const result = checkDistObservation(options);
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
4
  import { checkModuleParallelStructure } from "./lib/check-module-parallel.mjs";