coding-agent-harness 1.0.8 → 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 (232) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/CONTRIBUTING.md +8 -4
  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 +19 -6
  7. package/dist/check-dist-observation.mjs +57 -29
  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 +7 -7
  12. package/dist/check-runtime-emit.mjs +10 -3
  13. package/dist/check-type-boundaries.mjs +51 -9
  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 +51 -12
  18. package/dist/commands/registry.mjs +483 -0
  19. package/dist/commands/task-command.mjs +109 -52
  20. package/dist/harness.mjs +6 -304
  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 +4 -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 +67 -29
  44. package/dist/lib/preset-registry.mjs +361 -71
  45. package/dist/lib/preset-runner.mjs +292 -26
  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 +3 -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 +116 -160
  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 +36 -17
  70. package/dist/lib/task-scanner.mjs +74 -23
  71. package/dist/lib/task-template-materials.mjs +131 -0
  72. package/dist/lib/task-tombstone-commands.mjs +186 -29
  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 +1 -0
  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 +16 -12
  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 +6 -1
  120. package/presets/release-closeout/preset.yaml +9 -9
  121. package/presets/release-closeout/scripts/generate-release-package.mjs +387 -25
  122. package/presets/release-closeout/templates/task_plan.append.md +5 -5
  123. package/presets/standard-task/preset.yaml +2 -2
  124. package/references/agents-md-pattern.md +23 -17
  125. package/references/lessons-governance.md +2 -2
  126. package/references/module-parallel-standard.md +3 -6
  127. package/references/ssot-governance.md +2 -2
  128. package/references/taskr-gap-analysis.md +3 -3
  129. package/run-dist.mjs +34 -0
  130. package/skills/preset-creator/SKILL.md +40 -8
  131. package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
  132. package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
  133. package/skills/preset-creator/references/structure-aware-paths.md +112 -0
  134. package/templates/AGENTS.md.template +28 -26
  135. package/templates/architecture/README.md +2 -2
  136. package/templates/architecture/service-catalog.md +2 -2
  137. package/templates/architecture/services/service-template.md +1 -1
  138. package/templates/dashboard/assets/app-src/00-state.js +5 -1
  139. package/templates/dashboard/assets/app-src/10-router.js +7 -0
  140. package/templates/dashboard/assets/app-src/20-overview.js +8 -8
  141. package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
  142. package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
  143. package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
  144. package/templates/dashboard/assets/app-src/40-modules.js +257 -41
  145. package/templates/dashboard/assets/app-src/45-review.js +127 -1
  146. package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
  147. package/templates/dashboard/assets/app.css +928 -53
  148. package/templates/dashboard/assets/app.css.manifest.json +2 -0
  149. package/templates/dashboard/assets/app.js +1071 -98
  150. package/templates/dashboard/assets/app.manifest.json +1 -0
  151. package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
  152. package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
  153. package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
  154. package/templates/dashboard/assets/css-src/31-archive.css +94 -0
  155. package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
  156. package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
  157. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
  158. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
  159. package/templates/dashboard/assets/i18n.js +166 -2
  160. package/templates/development/README.md +9 -9
  161. package/templates/development/cross-repo-debugging.md +3 -3
  162. package/templates/development/external-context/service-template.md +1 -1
  163. package/templates/development/external-source-packs/README.md +2 -2
  164. package/templates/integrations/README.md +4 -4
  165. package/templates/integrations/api-contract.md +1 -1
  166. package/templates/integrations/event-contract.md +1 -1
  167. package/templates/integrations/third-party/vendor-template.md +1 -1
  168. package/templates/integrations/webhook-contract.md +1 -1
  169. package/templates/ledger/Harness-Ledger.md +1 -1
  170. package/templates/modules/module_brief.md +50 -0
  171. package/templates/modules/module_plan.md +49 -0
  172. package/templates/modules/registry_view.md +9 -0
  173. package/templates/modules/session_prompt_pack.md +55 -0
  174. package/templates/planning/brief.md +32 -8
  175. package/templates/planning/module_brief.md +28 -3
  176. package/templates/planning/module_plan.md +26 -11
  177. package/templates/planning/module_session_prompt.md +11 -2
  178. package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
  179. package/templates/planning/review.md +1 -1
  180. package/templates/planning/visual_map.md +1 -1
  181. package/templates/reference/docs-library-standard.md +7 -7
  182. package/templates/reference/execution-workflow-standard.md +13 -0
  183. package/templates/reference/external-source-intake-standard.md +10 -10
  184. package/templates/reference/repo-governance-standard.md +1 -1
  185. package/templates/reference/review-routing-standard.md +4 -0
  186. package/templates/ssot/Module-Registry.md +4 -38
  187. package/templates/walkthrough/walkthrough-template.md +1 -1
  188. package/templates-zh-CN/AGENTS.md.template +27 -25
  189. package/templates-zh-CN/CLAUDE.md.template +1 -1
  190. package/templates-zh-CN/architecture/README.md +2 -2
  191. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  192. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  193. package/templates-zh-CN/development/README.md +9 -9
  194. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  195. package/templates-zh-CN/development/external-context/service-template.md +1 -1
  196. package/templates-zh-CN/development/external-source-packs/README.md +2 -2
  197. package/templates-zh-CN/integrations/README.md +4 -4
  198. package/templates-zh-CN/integrations/api-contract.md +1 -1
  199. package/templates-zh-CN/integrations/event-contract.md +1 -1
  200. package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
  201. package/templates-zh-CN/integrations/webhook-contract.md +1 -1
  202. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  203. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  204. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  205. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  206. package/templates-zh-CN/modules/module_brief.md +47 -0
  207. package/templates-zh-CN/modules/module_plan.md +48 -0
  208. package/templates-zh-CN/modules/registry_view.md +9 -0
  209. package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
  210. package/templates-zh-CN/planning/INDEX.md +1 -0
  211. package/templates-zh-CN/planning/brief.md +26 -7
  212. package/templates-zh-CN/planning/module_brief.md +24 -2
  213. package/templates-zh-CN/planning/module_plan.md +35 -29
  214. package/templates-zh-CN/planning/module_session_prompt.md +15 -11
  215. package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
  216. package/templates-zh-CN/planning/review.md +1 -1
  217. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  218. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  219. package/templates-zh-CN/reference/docs-library-standard.md +27 -27
  220. package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
  221. package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
  222. package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
  223. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  224. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  225. package/templates-zh-CN/reference/review-routing-standard.md +3 -0
  226. package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
  227. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  228. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  229. package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
  230. package/templates-zh-CN/ssot/Module-Registry.md +5 -44
  231. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  232. package/templates-zh-CN/walkthrough/walkthrough-template.md +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
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
+
3
13
  ## 1.0.8
