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.
- package/CHANGELOG.md +10 -0
- package/CONTRIBUTING.md +8 -4
- package/README.md +12 -2
- package/README.zh-CN.md +10 -2
- package/SKILL.md +14 -3
- package/dist/build-dist.mjs +19 -6
- package/dist/check-dist-observation.mjs +57 -29
- package/dist/check-harness.mjs +0 -1
- package/dist/check-import-graph.mjs +44 -27
- package/dist/check-lite-forbidden-surfaces.mjs +121 -0
- package/dist/check-no-ts-nocheck.mjs +7 -7
- package/dist/check-runtime-emit.mjs +10 -3
- package/dist/check-type-boundaries.mjs +51 -9
- package/dist/commands/dashboard-command.mjs +52 -14
- package/dist/commands/migration-command.mjs +18 -8
- package/dist/commands/module-command.mjs +142 -0
- package/dist/commands/preset-command.mjs +51 -12
- package/dist/commands/registry.mjs +483 -0
- package/dist/commands/task-command.mjs +109 -52
- package/dist/harness.mjs +6 -304
- package/dist/lib/capability-registry.mjs +229 -53
- package/dist/lib/check-module-parallel.mjs +1 -6
- package/dist/lib/check-profiles.mjs +39 -46
- package/dist/lib/check-task-contracts.mjs +6 -4
- package/dist/lib/command-registry.mjs +248 -0
- package/dist/lib/core-shared.mjs +78 -3
- package/dist/lib/dashboard-data.mjs +203 -22
- package/dist/lib/dashboard-workbench.mjs +245 -21
- package/dist/lib/dashboard-writer.mjs +4 -1
- package/dist/lib/git-status-summary.mjs +0 -1
- package/dist/lib/governance-index-generator.mjs +7 -5
- package/dist/lib/governance-sync.mjs +46 -121
- package/dist/lib/governance-table-boundary.mjs +1 -14
- package/dist/lib/harness-core.mjs +4 -1
- package/dist/lib/harness-paths.mjs +115 -1
- package/dist/lib/impact-classifier.mjs +420 -0
- package/dist/lib/lesson-maintenance.mjs +1 -2
- package/dist/lib/markdown-utils.mjs +50 -1
- package/dist/lib/migration-planner.mjs +31 -16
- package/dist/lib/migration-support.mjs +5 -4
- package/dist/lib/module-registry.mjs +296 -0
- package/dist/lib/preset-audit-contracts.mjs +24 -1
- package/dist/lib/preset-engine.mjs +67 -29
- package/dist/lib/preset-registry.mjs +361 -71
- package/dist/lib/preset-runner.mjs +292 -26
- package/dist/lib/review-confirm-git-gate.mjs +73 -19
- package/dist/lib/status-builder.mjs +23 -8
- package/dist/lib/structure-migration.mjs +6 -4
- package/dist/lib/subagent-authorization-audit.mjs +8 -2
- package/dist/lib/task-archive-eligibility.mjs +65 -0
- package/dist/lib/task-audit-metadata.mjs +25 -11
- package/dist/lib/task-audit-migration.mjs +21 -14
- package/dist/lib/task-discovery-contract.mjs +32 -0
- package/dist/lib/task-index.mjs +3 -2
- package/dist/lib/task-lesson-candidates.mjs +1 -2
- package/dist/lib/task-lesson-sedimentation.mjs +310 -9
- package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
- package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
- package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
- package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
- package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
- package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
- package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
- package/dist/lib/task-lifecycle/template-files.mjs +2 -5
- package/dist/lib/task-lifecycle.mjs +116 -160
- package/dist/lib/task-metadata.mjs +10 -5
- package/dist/lib/task-preset-contract-drift.mjs +45 -0
- package/dist/lib/task-repository.mjs +192 -0
- package/dist/lib/task-review-model.mjs +36 -17
- package/dist/lib/task-scanner.mjs +74 -23
- package/dist/lib/task-template-materials.mjs +131 -0
- package/dist/lib/task-tombstone-commands.mjs +186 -29
- package/dist/lib/types/check-profiles.js +1 -0
- package/dist/lib/types/impact.js +1 -0
- package/dist/lib/types/preset.js +1 -0
- package/dist/lib/types/task-lifecycle.js +1 -0
- package/dist/lib/types/task-scanner.js +1 -0
- package/dist/postinstall.mjs +2 -2
- package/dist/run-built-tests.mjs +10 -3
- package/docs-release/README.md +1 -0
- package/docs-release/architecture/document-contract-kernel/README.md +150 -0
- package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
- package/docs-release/architecture/overview.md +2 -2
- package/docs-release/architecture/overview.zh-CN.md +2 -2
- package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
- package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
- package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/README.md +1 -1
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
- package/docs-release/guides/agent-installation.en-US.md +4 -6
- package/docs-release/guides/agent-installation.md +11 -8
- package/docs-release/guides/contributing.md +10 -3
- package/docs-release/guides/contributing.zh-CN.md +10 -3
- package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
- package/docs-release/guides/migration-playbook.en-US.md +9 -6
- package/docs-release/guides/migration-playbook.md +9 -6
- package/docs-release/guides/preset-development.md +68 -2
- package/docs-release/guides/task-state-machine.en-US.md +8 -8
- package/docs-release/guides/task-state-machine.md +7 -7
- package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
- package/package.json +16 -12
- package/postinstall.mjs +37 -0
- package/presets/legacy-migration/preset.yaml +5 -5
- package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
- package/presets/lesson-sedimentation/preset.yaml +3 -3
- package/presets/module/preset.yaml +2 -2
- package/presets/module/templates/execution_strategy.append.md +1 -1
- package/presets/module/templates/task_plan.append.md +3 -3
- package/presets/release-closeout/checks/check-release-package.mjs +6 -1
- package/presets/release-closeout/preset.yaml +9 -9
- package/presets/release-closeout/scripts/generate-release-package.mjs +387 -25
- package/presets/release-closeout/templates/task_plan.append.md +5 -5
- package/presets/standard-task/preset.yaml +2 -2
- package/references/agents-md-pattern.md +23 -17
- package/references/lessons-governance.md +2 -2
- package/references/module-parallel-standard.md +3 -6
- package/references/ssot-governance.md +2 -2
- package/references/taskr-gap-analysis.md +3 -3
- package/run-dist.mjs +34 -0
- package/skills/preset-creator/SKILL.md +40 -8
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
- package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
- package/skills/preset-creator/references/structure-aware-paths.md +112 -0
- package/templates/AGENTS.md.template +28 -26
- package/templates/architecture/README.md +2 -2
- package/templates/architecture/service-catalog.md +2 -2
- package/templates/architecture/services/service-template.md +1 -1
- package/templates/dashboard/assets/app-src/00-state.js +5 -1
- package/templates/dashboard/assets/app-src/10-router.js +7 -0
- package/templates/dashboard/assets/app-src/20-overview.js +8 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
- package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
- package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
- package/templates/dashboard/assets/app-src/40-modules.js +257 -41
- package/templates/dashboard/assets/app-src/45-review.js +127 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
- package/templates/dashboard/assets/app.css +928 -53
- package/templates/dashboard/assets/app.css.manifest.json +2 -0
- package/templates/dashboard/assets/app.js +1071 -98
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
- package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
- package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
- package/templates/dashboard/assets/css-src/31-archive.css +94 -0
- package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
- package/templates/dashboard/assets/i18n.js +166 -2
- package/templates/development/README.md +9 -9
- package/templates/development/cross-repo-debugging.md +3 -3
- package/templates/development/external-context/service-template.md +1 -1
- package/templates/development/external-source-packs/README.md +2 -2
- package/templates/integrations/README.md +4 -4
- package/templates/integrations/api-contract.md +1 -1
- package/templates/integrations/event-contract.md +1 -1
- package/templates/integrations/third-party/vendor-template.md +1 -1
- package/templates/integrations/webhook-contract.md +1 -1
- package/templates/ledger/Harness-Ledger.md +1 -1
- package/templates/modules/module_brief.md +50 -0
- package/templates/modules/module_plan.md +49 -0
- package/templates/modules/registry_view.md +9 -0
- package/templates/modules/session_prompt_pack.md +55 -0
- package/templates/planning/brief.md +32 -8
- package/templates/planning/module_brief.md +28 -3
- package/templates/planning/module_plan.md +26 -11
- package/templates/planning/module_session_prompt.md +11 -2
- package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
- package/templates/planning/review.md +1 -1
- package/templates/planning/visual_map.md +1 -1
- package/templates/reference/docs-library-standard.md +7 -7
- package/templates/reference/execution-workflow-standard.md +13 -0
- package/templates/reference/external-source-intake-standard.md +10 -10
- package/templates/reference/repo-governance-standard.md +1 -1
- package/templates/reference/review-routing-standard.md +4 -0
- package/templates/ssot/Module-Registry.md +4 -38
- package/templates/walkthrough/walkthrough-template.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +27 -25
- package/templates-zh-CN/CLAUDE.md.template +1 -1
- package/templates-zh-CN/architecture/README.md +2 -2
- package/templates-zh-CN/architecture/service-catalog.md +2 -2
- package/templates-zh-CN/architecture/services/service-template.md +1 -1
- package/templates-zh-CN/development/README.md +9 -9
- package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
- package/templates-zh-CN/development/external-context/service-template.md +1 -1
- package/templates-zh-CN/development/external-source-packs/README.md +2 -2
- package/templates-zh-CN/integrations/README.md +4 -4
- package/templates-zh-CN/integrations/api-contract.md +1 -1
- package/templates-zh-CN/integrations/event-contract.md +1 -1
- package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
- package/templates-zh-CN/integrations/webhook-contract.md +1 -1
- package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
- package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
- package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
- package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
- package/templates-zh-CN/modules/module_brief.md +47 -0
- package/templates-zh-CN/modules/module_plan.md +48 -0
- package/templates-zh-CN/modules/registry_view.md +9 -0
- package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
- package/templates-zh-CN/planning/INDEX.md +1 -0
- package/templates-zh-CN/planning/brief.md +26 -7
- package/templates-zh-CN/planning/module_brief.md +24 -2
- package/templates-zh-CN/planning/module_plan.md +35 -29
- package/templates-zh-CN/planning/module_session_prompt.md +15 -11
- package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
- package/templates-zh-CN/planning/review.md +1 -1
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
- package/templates-zh-CN/reference/docs-library-standard.md +27 -27
- package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
- package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
- package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
- package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
- package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
- package/templates-zh-CN/reference/review-routing-standard.md +3 -0
- package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
- package/templates-zh-CN/reference/worktree-standard.md +1 -1
- package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
- package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
- package/templates-zh-CN/ssot/Module-Registry.md +5 -44
- package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
- 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
|
|
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/
|
|
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 | `
|
|
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` | 交付组织模型选择 |
|
package/dist/build-dist.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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: "
|
|
25
|
-
"check:private": "
|
|
26
|
-
status: "
|
|
27
|
-
dashboard: "
|
|
28
|
-
"dashboard:folder": "
|
|
29
|
-
postinstall: "
|
|
30
|
-
|
|
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,
|
|
33
|
-
|
|
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]
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
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 (!
|
|
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 (
|
|
261
|
+
if (installSmoke.hasTests)
|
|
253
262
|
failures.push({ code: "installed-package-includes-tests", message: "installed package must not include tests/**" });
|
|
254
|
-
if (
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
461
|
+
console.error(errorMessage(error));
|
|
434
462
|
process.exit(1);
|
|
435
463
|
}
|
|
436
464
|
const result = checkDistObservation(options);
|
package/dist/check-harness.mjs
CHANGED
|
@@ -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"
|
|
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"
|
|
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"
|
|
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
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
else if (arg === "--expect-
|
|
468
|
-
args.
|
|
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 });
|