harness-everything 0.4.0 → 0.4.2
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.github/workflows/behavioral-evals.yml +3 -3
- package/.github/workflows/ci.yml +26 -10
- package/.github/workflows/release.yml +3 -4
- package/.nvmrc +1 -0
- package/AGENTS.md +23 -14
- package/CHANGELOG.md +4 -0
- package/CONTRIBUTING.md +25 -10
- package/Dockerfile +1 -1
- package/README.md +13 -9
- package/RELEASING.md +13 -2
- package/VERIFICATION.md +21 -2
- package/ci/mechanism-2u-issue-44.test.js +15 -9
- package/ci/repository-contract.test.js +27 -0
- package/docs/release-evidence.md +3 -1
- package/docs/repository-contract.md +27 -0
- package/docs/troubleshooting.md +13 -6
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +6 -3
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/scripts/repository-contract.js +213 -0
- package/scripts/sync-current-docs.js +12 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"name": "harness-everything",
|
|
10
10
|
"source": "./",
|
|
11
11
|
"description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
|
|
12
|
-
"version": "0.4.
|
|
12
|
+
"version": "0.4.2",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "Miya Daniel"
|
|
15
15
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-everything",
|
|
3
3
|
"description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Miya Daniel"
|
|
7
7
|
},
|
|
@@ -9,10 +9,10 @@ jobs:
|
|
|
9
9
|
validate:
|
|
10
10
|
runs-on: ubuntu-latest
|
|
11
11
|
steps:
|
|
12
|
-
- uses: actions/checkout@
|
|
13
|
-
- uses: actions/setup-node@
|
|
12
|
+
- uses: actions/checkout@v7
|
|
13
|
+
- uses: actions/setup-node@v7
|
|
14
14
|
with:
|
|
15
|
-
node-version:
|
|
15
|
+
node-version: 24
|
|
16
16
|
- name: Validate eval case structure (free)
|
|
17
17
|
run: node behavioral-evals/run.js validate
|
|
18
18
|
- name: Install Harness for eval
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -16,12 +16,12 @@ jobs:
|
|
|
16
16
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
17
17
|
runs-on: ${{ matrix.os }}
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
20
|
with:
|
|
21
21
|
fetch-depth: 0
|
|
22
|
-
- uses: actions/setup-node@
|
|
22
|
+
- uses: actions/setup-node@v7
|
|
23
23
|
with:
|
|
24
|
-
node-version:
|
|
24
|
+
node-version: 24
|
|
25
25
|
- name: Install dependencies for regression suite
|
|
26
26
|
run: npm ci
|
|
27
27
|
- name: Run hermetic self-regression suite
|
|
@@ -29,10 +29,26 @@ jobs:
|
|
|
29
29
|
- name: Run focused mutation checks
|
|
30
30
|
run: npm run test:mutations
|
|
31
31
|
|
|
32
|
+
runtime-floor:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v7
|
|
36
|
+
with:
|
|
37
|
+
fetch-depth: 0
|
|
38
|
+
- uses: actions/setup-node@v7
|
|
39
|
+
with:
|
|
40
|
+
node-version: 22
|
|
41
|
+
- name: Install dependencies on minimum supported Node
|
|
42
|
+
run: npm ci
|
|
43
|
+
- name: Run representative regression suite on Node 22
|
|
44
|
+
run: npm test
|
|
45
|
+
- name: Verify installer contract on Node 22
|
|
46
|
+
run: node ci/installer-roundtrip.test.js
|
|
47
|
+
|
|
32
48
|
skill-quality:
|
|
33
49
|
runs-on: ubuntu-latest
|
|
34
50
|
steps:
|
|
35
|
-
- uses: actions/checkout@
|
|
51
|
+
- uses: actions/checkout@v7
|
|
36
52
|
with:
|
|
37
53
|
fetch-depth: 0
|
|
38
54
|
- name: Install waza CLI
|
|
@@ -80,12 +96,12 @@ jobs:
|
|
|
80
96
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
81
97
|
runs-on: ${{ matrix.os }}
|
|
82
98
|
steps:
|
|
83
|
-
- uses: actions/checkout@
|
|
99
|
+
- uses: actions/checkout@v7
|
|
84
100
|
with:
|
|
85
101
|
fetch-depth: 0
|
|
86
|
-
- uses: actions/setup-node@
|
|
102
|
+
- uses: actions/setup-node@v7
|
|
87
103
|
with:
|
|
88
|
-
node-version:
|
|
104
|
+
node-version: 24
|
|
89
105
|
- name: Install Node dependencies
|
|
90
106
|
run: npm ci
|
|
91
107
|
- name: Docs, manifest, and eval-coverage consistency
|
|
@@ -110,12 +126,12 @@ jobs:
|
|
|
110
126
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
111
127
|
runs-on: ${{ matrix.os }}
|
|
112
128
|
steps:
|
|
113
|
-
- uses: actions/checkout@
|
|
129
|
+
- uses: actions/checkout@v7
|
|
114
130
|
with:
|
|
115
131
|
fetch-depth: 0
|
|
116
|
-
- uses: actions/setup-node@
|
|
132
|
+
- uses: actions/setup-node@v7
|
|
117
133
|
with:
|
|
118
|
-
node-version:
|
|
134
|
+
node-version: 24
|
|
119
135
|
- name: CLI --help works
|
|
120
136
|
run: node bin/cli.js --help
|
|
121
137
|
- name: Install, verify, uninstall, and restore seeded user files
|
|
@@ -28,14 +28,13 @@ jobs:
|
|
|
28
28
|
runs-on: ubuntu-latest
|
|
29
29
|
steps:
|
|
30
30
|
- name: Checkout full release history
|
|
31
|
-
uses: actions/checkout@
|
|
31
|
+
uses: actions/checkout@v7
|
|
32
32
|
with:
|
|
33
33
|
fetch-depth: 0
|
|
34
34
|
|
|
35
|
-
#
|
|
36
|
-
# normal Node 20 CI matrix. This only changes the release runner.
|
|
35
|
+
# Release tooling uses the repository's primary Node runtime.
|
|
37
36
|
- name: Setup Node.js
|
|
38
|
-
uses: actions/setup-node@
|
|
37
|
+
uses: actions/setup-node@v7
|
|
39
38
|
with:
|
|
40
39
|
node-version: 24
|
|
41
40
|
cache: npm
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
24
|
package/AGENTS.md
CHANGED
|
@@ -17,10 +17,12 @@ You are modifying **Harness**: an orchestrated agent operating system (skills +
|
|
|
17
17
|
| `.agents/plugins/` + `plugins/harness-everything/` | Local OpenAI/Codex marketplace + `.codex-plugin` package with canonical skill copies and packaged session/prompt hooks. |
|
|
18
18
|
| `submission/openai/` | Public OpenAI Skills-only listing/test inputs. The public artifact does not include local `.codex-plugin` lifecycle hooks. |
|
|
19
19
|
| `evals/<skill>/` | Trigger/routing eval per skill (waza format). Required for every canonical routed skill. |
|
|
20
|
-
| `behavioral-evals/` | LLM-behavior cases run via headless CLI sessions.
|
|
20
|
+
| `behavioral-evals/` | LLM-behavior cases run via headless CLI sessions. They can be run on demand and are also structurally validated by the weekly `behavioral-evals.yml` workflow; live cases run there only when the Claude CLI is available. |
|
|
21
21
|
| `benchmarks/` | BENCHMARK_SOP fixtures and recorded results. Evidence lives here or nowhere. |
|
|
22
22
|
| `.claude-plugin/` | Claude Code plugin distribution manifests. Must list exactly the on-disk canonical skills. |
|
|
23
23
|
| `docs/platform-capabilities.md` | Canonical current platform/install/enforcement/evidence matrix. |
|
|
24
|
+
| `docs/repository-contract.md` | Generated current-state Node/workflow/action/gate contract. Regenerate with `npm run docs:sync`; do not hand-edit. |
|
|
25
|
+
| `.nvmrc` | Primary development/CI Node runtime. `package.json#engines.node` remains the minimum supported runtime. |
|
|
24
26
|
| `docs/`, `references/` | Philosophy, architecture, workflow documentation, shared checklists. |
|
|
25
27
|
|
|
26
28
|
## Non-Negotiable Change Rules
|
|
@@ -31,24 +33,28 @@ You are modifying **Harness**: an orchestrated agent operating system (skills +
|
|
|
31
33
|
4. **Release version policy:** normal PRs do not manually bump package/plugin versions. `semantic-release` determines the next stable SemVer from Conventional Commits on `main`, and `scripts/sync-release-version.js` owns distribution-version synchronization. Skill frontmatter versions remain release metadata and must never exceed the package version's numeric base. Nested sub-skills (`<skill>/<sub>/SKILL.md`) inherit the parent skill's version and must always match it.
|
|
32
34
|
5. **Distribution manifests are generated facts, not opinions:** if you add/remove/rename a canonical skill, update/sync every distribution that copies or lists it. Run `npm run plugin:sync`, `npm run test:consistency`, and `npm run test:plugin:openai` rather than updating only `.claude-plugin/plugin.json`.
|
|
33
35
|
6. **Platform capability claims are tested contracts.** If host integration behavior changes, update `docs/platform-capabilities.md` and every affected current-state surface in the same change. Never turn package/mechanism evidence into a live-host claim, and never describe public Skills-only behavior as if it includes local lifecycle hooks.
|
|
34
|
-
7. **
|
|
35
|
-
8. **
|
|
36
|
+
7. **Runtime/workflow claims are generated facts.** `package.json`, `.nvmrc`, `Dockerfile`, and active `.github/workflows/*.yml` are parsed by `scripts/repository-contract.js`. After changing them, run `npm run docs:sync`; `test:repo-contract` rejects stale generated docs, unsupported action majors, Node runtimes below the support floor, or loss of the Node 22 compatibility lane.
|
|
37
|
+
8. **CHANGELOG.md keeps pending human-authored notes under `[Unreleased]`.** Do not create new alpha/beta/rc release headings; semantic-release owns stable release headings, tags, and release notes.
|
|
38
|
+
9. **Conventional Commit type is release input.** The final commit/squash title that lands on `main` must follow the convention. Breaking changes release major; `feat` releases minor; `fix`, `perf`, `refactor`, `build`, and `revert` release patch; `docs`, `test`, `ci`, `style`, and `chore` do not release by themselves.
|
|
36
39
|
|
|
37
40
|
## Verification Before You Claim Done
|
|
38
41
|
|
|
39
|
-
Run the local gates that
|
|
42
|
+
Run the local gates that cover the same contract categories as CI:
|
|
40
43
|
|
|
41
44
|
```bash
|
|
42
|
-
npm test
|
|
43
|
-
npm run test:
|
|
44
|
-
npm run test:
|
|
45
|
-
npm run test:docs
|
|
46
|
-
npm run test:
|
|
47
|
-
npm run test:
|
|
48
|
-
npm run test:
|
|
49
|
-
npm run test:
|
|
50
|
-
npm run test:
|
|
51
|
-
npm run test:
|
|
45
|
+
npm test # self-regression: syntax, routing, mechanisms
|
|
46
|
+
npm run test:mutations # focused mutation checks
|
|
47
|
+
npm run test:mechanism # mechanism suites alone
|
|
48
|
+
npm run test:consistency # manifests, versions, docs links, eval coverage + repository contract
|
|
49
|
+
npm run test:repo-contract # runtime/workflow/action/schedule drift gate
|
|
50
|
+
npm run test:docs:capabilities # platform documentation drift gate
|
|
51
|
+
npm run test:references # executable/deep-dive references
|
|
52
|
+
npm run test:release # release catalog + semantic-release/version-sync contract
|
|
53
|
+
npm run test:collision # description collision detection
|
|
54
|
+
npm run test:routing:skills # every positive skill route reaches its target
|
|
55
|
+
npm run test:plugin:openai # local OpenAI/Codex plugin package
|
|
56
|
+
npm run test:plugin:submission # public Skills-only submission inputs/bundle parity
|
|
57
|
+
npm run test:platform:compatibility
|
|
52
58
|
```
|
|
53
59
|
|
|
54
60
|
If waza is installed (`~/bin/waza`), also run what CI runs:
|
|
@@ -59,6 +65,8 @@ waza spec verify <skill> evals/<skill>/eval.yaml --fail --threshold 1
|
|
|
59
65
|
waza tokens check
|
|
60
66
|
```
|
|
61
67
|
|
|
68
|
+
Node.js 24 is the primary CI runtime across Ubuntu, Windows, and macOS. A separate Ubuntu Node.js 22 lane protects the advertised minimum runtime. See [docs/repository-contract.md](docs/repository-contract.md) for the generated current state.
|
|
69
|
+
|
|
62
70
|
Do not report "it works" from reading code. Record which evidence layer you actually proved: package, deterministic mechanism, live host, or behavior.
|
|
63
71
|
|
|
64
72
|
## Behavioral & Benchmark Evidence
|
|
@@ -66,6 +74,7 @@ Do not report "it works" from reading code. Record which evidence layer you actu
|
|
|
66
74
|
Deterministic mechanism tests prove the tested hook/plugin contract; they do **not** prove a real host loaded it or that agents behave differently. For behavior:
|
|
67
75
|
|
|
68
76
|
- `node behavioral-evals/run.js run --engine opencode` (or `--engine claude`) — on-demand, token-costing.
|
|
77
|
+
- The weekly `behavioral-evals.yml` workflow always validates case structure and runs live cases only when its runner has the Claude CLI.
|
|
69
78
|
- `node benchmarks/run.js scaffold <scenario>` → run both variants → `record` with evidence logs.
|
|
70
79
|
|
|
71
80
|
For a live-host enforcement claim, preserve a host/session artifact showing the mechanism was actually loaded and fired. Results are committed where the relevant evidence workflow requires it. A benchmark run without a recorded result is indistinguishable from one that never happened.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.4.2](https://github.com/dyphn1/Harness-everything/compare/v0.4.1...v0.4.2) (2026-09-16)
|
|
6
|
+
|
|
7
|
+
## [0.4.1](https://github.com/dyphn1/Harness-everything/compare/v0.4.0...v0.4.1) (2026-09-16)
|
|
8
|
+
|
|
5
9
|
## [0.4.0](https://github.com/dyphn1/Harness-everything/compare/v0.3.6...v0.4.0) (2026-09-16)
|
|
6
10
|
|
|
7
11
|
### Features
|
package/CONTRIBUTING.md
CHANGED
|
@@ -4,24 +4,38 @@ Thanks for your interest in contributing.
|
|
|
4
4
|
|
|
5
5
|
## Development Setup
|
|
6
6
|
|
|
7
|
+
Harness supports **Node.js 22 or newer**. Node.js **24** is the primary development, CI, Docker, and release runtime; `.nvmrc` is the checked-in default. The generated current-state contract is [docs/repository-contract.md](docs/repository-contract.md).
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
10
|
git clone https://github.com/dyphn1/Harness-everything.git
|
|
9
11
|
cd Harness-everything
|
|
12
|
+
nvm use # uses Node 24 from .nvmrc when nvm is available
|
|
13
|
+
npm ci
|
|
10
14
|
npm test
|
|
11
15
|
```
|
|
12
16
|
|
|
13
17
|
## Quality Gates
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
The reusable CI workflow runs the repository's deterministic gates across the supported operating-system matrix, with a separate Node 22 minimum-runtime lane. Before opening a PR, run the relevant local gates:
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
|
-
npm test
|
|
19
|
-
npm run test:
|
|
20
|
-
npm run test:
|
|
21
|
-
npm run test:
|
|
22
|
-
npm run test:
|
|
22
|
+
npm test # self-regression
|
|
23
|
+
npm run test:mutations # focused mutation checks
|
|
24
|
+
npm run test:mechanism # hook/plugin mechanism checks
|
|
25
|
+
npm run test:consistency # manifests, docs, eval coverage + repository contract
|
|
26
|
+
npm run test:repo-contract # Node/workflow/action/schedule drift gate
|
|
27
|
+
npm run test:references # executable/deep-dive references
|
|
28
|
+
npm run test:release # release catalog + version automation contract
|
|
29
|
+
npm run test:collision # description collision detection
|
|
30
|
+
npm run test:plugin:openai # local OpenAI/Codex plugin package
|
|
31
|
+
npm run test:plugin:submission # public Skills-only package reproducibility
|
|
32
|
+
npm run test:platform:compatibility
|
|
23
33
|
```
|
|
24
34
|
|
|
35
|
+
If `waza` is installed, CI also validates canonical skill readiness, routing evals, and token growth. See [AGENTS.md](AGENTS.md) for the repository operating rules.
|
|
36
|
+
|
|
37
|
+
When runtime or workflow configuration changes, run `npm run docs:sync` and commit the regenerated [docs/repository-contract.md](docs/repository-contract.md). CI rejects stale generated contract data.
|
|
38
|
+
|
|
25
39
|
## Skill Contributions
|
|
26
40
|
|
|
27
41
|
Each skill lives in its own top-level directory with a `SKILL.md`. See [AGENTS.md](AGENTS.md) for full rules, but the non-negotiables:
|
|
@@ -46,10 +60,11 @@ If the repository uses squash merging, make the PR title a valid Conventional Co
|
|
|
46
60
|
## Pull Requests
|
|
47
61
|
|
|
48
62
|
1. Fork and create a feature branch.
|
|
49
|
-
2. Make changes and ensure all gates pass.
|
|
50
|
-
3.
|
|
51
|
-
4.
|
|
52
|
-
5.
|
|
63
|
+
2. Make changes and ensure all relevant gates pass.
|
|
64
|
+
3. Run `npm run docs:sync` after runtime/workflow changes and commit generated current-state documentation.
|
|
65
|
+
4. Update `CHANGELOG.md` under `[Unreleased]` for user-visible changes.
|
|
66
|
+
5. Do not manually bump release versions; release automation owns them.
|
|
67
|
+
6. Open a PR with a Conventional Commit title that accurately represents its release impact.
|
|
53
68
|
|
|
54
69
|
## Reporting Issues
|
|
55
70
|
|
package/Dockerfile
CHANGED
package/README.md
CHANGED
|
@@ -48,6 +48,8 @@ The exact "Harness" behavior depends on the installation surface. Claude Code ha
|
|
|
48
48
|
|
|
49
49
|
Harness integrates directly into your workspace. There is no heavy daemon, no paid external APIs, and zero configuration required.
|
|
50
50
|
|
|
51
|
+
**Runtime:** Harness supports **Node.js 22+**. Node.js **24** is the primary/recommended development and CI runtime (`.nvmrc`). The generated current-state runtime/workflow summary is [docs/repository-contract.md](docs/repository-contract.md).
|
|
52
|
+
|
|
51
53
|
```bash
|
|
52
54
|
# Option A: Claude Code plugin (marketplace manifest included)
|
|
53
55
|
# /plugin marketplace add dyphn1/Harness-everything
|
|
@@ -200,18 +202,18 @@ This repo uses a flat layout (waza/agentskills.io convention). The table below m
|
|
|
200
202
|
|---|---|---|
|
|
201
203
|
| `harness-everything` | **Core Runtime** | Bootstrap, kernel-router, tier-router, verify-gate, self-heal |
|
|
202
204
|
| `hooks` | **Core Runtime** | Claude Code lifecycle hooks (prompt routing, circuit breaker, scope guard, stop gate, etc.) |
|
|
203
|
-
| `scripts` | **Core Runtime** | Installer, manifest, prompts, workspace utilities |
|
|
205
|
+
| `scripts` | **Core Runtime** | Installer, manifest, prompts, workspace utilities, repository contract extraction/sync |
|
|
204
206
|
| `bin` | **Core Runtime** | `harness` CLI entry point |
|
|
205
|
-
| `ci` | **Quality Gates** | Consistency checks, description collision, mechanism tests, invariant-routing regression, documentation
|
|
207
|
+
| `ci` | **Quality Gates** | Consistency checks, description collision, mechanism tests, invariant-routing regression, documentation/runtime contract drift guards |
|
|
206
208
|
| `.github` | **CI/CD** | GitHub Actions workflows (ci.yml, release.yml, behavioral-evals.yml) |
|
|
207
209
|
| `.claude-plugin` | **Distribution** | Plugin manifests for Claude Code marketplace |
|
|
208
210
|
| `.agents/plugins` | **Distribution** | OpenAI/Codex repository marketplace metadata |
|
|
209
211
|
| `plugins/harness-everything` | **Distribution** | Local OpenAI/Codex plugin package plus canonical skill copies |
|
|
210
212
|
| `submission/openai` | **Distribution / Review** | Public OpenAI Skills-only listing/test inputs |
|
|
211
213
|
| `evals` | **Routing Evals** | 26 trigger/routing eval suites (waza format) |
|
|
212
|
-
| `behavioral-evals` | **Behavioral Evals** | LLM-level discipline cases
|
|
214
|
+
| `behavioral-evals` | **Behavioral Evals** | LLM-level discipline cases plus weekly structural validation workflow |
|
|
213
215
|
| `benchmarks` | **Benchmarks** | BENCHMARK_SOP fixtures and recorded A/B results |
|
|
214
|
-
| `docs` | **Documentation** | Philosophy, architecture, routing, reflection, platform capabilities, audit |
|
|
216
|
+
| `docs` | **Documentation** | Philosophy, architecture, routing, reflection, platform capabilities, generated repository contract, audit |
|
|
215
217
|
| `references` | **Documentation** | Shared checklists (security, performance, definition-of-done) |
|
|
216
218
|
| `multi-agent-workspace` | **Skill (Tier 3)** | Scaffold six zones, select agency specialists, and generate bounded launchers |
|
|
217
219
|
| `environment-detection` | **Foundation** | Preflight: detect OS, shell, package manager |
|
|
@@ -249,6 +251,7 @@ For a deep dive into individual modules and the underlying philosophy, explore o
|
|
|
249
251
|
* [Harness Philosophy](docs/philosophy.md): The core behavior-first, intervention-only design.
|
|
250
252
|
* [Harness Architecture](docs/architecture.md): Lifecycle hooks, security model, and data locality.
|
|
251
253
|
* [Platform Capability Matrix](docs/platform-capabilities.md): Canonical current enforcement/install/evidence boundary per surface.
|
|
254
|
+
* [Repository Runtime & Workflow Contract](docs/repository-contract.md): Generated Node/action/workflow/gate state used by consistency CI.
|
|
252
255
|
* [OpenAI / ChatGPT Plugin Packaging](docs/openai-plugin.md): Local Codex/OpenAI plugin packaging plus public Skills-only submission boundary.
|
|
253
256
|
* [Harness Routing & Triage](docs/routing.md): Detailed trigger criteria for Tiers 1, 2, and 3.
|
|
254
257
|
* [Harness Reflection & Memory](docs/reflection.md): WAL session handoffs and workspace rules immunization.
|
|
@@ -264,7 +267,7 @@ Maintainers should follow [RELEASING.md](RELEASING.md) for tag-driven npm releas
|
|
|
264
267
|
|
|
265
268
|
**If you are an agent asked to verify a Harness install, start at [VERIFICATION.md](VERIFICATION.md), not here.** It separates package/integrity checks, mechanism evidence, live-host evidence, and behavioral evidence so one kind of pass is not mistaken for another.
|
|
266
269
|
|
|
267
|
-
`npm test` (`self-evolve/scripts/self-regression.js`) runs deterministic syntax, CLI, routing-matrix, positive skill-route coverage, **invariant-first routing regression**, reference, behavioral-case, Fable model-mode, and mechanism checks (`ci/mechanism-test.js`, `npm run test:mechanism` to run it alone). The suite checks real exit codes and stderr, not just "the code looks right."
|
|
270
|
+
`npm test` (`self-evolve/scripts/self-regression.js`) runs deterministic syntax, CLI, routing-matrix, positive skill-route coverage, **invariant-first routing regression**, reference, behavioral-case, Fable model-mode, and mechanism checks (`ci/mechanism-test.js`, `npm run test:mechanism` to run it alone). The suite checks real exit codes and stderr, not just "the code looks right." Primary CI runs on Node.js 24 across Ubuntu, Windows, and macOS for every push and pull request (`.github/workflows/ci.yml`), with a separate Node.js 22 compatibility lane for the advertised minimum runtime.
|
|
268
271
|
|
|
269
272
|
For a fuller vanilla-vs-Harness behavioral comparison, see [Harness Skills Benchmark SOP](BENCHMARK_SOP.md) — standardized, reproducible scenarios:
|
|
270
273
|
* **Test A:** Over-engineering defense (Tier 1 typo correction)
|
|
@@ -276,13 +279,13 @@ For a fuller vanilla-vs-Harness behavioral comparison, see [Harness Skills Bench
|
|
|
276
279
|
|
|
277
280
|
Benchmark **results** are tracked in [benchmarks/](benchmarks/README.md) (`run.js scaffold` builds the fixture, `record` commits a schema-validated result bound to a session log). Until those cells are filled, effectiveness claims are unbacked by recorded evidence.
|
|
278
281
|
|
|
279
|
-
### Behavioral evals (LLM-level, on demand)
|
|
282
|
+
### Behavioral evals (LLM-level, on demand + weekly structural validation)
|
|
280
283
|
|
|
281
|
-
Mechanism tests prove individual packaged mechanisms; only real host/session evidence proves the host actually loaded and fired them. [behavioral-evals/](behavioral-evals/README.md) runs discipline cases (including pressure variants like "we ship in 5 minutes, skip checks") against headless agent sessions (`claude -p`, OpenCode) in throwaway workspaces: `npm run eval:behavioral`.
|
|
284
|
+
Mechanism tests prove individual packaged mechanisms; only real host/session evidence proves the host actually loaded and fired them. [behavioral-evals/](behavioral-evals/README.md) runs discipline cases (including pressure variants like "we ship in 5 minutes, skip checks") against headless agent sessions (`claude -p`, OpenCode) in throwaway workspaces: `npm run eval:behavioral`. Live runs remain token-costing and can be invoked on demand. The weekly `behavioral-evals.yml` workflow always validates case structure and runs live cases only when the runner actually has the Claude CLI.
|
|
282
285
|
|
|
283
286
|
### Catalog hygiene
|
|
284
287
|
|
|
285
|
-
`npm run test:consistency` keeps
|
|
288
|
+
`npm run test:consistency` keeps distribution manifests, docs links, skill frontmatter, routing-eval coverage, platform capability claims, and the generated repository runtime/workflow contract in lockstep with what is actually on disk. `npm run test:repo-contract` runs the runtime/workflow drift gate directly, while `npm run docs:sync` regenerates [docs/repository-contract.md](docs/repository-contract.md) after an intentional change. `npm run test:docs:capabilities` runs the platform-doc drift check directly. `npm run test:references` checks every executable/deep-dive path named by `SKILL.md`; `npm run test:release` compares release/catalog evidence; `npm run test:routing:skills` recursively classifies nested skills and executes the real router for every directly-routable skill's positive cases; `npm run test:routing:invariants` guards the invariant-first architecture; and `harness verify-install` detects stale installed versions or file trees. The installer E2E gate performs install → verify-install → uninstall against seeded user-owned files on Ubuntu, Windows, and macOS so path and ownership symmetry regressions fail CI. `npm run test:collision` fails CI when two skills' descriptions overlap enough to confuse the router.
|
|
286
289
|
|
|
287
290
|
---
|
|
288
291
|
|
|
@@ -303,8 +306,9 @@ To contribute to Harness or modify any Skill behavior, ensure you run the local
|
|
|
303
306
|
```bash
|
|
304
307
|
npm run self-regression
|
|
305
308
|
npm run test:consistency
|
|
309
|
+
npm run test:repo-contract
|
|
306
310
|
npm run test:plugin:openai
|
|
307
311
|
npm run test:plugin:submission
|
|
308
312
|
```
|
|
309
313
|
|
|
310
|
-
All script modifications must pass 100% cleanly before pushing to keep the runtime immunized against behavioral regression.
|
|
314
|
+
After intentional runtime/workflow changes, run `npm run docs:sync` and commit the regenerated repository contract. All script modifications must pass 100% cleanly before pushing to keep the runtime immunized against behavioral regression.
|
package/RELEASING.md
CHANGED
|
@@ -15,14 +15,25 @@ Harness releases are stable SemVer releases generated automatically from Convent
|
|
|
15
15
|
|
|
16
16
|
There are no alpha, beta, or rc release channels. Historical prerelease tags remain history, but new releases come only from `main` and use `vX.Y.Z` tags.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Do not hard-code a "current release baseline" in this guide. `semantic-release` discovers the latest stable tag from Git history on each run, and release evidence belongs in [docs/release-evidence.md](docs/release-evidence.md).
|
|
19
|
+
|
|
20
|
+
## Runtime contract
|
|
21
|
+
|
|
22
|
+
The release job uses the repository's primary Node runtime from the checked-in current-state policy: Node.js 24 today, with Node.js 22 retained as the minimum supported product runtime. The generated source-of-truth summary is [docs/repository-contract.md](docs/repository-contract.md).
|
|
23
|
+
|
|
24
|
+
When changing Node versions, GitHub Action majors, or workflow triggers, run:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm run docs:sync
|
|
28
|
+
npm run test:repo-contract
|
|
29
|
+
```
|
|
19
30
|
|
|
20
31
|
## Release flow
|
|
21
32
|
|
|
22
33
|
A push to `main` runs the full reusable CI gate and then semantic-release. A manual `workflow_dispatch` run is available for a safe retry.
|
|
23
34
|
|
|
24
35
|
1. Check out full Git history and tags.
|
|
25
|
-
2. Run the same CI jobs used for pull requests.
|
|
36
|
+
2. Run the same CI jobs used for pull requests, including the Node 22 minimum-runtime compatibility lane.
|
|
26
37
|
3. Analyze Conventional Commits since the latest stable release.
|
|
27
38
|
4. Exit without publishing when there is no releasable commit.
|
|
28
39
|
5. Calculate the next stable SemVer.
|
package/VERIFICATION.md
CHANGED
|
@@ -11,7 +11,7 @@ Harness verification separates four kinds of evidence that must not be conflated
|
|
|
11
11
|
|
|
12
12
|
A mechanism test can pass while a live host never loads the plugin. A behavior test can pass on an advisory-only surface without proving any hard enforcement. Keep those claims separate.
|
|
13
13
|
|
|
14
|
-
The authoritative current platform boundary is [docs/platform-capabilities.md](docs/platform-capabilities.md).
|
|
14
|
+
The authoritative current platform boundary is [docs/platform-capabilities.md](docs/platform-capabilities.md). The generated current runtime/workflow boundary is [docs/repository-contract.md](docs/repository-contract.md).
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -146,6 +146,21 @@ The capability test checks the current-state documentation surfaces for stale pl
|
|
|
146
146
|
|
|
147
147
|
When changing platform integration behavior, update [docs/platform-capabilities.md](docs/platform-capabilities.md) and all affected current-state docs in the same PR.
|
|
148
148
|
|
|
149
|
+
### 3a. Repository runtime/workflow contract
|
|
150
|
+
|
|
151
|
+
Runtime and workflow claims are extracted from `package.json`, `.nvmrc`, `Dockerfile`, and active `.github/workflows/*.yml` rather than being independently maintained in prose.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npm run repo:contract # human-readable current state
|
|
155
|
+
npm run repo:contract -- --json
|
|
156
|
+
npm run docs:sync # regenerate docs/repository-contract.md
|
|
157
|
+
npm run test:repo-contract # fail on drift
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The contract gate requires Node.js 22 or newer as the supported floor, requires the primary `.nvmrc` runtime to match Docker and active primary workflows, keeps an explicit Node 22 CI compatibility lane, rejects GitHub Action majors below the supported floor, preserves the weekly behavioral-eval schedule, and checks that the generated contract document is current.
|
|
161
|
+
|
|
162
|
+
After intentionally changing runtime/workflow configuration, regenerate the document and commit it in the same PR. Historical audit/evidence snapshots are not rewritten merely because the current contract changed.
|
|
163
|
+
|
|
149
164
|
---
|
|
150
165
|
|
|
151
166
|
## 4. Behavioral test prompts
|
|
@@ -271,13 +286,17 @@ For a comprehensive audit:
|
|
|
271
286
|
|
|
272
287
|
```bash
|
|
273
288
|
npm test
|
|
289
|
+
npm run test:mutations
|
|
274
290
|
npm run test:consistency
|
|
291
|
+
npm run test:repo-contract
|
|
275
292
|
npm run test:references
|
|
293
|
+
npm run test:release
|
|
276
294
|
npm run test:routing:skills
|
|
277
295
|
npm run test:routing:invariants
|
|
278
296
|
npm run test:plugin:openai
|
|
279
297
|
npm run test:plugin:submission
|
|
298
|
+
npm run test:platform:compatibility
|
|
280
299
|
node ci/installer-roundtrip.test.js
|
|
281
300
|
```
|
|
282
301
|
|
|
283
|
-
Do not accept "I read the code and it looks right" as evidence for a runtime or live-host claim.
|
|
302
|
+
Primary CI uses Node.js 24 across Ubuntu, Windows, and macOS; the dedicated Node.js 22 lane proves the advertised minimum remains viable. Do not accept "I read the code and it looks right" as evidence for a runtime or live-host claim.
|
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const helper = require('./test-helper');
|
|
4
|
+
const { isStableSemVer } = require('../scripts/sync-release-version');
|
|
4
5
|
|
|
5
6
|
console.log('\n[2u] Issue #44 release metadata regression checks...');
|
|
6
7
|
|
|
7
|
-
const packageJson = JSON.parse(fs.readFileSync(path.join(helper.root, 'package.json'), 'utf8'));
|
|
8
8
|
const skillText = name => fs.readFileSync(path.join(helper.root, name, 'SKILL.md'), 'utf8');
|
|
9
9
|
const versionOf = text => {
|
|
10
10
|
const match = text.match(/^ version:\s*(\S+)$/m);
|
|
11
11
|
return match && match[1];
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
// Skill metadata records the release in which that skill last changed; it is
|
|
15
|
+
// intentionally not required to equal the current package version. The
|
|
16
|
+
// changed-skill bump policy itself is exercised dynamically by
|
|
17
|
+
// mechanism-2x-release-automation.test.js.
|
|
18
|
+
const fableVersion = versionOf(skillText('fable-mode'));
|
|
14
19
|
helper.check(
|
|
15
|
-
'2u.
|
|
16
|
-
|
|
17
|
-
`fable-mode=${
|
|
20
|
+
'2u. fable-mode carries stable release metadata',
|
|
21
|
+
isStableSemVer(fableVersion),
|
|
22
|
+
`fable-mode=${fableVersion}`
|
|
18
23
|
);
|
|
19
24
|
for (const nested of ['execution-guardrails', 'fable-haiku', 'fable-opus', 'fable-sonnet']) {
|
|
20
25
|
helper.check(
|
|
21
26
|
`2u. fable-mode/${nested} inherits the parent version`,
|
|
22
|
-
versionOf(skillText(path.join('fable-mode', nested))) ===
|
|
23
|
-
`${nested}=${versionOf(skillText(path.join('fable-mode', nested)))}, parent=${
|
|
27
|
+
versionOf(skillText(path.join('fable-mode', nested))) === fableVersion,
|
|
28
|
+
`${nested}=${versionOf(skillText(path.join('fable-mode', nested)))}, parent=${fableVersion}`
|
|
24
29
|
);
|
|
25
30
|
}
|
|
31
|
+
const todoVersion = versionOf(skillText('todo-driven-workflow'));
|
|
26
32
|
helper.check(
|
|
27
|
-
'2u.
|
|
28
|
-
|
|
29
|
-
`todo-driven-workflow=${
|
|
33
|
+
'2u. todo-driven-workflow carries stable release metadata',
|
|
34
|
+
isStableSemVer(todoVersion),
|
|
35
|
+
`todo-driven-workflow=${todoVersion}`
|
|
30
36
|
);
|
|
31
37
|
|
|
32
38
|
const changelog = fs.readFileSync(path.join(helper.root, 'CHANGELOG.md'), 'utf8');
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { collectContract, renderContractMarkdown, validateContract } = require('../scripts/repository-contract');
|
|
8
|
+
|
|
9
|
+
const contract = collectContract();
|
|
10
|
+
const failures = validateContract(contract);
|
|
11
|
+
assert.deepStrictEqual(failures, [], `Repository contract drift:\n- ${failures.join('\n- ')}`);
|
|
12
|
+
|
|
13
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
14
|
+
const packageLock = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package-lock.json'), 'utf8'));
|
|
15
|
+
assert.strictEqual(
|
|
16
|
+
packageLock.packages?.['']?.engines?.node,
|
|
17
|
+
packageJson.engines?.node,
|
|
18
|
+
'package-lock.json root engines.node must mirror package.json engines.node; run npm install --package-lock-only after changing the Node runtime contract',
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const rendered = renderContractMarkdown(contract);
|
|
22
|
+
assert.match(rendered, /Minimum supported Node\.js/);
|
|
23
|
+
assert.match(rendered, /GitHub Actions runtime majors/);
|
|
24
|
+
assert.ok(contract.ci.qualityGates.includes('test:consistency'), 'CI contract must expose test:consistency');
|
|
25
|
+
assert.ok(contract.behavioralEvals.scheduled, 'Behavioral eval schedule must remain visible to the contract');
|
|
26
|
+
|
|
27
|
+
console.log('Repository contract verified: runtime, lockfile, workflows, action majors, schedule, gates, and generated docs are synchronized.');
|
package/docs/release-evidence.md
CHANGED
|
@@ -14,7 +14,9 @@ The source capture for this row is the local review artifact `.git/issue-review-
|
|
|
14
14
|
|
|
15
15
|
## Current review boundary
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
This file is an evidence ledger, not a source of truth for the current package version. Historical rows stay unchanged until all four observations for a newer release are deliberately collected. Current release/runtime configuration must be read from Git history, package metadata, the release workflow, and [repository-contract.md](repository-contract.md), rather than from prose such as the retired `0.3.7-beta` review note.
|
|
18
|
+
|
|
19
|
+
A GitHub tag or source version alone is not enough to add a completed row here; keep using `not observed` when registry, workflow, or publication evidence has not been checked.
|
|
18
20
|
|
|
19
21
|
## Evidence entry template
|
|
20
22
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!-- GENERATED by scripts/repository-contract.js. Run `npm run docs:sync` after workflow/runtime changes. -->
|
|
2
|
+
# Repository runtime and workflow contract
|
|
3
|
+
|
|
4
|
+
This page is generated from checked-in runtime and workflow sources. Do not edit it by hand. Historical audit/evidence documents are intentionally outside this current-state contract.
|
|
5
|
+
|
|
6
|
+
## Runtime
|
|
7
|
+
|
|
8
|
+
- Minimum supported Node.js: **22** (from `package.json#engines.node`: `>=22`)
|
|
9
|
+
- Primary development/CI Node.js: **24** (from `.nvmrc`)
|
|
10
|
+
- Docker verification Node.js: **24** (from `Dockerfile`)
|
|
11
|
+
|
|
12
|
+
## GitHub Actions runtime majors
|
|
13
|
+
|
|
14
|
+
- `actions/checkout`: v7
|
|
15
|
+
- `actions/setup-node`: v7
|
|
16
|
+
|
|
17
|
+
## Active workflows
|
|
18
|
+
|
|
19
|
+
| Workflow | Node.js versions | Explicit triggers | npm quality gates |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `behavioral-evals.yml` | 24 | schedule, workflow_dispatch | n/a |
|
|
22
|
+
| `ci.yml` | 22, 24 | workflow_call | `test`, `test:mutations`, `test:consistency`, `test:references`, `test:release`, `test:collision`, `test:plugin:openai`, `test:plugin:submission`, `test:platform:compatibility` |
|
|
23
|
+
| `release.yml` | 24 | workflow_dispatch | n/a |
|
|
24
|
+
|
|
25
|
+
## Drift prevention
|
|
26
|
+
|
|
27
|
+
`npm run test:repo-contract` fails when runtime sources disagree, an active workflow reintroduces an unsupported Node version/action major, the Node 22 compatibility floor is no longer exercised, or this generated document is stale.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Harness OS Troubleshooting Guide
|
|
2
2
|
|
|
3
|
-
This guide helps you resolve common issues with Harness OS installation and usage. Platform enforcement claims follow [platform-capabilities.md](platform-capabilities.md).
|
|
3
|
+
This guide helps you resolve common issues with Harness OS installation and usage. Platform enforcement claims follow [platform-capabilities.md](platform-capabilities.md). Current Node/workflow values are generated in [repository-contract.md](repository-contract.md).
|
|
4
4
|
|
|
5
5
|
## Installation Issues
|
|
6
6
|
|
|
@@ -31,14 +31,20 @@ export PATH=~/.npm-global/bin:$PATH
|
|
|
31
31
|
# Check your Node.js version
|
|
32
32
|
node --version
|
|
33
33
|
|
|
34
|
-
# Harness
|
|
35
|
-
#
|
|
36
|
-
nvm install
|
|
37
|
-
nvm use
|
|
34
|
+
# Harness supports Node.js 22 or newer.
|
|
35
|
+
# Node.js 24 is the primary/recommended development runtime.
|
|
36
|
+
nvm install 24
|
|
37
|
+
nvm use 24
|
|
38
38
|
|
|
39
|
-
#
|
|
39
|
+
# To reproduce the minimum-runtime CI lane explicitly:
|
|
40
|
+
nvm install 22
|
|
41
|
+
nvm use 22
|
|
42
|
+
|
|
43
|
+
# Or download a supported release from https://nodejs.org/
|
|
40
44
|
```
|
|
41
45
|
|
|
46
|
+
The checked-in `.nvmrc` selects the primary runtime. If runtime/workflow files change, run `npm run docs:sync` followed by `npm run test:repo-contract` so generated current-state documentation cannot drift.
|
|
47
|
+
|
|
42
48
|
### Network Issues
|
|
43
49
|
|
|
44
50
|
**Symptom:** `npm ERR! network` or timeout errors
|
|
@@ -264,6 +270,7 @@ For deterministic cross-platform mechanism checks, prefer:
|
|
|
264
270
|
```bash
|
|
265
271
|
npm run test:mechanism
|
|
266
272
|
npm run test:docs:capabilities
|
|
273
|
+
npm run test:repo-contract
|
|
267
274
|
```
|
|
268
275
|
|
|
269
276
|
When isolating a specific Claude hook, use the Windows-safe `spawnSync` recipes in [../VERIFICATION.md](../VERIFICATION.md) rather than assuming a shell `echo | node` pipeline behaves the same on every OS.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-everything",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Harness OS: An Orchestrated Agent Operating System for AI-driven Development",
|
|
5
5
|
"main": "harness-everything/scripts/bootstrap.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
"test:mutations": "node ci/mutation-check.js",
|
|
14
14
|
"test:tdd-quality": "node ci/mechanism-2s-tdd-quality.test.js",
|
|
15
15
|
"tdd:quality": "node tdd/scripts/quality-gate.js",
|
|
16
|
-
"
|
|
16
|
+
"repo:contract": "node scripts/repository-contract.js",
|
|
17
|
+
"docs:sync": "node scripts/sync-current-docs.js",
|
|
18
|
+
"test:repo-contract": "node ci/repository-contract.test.js",
|
|
19
|
+
"test:consistency": "node ci/consistency-check.js && node ci/doc-capability-consistency.test.js && node ci/repository-contract.test.js",
|
|
17
20
|
"test:docs:capabilities": "node ci/doc-capability-consistency.test.js",
|
|
18
21
|
"test:references": "node ci/reference-check.js",
|
|
19
22
|
"test:release": "node ci/release-consistency-check.js && node ci/mechanism-2x-release-automation.test.js && node ci/mechanism-2x-release-commit-message.test.js",
|
|
@@ -39,7 +42,7 @@
|
|
|
39
42
|
"commitlint": "commitlint --edit"
|
|
40
43
|
},
|
|
41
44
|
"engines": {
|
|
42
|
-
"node": ">=
|
|
45
|
+
"node": ">=22"
|
|
43
46
|
},
|
|
44
47
|
"author": "Miya Daniel",
|
|
45
48
|
"license": "Apache-2.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "harness-everything",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.2",
|
|
5
5
|
"description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Miya Daniel",
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
8
|
+
const ACTION_MINIMUMS = Object.freeze({ checkout: 7, setupNode: 7 });
|
|
9
|
+
const MINIMUM_SUPPORTED_NODE_FLOOR = 22;
|
|
10
|
+
|
|
11
|
+
function read(root, rel) {
|
|
12
|
+
return fs.readFileSync(path.join(root, rel), 'utf8');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readJson(root, rel) {
|
|
16
|
+
return JSON.parse(read(root, rel));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseMinimumNode(engine) {
|
|
20
|
+
const match = String(engine || '').match(/>=\s*v?(\d+)/);
|
|
21
|
+
return match ? Number(match[1]) : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseMajor(text) {
|
|
25
|
+
const match = String(text || '').trim().match(/^v?(\d+)/);
|
|
26
|
+
return match ? Number(match[1]) : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function uniqSorted(values) {
|
|
30
|
+
return [...new Set(values.filter(Number.isInteger))].sort((a, b) => a - b);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractMajors(text, action) {
|
|
34
|
+
const escaped = action.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
35
|
+
const re = new RegExp(`uses:\\s*${escaped}@v(\\d+)`, 'g');
|
|
36
|
+
const out = [];
|
|
37
|
+
let match;
|
|
38
|
+
while ((match = re.exec(text))) out.push(Number(match[1]));
|
|
39
|
+
return uniqSorted(out);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function extractNodeVersions(text) {
|
|
43
|
+
const out = [];
|
|
44
|
+
const re = /node-version:\s*["']?(\d+)(?:\.\d+)?["']?/g;
|
|
45
|
+
let match;
|
|
46
|
+
while ((match = re.exec(text))) out.push(Number(match[1]));
|
|
47
|
+
return uniqSorted(out);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractQualityGates(text) {
|
|
51
|
+
const gates = [];
|
|
52
|
+
if (/\bnpm\s+test\b/.test(text)) gates.push('test');
|
|
53
|
+
const re = /\bnpm\s+run\s+([A-Za-z0-9:_-]+)/g;
|
|
54
|
+
let match;
|
|
55
|
+
while ((match = re.exec(text))) gates.push(match[1]);
|
|
56
|
+
return [...new Set(gates)];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function listWorkflowFiles(root) {
|
|
60
|
+
const dir = path.join(root, '.github', 'workflows');
|
|
61
|
+
if (!fs.existsSync(dir)) return [];
|
|
62
|
+
return fs.readdirSync(dir)
|
|
63
|
+
.filter(name => /\.ya?ml$/i.test(name))
|
|
64
|
+
.sort();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseWorkflow(root, file) {
|
|
68
|
+
const rel = path.posix.join('.github/workflows', file);
|
|
69
|
+
const text = read(root, rel);
|
|
70
|
+
return {
|
|
71
|
+
file,
|
|
72
|
+
nodeVersions: extractNodeVersions(text),
|
|
73
|
+
checkoutMajors: extractMajors(text, 'actions/checkout'),
|
|
74
|
+
setupNodeMajors: extractMajors(text, 'actions/setup-node'),
|
|
75
|
+
scheduled: /^\s*schedule:\s*$/m.test(text),
|
|
76
|
+
workflowDispatch: /^\s*workflow_dispatch:\s*$/m.test(text),
|
|
77
|
+
workflowCall: /^\s*workflow_call:\s*$/m.test(text),
|
|
78
|
+
qualityGates: extractQualityGates(text),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function collectContract(root = ROOT) {
|
|
83
|
+
const pkg = readJson(root, 'package.json');
|
|
84
|
+
const nvmPath = path.join(root, '.nvmrc');
|
|
85
|
+
const primaryNode = fs.existsSync(nvmPath) ? parseMajor(read(root, '.nvmrc')) : null;
|
|
86
|
+
const dockerText = read(root, 'Dockerfile');
|
|
87
|
+
const dockerMatch = dockerText.match(/^FROM\s+node:(\d+)(?:[^\s]*)/m);
|
|
88
|
+
const workflows = listWorkflowFiles(root).map(file => parseWorkflow(root, file));
|
|
89
|
+
const ci = workflows.find(workflow => workflow.file === 'ci.yml' || workflow.file === 'ci.yaml');
|
|
90
|
+
const behavioral = workflows.find(workflow => /^behavioral-evals\.ya?ml$/.test(workflow.file));
|
|
91
|
+
const release = workflows.find(workflow => /^release\.ya?ml$/.test(workflow.file));
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
runtime: {
|
|
95
|
+
packageEngine: pkg.engines?.node || null,
|
|
96
|
+
minimumNode: parseMinimumNode(pkg.engines?.node),
|
|
97
|
+
primaryNode,
|
|
98
|
+
dockerNode: dockerMatch ? Number(dockerMatch[1]) : null,
|
|
99
|
+
},
|
|
100
|
+
githubActions: {
|
|
101
|
+
checkoutMajors: uniqSorted(workflows.flatMap(workflow => workflow.checkoutMajors)),
|
|
102
|
+
setupNodeMajors: uniqSorted(workflows.flatMap(workflow => workflow.setupNodeMajors)),
|
|
103
|
+
},
|
|
104
|
+
workflows: Object.fromEntries(workflows.map(workflow => [workflow.file, workflow])),
|
|
105
|
+
ci: ci || null,
|
|
106
|
+
behavioralEvals: behavioral || null,
|
|
107
|
+
release: release || null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function renderContractMarkdown(contract) {
|
|
112
|
+
const runtime = contract.runtime;
|
|
113
|
+
const actionList = values => values.length ? values.map(v => `v${v}`).join(', ') : 'not used';
|
|
114
|
+
const rows = Object.values(contract.workflows).map(workflow => {
|
|
115
|
+
const nodes = workflow.nodeVersions.length ? workflow.nodeVersions.map(String).join(', ') : 'n/a';
|
|
116
|
+
const trigger = [workflow.scheduled && 'schedule', workflow.workflowDispatch && 'workflow_dispatch', workflow.workflowCall && 'workflow_call'].filter(Boolean).join(', ') || 'push/pull request or other';
|
|
117
|
+
const gates = workflow.qualityGates.length ? workflow.qualityGates.map(g => `\`${g}\``).join(', ') : 'n/a';
|
|
118
|
+
return `| \`${workflow.file}\` | ${nodes} | ${trigger} | ${gates} |`;
|
|
119
|
+
}).join('\n');
|
|
120
|
+
|
|
121
|
+
return `<!-- GENERATED by scripts/repository-contract.js. Run \`npm run docs:sync\` after workflow/runtime changes. -->\n# Repository runtime and workflow contract\n\nThis page is generated from checked-in runtime and workflow sources. Do not edit it by hand. Historical audit/evidence documents are intentionally outside this current-state contract.\n\n## Runtime\n\n- Minimum supported Node.js: **${runtime.minimumNode}** (from \`package.json#engines.node\`: \`${runtime.packageEngine}\`)\n- Primary development/CI Node.js: **${runtime.primaryNode}** (from \`.nvmrc\`)\n- Docker verification Node.js: **${runtime.dockerNode}** (from \`Dockerfile\`)\n\n## GitHub Actions runtime majors\n\n- \`actions/checkout\`: ${actionList(contract.githubActions.checkoutMajors)}\n- \`actions/setup-node\`: ${actionList(contract.githubActions.setupNodeMajors)}\n\n## Active workflows\n\n| Workflow | Node.js versions | Explicit triggers | npm quality gates |\n| --- | --- | --- | --- |\n${rows}\n\n## Drift prevention\n\n\`npm run test:repo-contract\` fails when runtime sources disagree, an active workflow reintroduces an unsupported Node version/action major, the Node ${runtime.minimumNode} compatibility floor is no longer exercised, or this generated document is stale.\n`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function validateContract(contract, root = ROOT) {
|
|
125
|
+
const failures = [];
|
|
126
|
+
const fail = message => failures.push(message);
|
|
127
|
+
const { runtime } = contract;
|
|
128
|
+
|
|
129
|
+
if (!Number.isInteger(runtime.minimumNode) || runtime.minimumNode < MINIMUM_SUPPORTED_NODE_FLOOR) {
|
|
130
|
+
fail(`package.json must declare Node >=${MINIMUM_SUPPORTED_NODE_FLOOR}; got ${runtime.packageEngine || 'missing'}`);
|
|
131
|
+
}
|
|
132
|
+
if (!Number.isInteger(runtime.primaryNode) || runtime.primaryNode < runtime.minimumNode) {
|
|
133
|
+
fail(`.nvmrc primary Node must be >= minimum Node ${runtime.minimumNode}; got ${runtime.primaryNode ?? 'missing'}`);
|
|
134
|
+
}
|
|
135
|
+
if (runtime.dockerNode !== runtime.primaryNode) {
|
|
136
|
+
fail(`Dockerfile Node ${runtime.dockerNode ?? 'missing'} must match .nvmrc Node ${runtime.primaryNode ?? 'missing'}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const major of contract.githubActions.checkoutMajors) {
|
|
140
|
+
if (major < ACTION_MINIMUMS.checkout) fail(`actions/checkout@v${major} is below supported floor v${ACTION_MINIMUMS.checkout}`);
|
|
141
|
+
}
|
|
142
|
+
for (const major of contract.githubActions.setupNodeMajors) {
|
|
143
|
+
if (major < ACTION_MINIMUMS.setupNode) fail(`actions/setup-node@v${major} is below supported floor v${ACTION_MINIMUMS.setupNode}`);
|
|
144
|
+
}
|
|
145
|
+
if (contract.githubActions.checkoutMajors.length !== 1) fail(`active workflows must use one checkout major; found ${contract.githubActions.checkoutMajors.join(', ') || 'none'}`);
|
|
146
|
+
if (contract.githubActions.setupNodeMajors.length !== 1) fail(`active workflows must use one setup-node major; found ${contract.githubActions.setupNodeMajors.join(', ') || 'none'}`);
|
|
147
|
+
|
|
148
|
+
for (const workflow of Object.values(contract.workflows)) {
|
|
149
|
+
for (const node of workflow.nodeVersions) {
|
|
150
|
+
if (node < runtime.minimumNode) fail(`${workflow.file} uses Node ${node}, below minimum ${runtime.minimumNode}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!contract.ci) fail('ci.yml is missing');
|
|
155
|
+
else {
|
|
156
|
+
if (!contract.ci.nodeVersions.includes(runtime.primaryNode)) fail(`ci.yml must exercise primary Node ${runtime.primaryNode}`);
|
|
157
|
+
if (!contract.ci.nodeVersions.includes(runtime.minimumNode)) fail(`ci.yml must exercise minimum supported Node ${runtime.minimumNode}`);
|
|
158
|
+
}
|
|
159
|
+
if (!contract.behavioralEvals) fail('behavioral-evals.yml is missing');
|
|
160
|
+
else {
|
|
161
|
+
if (!contract.behavioralEvals.scheduled) fail('behavioral-evals.yml must retain its scheduled trigger');
|
|
162
|
+
if (!contract.behavioralEvals.nodeVersions.includes(runtime.primaryNode)) fail(`behavioral-evals.yml must use primary Node ${runtime.primaryNode}`);
|
|
163
|
+
}
|
|
164
|
+
if (!contract.release) fail('release.yml is missing');
|
|
165
|
+
else if (!contract.release.nodeVersions.includes(runtime.primaryNode)) {
|
|
166
|
+
fail(`release.yml must use primary Node ${runtime.primaryNode}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const generatedPath = path.join(root, 'docs', 'repository-contract.md');
|
|
170
|
+
if (!fs.existsSync(generatedPath)) {
|
|
171
|
+
fail('docs/repository-contract.md is missing; run npm run docs:sync');
|
|
172
|
+
} else {
|
|
173
|
+
const actual = fs.readFileSync(generatedPath, 'utf8').replace(/\r\n/g, '\n');
|
|
174
|
+
const expected = renderContractMarkdown(contract).replace(/\r\n/g, '\n');
|
|
175
|
+
if (actual !== expected) fail('docs/repository-contract.md is stale; run npm run docs:sync');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return failures;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function printSummary(contract) {
|
|
182
|
+
console.log(`Node: >=${contract.runtime.minimumNode} supported; ${contract.runtime.primaryNode} primary; ${contract.runtime.dockerNode} Docker`);
|
|
183
|
+
console.log(`Actions: checkout ${contract.githubActions.checkoutMajors.map(v => `v${v}`).join(', ')}; setup-node ${contract.githubActions.setupNodeMajors.map(v => `v${v}`).join(', ')}`);
|
|
184
|
+
for (const workflow of Object.values(contract.workflows)) {
|
|
185
|
+
console.log(`${workflow.file}: Node ${workflow.nodeVersions.join(', ') || 'n/a'}; gates ${workflow.qualityGates.join(', ') || 'n/a'}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (require.main === module) {
|
|
190
|
+
const args = new Set(process.argv.slice(2));
|
|
191
|
+
const contract = collectContract();
|
|
192
|
+
if (args.has('--json')) {
|
|
193
|
+
process.stdout.write(`${JSON.stringify(contract, null, 2)}\n`);
|
|
194
|
+
} else if (args.has('--check')) {
|
|
195
|
+
const failures = validateContract(contract);
|
|
196
|
+
if (failures.length) {
|
|
197
|
+
for (const failure of failures) console.error(`❌ ${failure}`);
|
|
198
|
+
process.exitCode = 1;
|
|
199
|
+
} else {
|
|
200
|
+
console.log('✅ Repository runtime/workflow contract is consistent.');
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
printSummary(contract);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
ACTION_MINIMUMS,
|
|
209
|
+
MINIMUM_SUPPORTED_NODE_FLOOR,
|
|
210
|
+
collectContract,
|
|
211
|
+
renderContractMarkdown,
|
|
212
|
+
validateContract,
|
|
213
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { collectContract, renderContractMarkdown } = require('./repository-contract');
|
|
7
|
+
|
|
8
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
9
|
+
const output = path.join(ROOT, 'docs', 'repository-contract.md');
|
|
10
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
11
|
+
fs.writeFileSync(output, renderContractMarkdown(collectContract(ROOT)), 'utf8');
|
|
12
|
+
console.log(`Updated ${path.relative(ROOT, output).replace(/\\/g, '/')}`);
|