4
14
 
5
15
  - Preserve the executable bit for the packaged `dist/harness.mjs` npm bin
package/CONTRIBUTING.md CHANGED
@@ -5,7 +5,7 @@ Thanks for helping improve Coding Agent Harness. This repository contains the pu
5
5
  ## Before You Start
6
6
 
7
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.
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
  }
@@ -178,13 +181,23 @@ function requireValue(argv, index, option) {
178
181
  throw new Error(`${option} requires a value`);
179
182
  return value;
180
183
  }
181
- 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()) {
182
195
  let options;
183
196
  try {
184
197
  options = parseArgs(process.argv.slice(2));
185
198
  }
186
199
  catch (error) {
187
- console.error(error.message);
200
+ console.error(error instanceof Error ? error.message : String(error));
188
201
  process.exit(1);
189
202
  }
190
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,7 +49,7 @@ 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 packedEntries = JSON.parse(pack.stdout)[0].files;
52
+ const packedEntries = JSON.parse(pack.stdout)[0]?.files ?? [];
52
53
  const packed = packedEntries.map((file) => file.path).sort();
53
54
  const packedModeByPath = new Map(packedEntries.map((file) => [file.path, file.mode]));
54
55
  const distHarnessMode = packedModeByPath.get("dist/harness.mjs");
@@ -57,13 +58,15 @@ export function checkDistObservation({ projectRoot = defaultProjectRoot, runPack
57
58
  hasDistHarness: packed.includes("dist/harness.mjs"),
58
59
  hasDistPostinstall: packed.includes("dist/postinstall.mjs"),
59
60
  hasDistObservationGate: packed.includes("dist/check-dist-observation.mjs"),
61
+ hasPostinstallBootstrap: packed.includes("postinstall.mjs"),
62
+ hasRunDistBootstrap: packed.includes("run-dist.mjs"),
60
63
  hasScriptsHarness: packed.includes("scripts/harness.mjs"),
61
64
  hasScripts: packed.some((file) => file.startsWith("scripts/")),
62
65
  hasTests: packed.some((file) => file.startsWith("tests/")),
63
66
  distHarnessMode,
64
67
  distHarnessExecutable: typeof distHarnessMode === "number" && Boolean(distHarnessMode & 0o111),
65
68
  };
66
- 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"]) {
67
70
  if (!packed.includes(required))
68
71
  failures.push({ code: "packed-file-missing", file: required, message: `package missing ${required}` });
69
72
  }
@@ -202,7 +205,12 @@ function runInstalledPackageSmoke(root, failures, observations) {
202
205
  failures.push({ code: "install-smoke-pack-failed", message: `npm pack failed\nSTDOUT:\n${pack.stdout}\nSTDERR:\n${pack.stderr}` });
203
206
  return;
204
207
  }
205
- 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);
206
214
  fs.writeFileSync(path.join(consumer, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2));
207
215
  const install = spawnSync("npm", ["install", "--silent", "--no-audit", "--no-fund", tarball], {
208
216
  cwd: consumer,
@@ -222,7 +230,7 @@ function runInstalledPackageSmoke(root, failures, observations) {
222
230
  const binTarget = fs.existsSync(bin) ? fs.readlinkSync(bin) : "";
223
231
  const installedBinFile = path.join(packageRoot, "dist/harness.mjs");
224
232
  const installedBinMode = fs.existsSync(installedBinFile) ? fs.statSync(installedBinFile).mode : undefined;
225
- observations.installSmoke = {
233
+ const installSmoke = {
226
234
  nodeVersion,
227
235
  tempRoot,
228
236
  binTarget,
@@ -236,51 +244,60 @@ function runInstalledPackageSmoke(root, failures, observations) {
236
244
  scriptsDisabled: [],
237
245
  steps: [],
238
246
  };
247
+ observations.installSmoke = installSmoke;
239
248
  expectEqual(failures, "installed-bin-not-dist", pkg.bin?.harness, "dist/harness.mjs", "installed package bin.harness must resolve to dist/harness.mjs");
240
- expectEqual(failures, "installed-postinstall-not-dist", pkg.scripts?.postinstall, "node dist/postinstall.mjs", "installed package postinstall must resolve to dist/postinstall.mjs");
241
- 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");
242
251
  if (!binTarget.includes("dist/harness.mjs")) {
243
252
  failures.push({ code: "installed-bin-link-not-dist", message: `installed bin link does not target dist/harness.mjs: ${binTarget}` });
244
253
  }
245
- if (!observations.installSmoke.binExecutable) {
254
+ if (!installSmoke.binExecutable) {
246
255
  failures.push({ code: "installed-bin-not-executable", file: "dist/harness.mjs", mode: installedBinMode, message: "installed package bin dist/harness.mjs must be executable" });
247
256
  }
248
- for (const relative of ["dist/harness.mjs", "dist/postinstall.mjs", "dist/check-dist-observation.mjs"]) {
257
+ for (const relative of ["postinstall.mjs", "run-dist.mjs", "dist/harness.mjs", "dist/postinstall.mjs", "dist/check-dist-observation.mjs"]) {
249
258
  if (!fs.existsSync(path.join(packageRoot, relative)))
250
259
  failures.push({ code: "installed-file-missing", file: relative, message: `installed package missing ${relative}` });
251
260
  }
252
- if (observations.installSmoke.hasTests)
261
+ if (installSmoke.hasTests)
253
262
  failures.push({ code: "installed-package-includes-tests", message: "installed package must not include tests/**" });
254
- if (observations.installSmoke.hasScripts)
263
+ if (installSmoke.hasScripts)
255
264
  failures.push({ code: "installed-package-includes-scripts", message: "installed package must not include scripts/** after historical shim deletion" });
256
265
  const installedScripts = path.join(packageRoot, "scripts");
257
266
  if (fs.existsSync(installedScripts)) {
258
267
  fs.renameSync(installedScripts, `${installedScripts}.disabled-by-dist-observation`);
259
- observations.installSmoke.scriptsDisabled.push("scripts/");
268
+ installSmoke.scriptsDisabled.push("scripts/");
260
269
  }
261
270
  const runtimeEnv = isolatedEnv({ nodeBin, home, extraPath: [path.join(consumer, "node_modules", ".bin")] });
262
- runInstalledMatrix(root, runtimeEnv, failures, observations.installSmoke.steps);
271
+ runInstalledMatrix(root, runtimeEnv, failures, installSmoke.steps);
263
272
  const postinstall = spawnSync(node24, [path.join(packageRoot, "dist/postinstall.mjs")], {
264
273
  cwd: packageRoot,
265
274
  encoding: "utf8",
266
275
  env: { ...runtimeEnv, CODING_AGENT_HARNESS_SKIP_POSTINSTALL: "1" },
267
276
  });
268
- observations.installSmoke.steps.push({ id: "installed-dist-postinstall", status: postinstall.status });
277
+ installSmoke.steps.push({ id: "installed-dist-postinstall", status: postinstall.status });
269
278
  if (postinstall.status !== 0)
270
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}` });
271
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"], {
272
289
  cwd: packageRoot,
273
290
  encoding: "utf8",
274
291
  env: runtimeEnv,
275
292
  maxBuffer: 32 * 1024 * 1024,
276
293
  });
277
- observations.installSmoke.steps.push({ id: "installed-observation", status: installedObservation.status });
294
+ installSmoke.steps.push({ id: "installed-observation", status: installedObservation.status });
278
295
  if (installedObservation.status !== 0) {
279
296
  failures.push({ code: "installed-observation-failed", message: `installed observation failed\nSTDOUT:\n${installedObservation.stdout}\nSTDERR:\n${installedObservation.stderr}` });
280
297
  }
281
298
  else {
282
299
  const installedResult = JSON.parse(installedObservation.stdout);
283
- observations.installSmoke.observationOk = installedResult.ok;
300
+ installSmoke.observationOk = installedResult.ok;
284
301
  if (!installedResult.ok)
285
302
  failures.push({ code: "installed-observation-not-ok", message: JSON.stringify(installedResult.failures, null, 2) });
286
303
  }
@@ -323,7 +340,7 @@ function findNode24() {
323
340
  path.join(os.homedir(), ".nvm", "versions", "node", "v24.13.1", "bin", "node"),
324
341
  "/opt/homebrew/opt/node@24/bin/node",
325
342
  "/usr/local/opt/node@24/bin/node",
326
- ].filter(Boolean);
343
+ ].filter(isNonEmptyString);
327
344
  for (const candidate of candidates) {
328
345
  if (!fs.existsSync(candidate))
329
346
  continue;
@@ -333,7 +350,10 @@ function findNode24() {
333
350
  }
334
351
  return undefined;
335
352
  }
336
- 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 = [], }) {
337
357
  return {
338
358
  ...process.env,
339
359
  HOME: home,
@@ -346,14 +366,22 @@ function readJson(file, failures, code) {
346
366
  return JSON.parse(fs.readFileSync(file, "utf8"));
347
367
  }
348
368
  catch (error) {
349
- failures.push({ code, message: `failed to read ${file}: ${error.message}` });
369
+ failures.push({ code, message: `failed to read ${file}: ${errorMessage(error)}` });
350
370
  return undefined;
351
371
  }
352
372
  }
373
+ function errorMessage(error) {
374
+ return error instanceof Error ? error.message : String(error);
375
+ }
353
376
  function expectEqual(failures, code, actual, expected, message) {
354
377
  if (actual !== expected)
355
378
  failures.push({ code, actual, expected, message });
356
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
+ }
357
385
  function parseImportSpecifiers(content) {
358
386
  const specifiers = [];
359
387
  for (const match of content.matchAll(/\bfrom\s*["']([^"']+)["']/g))
@@ -430,7 +458,7 @@ if (isMainModule()) {
430
458
  options = parseArgs(process.argv.slice(2));
431
459
  }
432
460
  catch (error) {
433
- console.error(error.message);
461
+ console.error(errorMessage(error));
434
462
  process.exit(1);
435
463
  }
436
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";
@@ -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 { fileURLToPath } from "node:url";
@@ -105,7 +104,7 @@ export function checkImportGraph({ repoRoot = defaultRepoRoot, expectNodes, expe
105
104
  const graph = buildImportGraph({ repoRoot });
106
105
  const violations = [];
107
106
  for (const edge of graph.unresolvedEdges) {
108
- violations.push({ code: "unresolved-local-edge", ...edge });
107
+ violations.push({ ...edge, code: "unresolved-local-edge" });
109
108
  }
110
109
  for (const cycle of graph.cycles) {
111
110
  violations.push({
@@ -115,23 +114,25 @@ export function checkImportGraph({ repoRoot = defaultRepoRoot, expectNodes, expe
115
114
  });
116
115
  }
117
116
  for (const edge of graph.runtimeMjsToTsEdges) {
118
- violations.push({ code: "mjs-imports-ts", ...edge });
117
+ violations.push({ ...edge, code: "mjs-imports-ts" });
119
118
  }
120
119
  for (const edge of graph.typesValueImports) {
121
- violations.push({ code: "types-value-import", ...edge });
120
+ violations.push({ ...edge, code: "types-value-import" });
122
121
  }
123
122
  const barrels = graph.nodes.filter((node) => node.path === "scripts/lib/harness-core.mts" || node.path === "scripts/lib/harness-core.mjs");
124
- for (const edge of barrels.flatMap((barrel) => barrel.imports || [])) {
125
- if (edge.kind !== "export" || !edge.target)
126
- continue;
127
- const target = graph.nodes.find((node) => node.path === edge.target);
128
- if (!target?.barrelReachable) {
129
- violations.push({
130
- code: "barrel-target-not-reachable",
131
- file: barrel.path,
132
- target: edge.target,
133
- message: `${edge.target} is exported by harness-core but is not marked barrel reachable`,
134
- });
123
+ for (const barrel of barrels) {
124
+ for (const edge of barrel.imports || []) {
125
+ if (edge.kind !== "export" || !edge.target)
126
+ continue;
127
+ const target = graph.nodes.find((node) => node.path === edge.target);
128
+ if (!target?.barrelReachable) {
129
+ violations.push({
130
+ code: "barrel-target-not-reachable",
131
+ file: barrel.path,
132
+ target: edge.target,
133
+ message: `${edge.target} is exported by harness-core but is not marked barrel reachable`,
134
+ });
135
+ }
135
136
  }
136
137
  }
137
138
  if (expectNodes !== undefined && graph.summary.fileCount !== expectNodes) {
@@ -344,6 +345,8 @@ function markReachable(nodesByPath, startPath, field) {
344
345
  const seen = new Set();
345
346
  while (stack.length > 0) {
346
347
  const current = stack.pop();
348
+ if (!current)
349
+ continue;
347
350
  if (seen.has(current))
348
351
  continue;
349
352
  seen.add(current);
@@ -385,10 +388,10 @@ function findCycles(nodesByPath) {
385
388
  for (const target of adjacency(nodesByPath, file)) {
386
389
  if (!indexByPath.has(target)) {
387
390
  strongConnect(target);
388
- lowlinkByPath.set(file, Math.min(lowlinkByPath.get(file), lowlinkByPath.get(target)));
391
+ lowlinkByPath.set(file, Math.min(lowlinkByPath.get(file) ?? 0, lowlinkByPath.get(target) ?? 0));
389
392
  }
390
393
  else if (onStack.has(target)) {
391
- lowlinkByPath.set(file, Math.min(lowlinkByPath.get(file), indexByPath.get(target)));
394
+ lowlinkByPath.set(file, Math.min(lowlinkByPath.get(file) ?? 0, indexByPath.get(target) ?? 0));
392
395
  }
393
396
  }
394
397
  if (lowlinkByPath.get(file) === indexByPath.get(file)) {
@@ -396,10 +399,12 @@ function findCycles(nodesByPath) {
396
399
  let current;
397
400
  do {
398
401
  current = stack.pop();
402
+ if (!current)
403
+ break;
399
404
  onStack.delete(current);
400
405
  component.push(current);
401
406
  } while (current !== file);
402
- if (component.length > 1 || hasSelfLoop(nodesByPath, component[0]))
407
+ if (component.length > 1 || hasSelfLoop(nodesByPath, component[0] ?? ""))
403
408
  cycles.push(component.sort());
404
409
  }
405
410
  }
@@ -407,13 +412,13 @@ function findCycles(nodesByPath) {
407
412
  if (!indexByPath.has(file))
408
413
  strongConnect(file);
409
414
  }
410
- return cycles.sort((left, right) => left[0].localeCompare(right[0]));
415
+ return cycles.sort((left, right) => (left[0] ?? "").localeCompare(right[0] ?? ""));
411
416
  }
412
417
  function assignLayers(nodesByPath, cycleNodeSet) {
413
418
  const memo = new Map();
414
419
  function layerFor(file, visiting = new Set()) {
415
420
  if (memo.has(file))
416
- return memo.get(file);
421
+ return memo.get(file) ?? null;
417
422
  if (cycleNodeSet.has(file) || visiting.has(file)) {
418
423
  memo.set(file, null);
419
424
  return null;
@@ -435,7 +440,7 @@ function assignLayers(nodesByPath, cycleNodeSet) {
435
440
  }
436
441
  }
437
442
  function adjacency(nodesByPath, file) {
438
- return (nodesByPath.get(file)?.imports || []).map((imported) => imported.target).filter((target) => target && nodesByPath.has(target));
443
+ return (nodesByPath.get(file)?.imports || []).map((imported) => imported.target).filter((target) => Boolean(target && nodesByPath.has(target)));
439
444
  }
440
445
  function hasSelfLoop(nodesByPath, file) {
441
446
  return adjacency(nodesByPath, file).includes(file);
@@ -460,17 +465,29 @@ function parseCliArgs(argv) {
460
465
  args.check = true;
461
466
  else if (arg === "--json")
462
467
  args.json = true;
463
- else if (arg === "--out")
464
- args.out = argv[++index];
465
- else if (arg === "--expect-nodes")
466
- args.expectNodes = Number(argv[++index]);
467
- else if (arg === "--expect-edges")
468
- args.expectEdges = Number(argv[++index]);
468
+ else if (arg === "--out") {
469
+ args.out = requireValue(argv, index, arg);
470
+ index += 1;
471
+ }
472
+ else if (arg === "--expect-nodes") {
473
+ args.expectNodes = Number(requireValue(argv, index, arg));
474
+ index += 1;
475
+ }
476
+ else if (arg === "--expect-edges") {
477
+ args.expectEdges = Number(requireValue(argv, index, arg));
478
+ index += 1;
479
+ }
469
480
  else
470
481
  throw new Error(`Unknown argument: ${arg}`);
471
482
  }
472
483
  return args;
473
484
  }
485
+ function requireValue(argv, index, option) {
486
+ const value = argv[index + 1];
487
+ if (!value)
488
+ throw new Error(`${option} requires a value`);
489
+ return value;
490
+ }
474
491
  function writeOutput({ graph, args }) {
475
492
  if (args.out) {
476
493
  fs.mkdirSync(path.dirname(path.resolve(args.out)), { recursive: true });