@chllming/wave-orchestration 0.5.1 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2 - 2026-03-22
4
+
5
+ - Hardened structured closure marker parsing so fenced or prose example `[wave-*]` lines no longer satisfy implementation, integration, documentation, or evaluator gates.
6
+ - Hardened `### Deliverables` so declared outputs must remain repo-relative file paths inside the implementation agent's declared file ownership before the exit contract can pass.
7
+ - Added regression coverage for the fenced-marker false-positive path and for deliverables that escape ownership boundaries.
8
+
3
9
  ## 0.5.1 - 2026-03-22
4
10
 
5
11
  - Fixed the Phase 4 autonomous finalization barrier so completed lanes still block on unresolved human feedback or escalation tickets from earlier waves.
package/README.md CHANGED
@@ -17,12 +17,12 @@ It includes:
17
17
  ## Quick Start
18
18
 
19
19
  Published package:
20
- - `@chllming/wave-orchestration@0.5.1`
21
- - Current release registry: `https://npm.pkg.github.com`
22
- - Release: [v0.5.1](https://github.com/chllming/wave-orchestration/releases/tag/v0.5.1)
23
- - npmjs trusted publishing workflow: [publish-npm.yml](./.github/workflows/publish-npm.yml)
20
+ - `@chllming/wave-orchestration@0.5.2`
21
+ - Primary public registry: `https://registry.npmjs.org`
22
+ - Release: [v0.5.2](https://github.com/chllming/wave-orchestration/releases/tag/v0.5.2)
23
+ - npmjs publish workflow: [publish-npm.yml](./.github/workflows/publish-npm.yml)
24
24
 
25
- Install after configuring the current GitHub Packages path from [github-packages-setup.md](./docs/reference/github-packages-setup.md):
25
+ Install from npmjs:
26
26
 
27
27
  ```bash
28
28
  pnpm add -D @chllming/wave-orchestration
@@ -38,18 +38,25 @@ If your repo already has Wave config, docs, or waves you want to keep:
38
38
  pnpm exec wave init --adopt-existing
39
39
  ```
40
40
 
41
+ ## New In 0.5.2
42
+
43
+ - Example `[wave-*]` markers inside fenced snippets or prose no longer satisfy closure; only real standalone structured signals count.
44
+ - `### Deliverables` is now enforced as an ownership-scoped file contract, so declared outputs must stay inside that agent's `File ownership` block.
45
+ - Regression coverage now includes both of those failure paths directly.
46
+
41
47
  ## New In 0.5.1
42
48
 
43
49
  - Phase 4 finalization now correctly stays blocked on unresolved human feedback and escalation items from completed waves.
44
50
  - Hermetic trace fixtures now force local executor coverage for seeded control-plane agents, so replay tests cannot accidentally launch real Codex, Claude Code, or OpenCode sessions.
45
- - npmjs trusted publishing is now wired in parallel with GitHub Packages, with maintainer docs for the first public npmjs release bootstrap.
51
+ - npmjs is now the primary public install path.
52
+ - npmjs publishing is wired in parallel with GitHub Packages, with repository-secret auth through `NPM_TOKEN`.
46
53
 
47
54
  ## New In 0.5.0
48
55
 
49
56
  - Capability-targeted work is now first-class: open capability requests become explicit helper assignments with deterministic assignee resolution, ledger visibility, inbox coverage, and closure barriers.
50
57
  - Cross-lane work is now first-class too: `wave dep post|show|resolve|render` manages typed dependency tickets, and required inbound or outbound dependencies now surface directly in lane state and gating.
51
58
  - Hermetic replay acceptance is now stronger around the runtime-orchestration layer, with stored outcome snapshots and launcher-generated local trace fixtures covering fallback, clarification, and dependency paths.
52
- - The package now carries explicit repository metadata so GitHub Packages can link it back to this repo cleanly.
59
+ - The package now carries explicit repository metadata for package and release provenance.
53
60
 
54
61
  ## Requirements
55
62
 
@@ -61,19 +68,15 @@ pnpm exec wave init --adopt-existing
61
68
 
62
69
  ## Install Into Another Repo
63
70
 
64
- 1. Use the current GitHub Packages install path from [github-packages-setup.md](./docs/reference/github-packages-setup.md).
65
-
66
- npmjs trusted publishing is prepared in this repo, but zero-token npmjs installs only become available after the first npmjs release is published.
67
-
68
- GitHub Packages npm installs still require authentication, even for public packages on `npm.pkg.github.com`.
69
-
70
- 2. Add the package:
71
+ 1. Install the package from npmjs:
71
72
 
72
73
  ```bash
73
74
  pnpm add -D @chllming/wave-orchestration
74
75
  ```
75
76
 
76
- 3. Initialize the repo:
77
+ If you need GitHub Packages instead, use the authenticated fallback in [github-packages-setup.md](./docs/reference/github-packages-setup.md).
78
+
79
+ 2. Initialize the repo:
77
80
 
78
81
  Fresh repo:
79
82
 
@@ -87,14 +90,14 @@ Existing repo with Wave config/docs/waves you want to preserve:
87
90
  pnpm exec wave init --adopt-existing
88
91
  ```
89
92
 
90
- 4. Run a non-mutating health check:
93
+ 3. Run a non-mutating health check:
91
94
 
92
95
  ```bash
93
96
  pnpm exec wave doctor
94
97
  pnpm exec wave launch --lane main --dry-run --no-dashboard
95
98
  ```
96
99
 
97
- 5. Upgrade later without overwriting plans or waves:
100
+ 4. Upgrade later without overwriting plans or waves:
98
101
 
99
102
  ```bash
100
103
  pnpm up @chllming/wave-orchestration
@@ -145,8 +148,8 @@ node scripts/wave.mjs launch --lane main --start-wave 0 --end-wave 0 --executor
145
148
  - [docs/plans/current-state.md](./docs/plans/current-state.md): shipped runtime and package capabilities
146
149
  - [docs/plans/master-plan.md](./docs/plans/master-plan.md): next priorities after the current shipped runtime
147
150
  - [docs/plans/migration.md](./docs/plans/migration.md): adopt this package into another repository
148
- - [docs/reference/github-packages-setup.md](./docs/reference/github-packages-setup.md): `.npmrc` and GitHub Packages auth details
149
- - [docs/reference/npmjs-trusted-publishing.md](./docs/reference/npmjs-trusted-publishing.md): maintainer setup for zero-token npmjs publishing from GitHub Actions
151
+ - [docs/reference/github-packages-setup.md](./docs/reference/github-packages-setup.md): optional `.npmrc` and GitHub Packages auth details
152
+ - [docs/reference/npmjs-trusted-publishing.md](./docs/reference/npmjs-trusted-publishing.md): maintainer setup for npmjs publishing through the repo workflow and `NPM_TOKEN`
150
153
  - [docs/reference/runtime-config/README.md](./docs/reference/runtime-config/README.md): runtime precedence, merge rules, and generated artifact paths
151
154
  - [docs/reference/runtime-config/codex.md](./docs/reference/runtime-config/codex.md): full Codex configuration reference
152
155
  - [docs/reference/runtime-config/claude.md](./docs/reference/runtime-config/claude.md): full Claude configuration reference
@@ -265,6 +268,7 @@ Each wave is regular markdown. The harness looks for:
265
268
  - `### Context7`
266
269
  - `### Components`
267
270
  - `### Capabilities`
271
+ - `### Deliverables`
268
272
  - `### Exit contract`
269
273
  - `### Prompt`
270
274
 
@@ -341,6 +345,11 @@ File ownership (only touch these paths):
341
345
  - proof: integration
342
346
  - doc-impact: owned
343
347
 
348
+ ### Deliverables
349
+
350
+ - src/example.ts
351
+ - test/example.test.ts
352
+
344
353
  ### Prompt
345
354
  ```text
346
355
  Read docs/reference/repository-guidance.md.
@@ -356,6 +365,8 @@ File ownership (only touch these paths):
356
365
 
357
366
  `### Capabilities` is optional. It lets the coordination layer route targeted follow-up work to a capability rather than a single hard-coded agent.
358
367
 
368
+ `### Deliverables` is also optional. When present, the launcher validates that each listed repo-relative file both exists and stays within the implementation agent's declared file ownership before the exit contract can pass.
369
+
359
370
  Open capability-targeted requests now become explicit helper assignments. The launcher resolves them deterministically, writes the assignment snapshot under `.tmp/`, mirrors the decision into the coordination log for the board and replay surface, and keeps the wave blocked until the linked follow-up resolves.
360
371
 
361
372
  The component matrix is also expected to reflect the landed state. Before a promoted wave closes, `docs/plans/component-cutover-matrix.json` should advance each promoted component's `currentLevel` to the proved target.
@@ -533,17 +544,17 @@ pnpm exec wave changelog --since-installed
533
544
 
534
545
  ## Research Sources
535
546
 
536
- The repository only commits a source index. Hydrated paper or article caches should stay local and ignored under `docs/research/cache/` or `docs/research/agent-context-cache/`.
537
-
538
- - [Effective harnesses for long-running agents](./docs/research/agent-context-cache/articles/effective-harnesses-for-long-running-agents.md)
539
- - [Harness engineering: leveraging Codex in an agent-first world](./docs/research/agent-context-cache/articles/harness-engineering-leveraging-codex-in-an-agent-first-world.md)
540
- - [Unlocking the Codex harness: how we built the App Server](./docs/research/agent-context-cache/articles/unlocking-the-codex-harness-how-we-built-the-app-server.md)
541
- - [Building Effective AI Coding Agents for the Terminal: Scaffolding, Harness, Context Engineering, and Lessons Learned](./docs/research/agent-context-cache/papers/building-effective-ai-coding-agents-for-the-terminal-scaffolding-harness-context-engineering-and-lessons-learned.md)
542
- - [VeRO: An Evaluation Harness for Agents to Optimize Agents](./docs/research/agent-context-cache/papers/vero-an-evaluation-harness-for-agents-to-optimize-agents.md)
543
- - [EvoClaw: Evaluating AI Agents on Continuous Software Evolution](./docs/research/agent-context-cache/papers/evoclaw-evaluating-ai-agents-on-continuous-software-evolution.md)
544
- - [LLM-based Multi-Agent Blackboard System for Information Discovery in Data Science](./docs/research/agent-context-cache/papers/llm-based-multi-agent-blackboard-system-for-information-discovery-in-data-science.md)
545
- - [Exploring Advanced LLM Multi-Agent Systems Based on Blackboard Architecture](./docs/research/agent-context-cache/papers/exploring-advanced-llm-multi-agent-systems-based-on-blackboard-architecture.md)
546
- - [DOVA: Deliberation-First Multi-Agent Orchestration for Autonomous Research Automation](./docs/research/agent-context-cache/papers/dova-deliberation-first-multi-agent-orchestration-for-autonomous-research-automation.md)
547
- - [Silo-Bench: A Scalable Environment for Evaluating Distributed Coordination in Multi-Agent LLM Systems](./docs/research/agent-context-cache/papers/silo-bench-a-scalable-environment-for-evaluating-distributed-coordination-in-multi-agent-llm-systems.md)
548
- - [SYMPHONY: Synergistic Multi-agent Planning with Heterogeneous Language Model Assembly](./docs/research/agent-context-cache/papers/symphony-synergistic-multi-agent-planning-with-heterogeneous-language-model-assembly.md)
549
- - [An Open Agent Architecture](./docs/research/agent-context-cache/papers/an-open-agent-architecture.md)
547
+ The canonical source index is [docs/research/agent-context-sources.md](./docs/research/agent-context-sources.md). Hydrated paper or article caches should stay local and ignored under `docs/research/cache/` or `docs/research/agent-context-cache/`.
548
+
549
+ - [Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents)
550
+ - [Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/)
551
+ - [Unlocking the Codex harness: how we built the App Server](https://openai.com/index/unlocking-the-codex-harness/)
552
+ - [Building Effective AI Coding Agents for the Terminal: Scaffolding, Harness, Context Engineering, and Lessons Learned](https://arxiv.org/abs/2603.05344)
553
+ - [VeRO: An Evaluation Harness for Agents to Optimize Agents](https://arxiv.org/abs/2602.22480)
554
+ - [EvoClaw: Evaluating AI Agents on Continuous Software Evolution](https://arxiv.org/abs/2603.13428)
555
+ - [LLM-Based Multi-Agent Blackboard System for Information Discovery in Data Science](https://arxiv.org/abs/2510.01285)
556
+ - [Exploring Advanced LLM Multi-Agent Systems Based on Blackboard Architecture](https://arxiv.org/abs/2507.01701)
557
+ - [DOVA: Deliberation-First Multi-Agent Orchestration for Autonomous Research Automation](https://arxiv.org/abs/2603.13327)
558
+ - [Silo-Bench: A Scalable Environment for Evaluating Distributed Coordination in Multi-Agent LLM Systems](https://arxiv.org/abs/2603.01045)
559
+ - [SYMPHONY: Synergistic Multi-agent Planning with Heterogeneous Language Model Assembly](https://arxiv.org/abs/2601.22623)
560
+ - [An Open Agent Architecture](https://cdn.aaai.org/Symposia/Spring/1994/SS-94-03/SS94-03-001.pdf)
@@ -2,18 +2,17 @@
2
2
 
3
3
  ## Default Adoption Path
4
4
 
5
- 1. Install from the current GitHub Packages path as described in [github-packages-setup.md](../reference/github-packages-setup.md).
6
- 2. Install the package with `pnpm add -D @chllming/wave-orchestration`.
7
- 3. For a fresh repo, run `pnpm exec wave init`.
8
- 4. For a repo that already has Wave config, docs, or waves you want to preserve, run `pnpm exec wave init --adopt-existing`.
9
- 5. Edit `wave.config.json` for the repo's docs, roles, validation rules, executor defaults, and component-cutover matrix paths.
10
- 6. Replace the starter plan docs, sample waves, and component cutover matrix with repository-specific ones.
11
- 7. Configure Context7 bundles for the external libraries that repo actually uses.
12
- 8. Run `pnpm exec wave doctor` and `pnpm exec wave launch --lane main --dry-run --no-dashboard` until validation passes.
13
- 9. Inspect seeded coordination and inbox artifacts with `pnpm exec wave coord show --lane main --wave 0 --dry-run --json` and `pnpm exec wave coord inbox --lane main --wave 0 --agent A1 --dry-run`.
14
- 10. Upgrade later with `pnpm up @chllming/wave-orchestration` and `pnpm exec wave upgrade`.
5
+ 1. Install the package from npmjs with `pnpm add -D @chllming/wave-orchestration`.
6
+ 2. For a fresh repo, run `pnpm exec wave init`.
7
+ 3. For a repo that already has Wave config, docs, or waves you want to preserve, run `pnpm exec wave init --adopt-existing`.
8
+ 4. Edit `wave.config.json` for the repo's docs, roles, validation rules, executor defaults, and component-cutover matrix paths.
9
+ 5. Replace the starter plan docs, sample waves, and component cutover matrix with repository-specific ones.
10
+ 6. Configure Context7 bundles for the external libraries that repo actually uses.
11
+ 7. Run `pnpm exec wave doctor` and `pnpm exec wave launch --lane main --dry-run --no-dashboard` until validation passes.
12
+ 8. Inspect seeded coordination and inbox artifacts with `pnpm exec wave coord show --lane main --wave 0 --dry-run --json` and `pnpm exec wave coord inbox --lane main --wave 0 --agent A1 --dry-run`.
13
+ 9. Upgrade later with `pnpm up @chllming/wave-orchestration` and `pnpm exec wave upgrade`.
15
14
 
16
- `wave-orchestration` also ships an npmjs trusted-publishing workflow for future zero-token installs, but that path is only active after the first npmjs release is published from this repo. Maintainer setup is documented in [npmjs-trusted-publishing.md](../reference/npmjs-trusted-publishing.md).
15
+ GitHub Packages remains available as an authenticated fallback path, and maintainer npm publishing setup is documented in [npmjs-trusted-publishing.md](../reference/npmjs-trusted-publishing.md).
17
16
 
18
17
  ## Upgrade Contract
19
18
 
@@ -179,6 +179,7 @@ pnpm exec wave changelog --since-installed
179
179
  - From the configured thresholds onward, declare `## Component promotions` and keep them aligned with the component cutover matrix.
180
180
  - From the configured thresholds onward, every non-A0/A8/A9 agent must declare `### Components` and emit `[wave-component]` markers for those components.
181
181
  - `### Capabilities` is optional and lets the scheduler route targeted follow-up work by capability.
182
+ - `### Deliverables` is optional and lets a wave declare exact repo-relative file outputs that must exist, and that stay within the agent's declared file ownership, before an implementation agent can satisfy its exit contract.
182
183
  - `### Executor` can declare `profile`, `fallbacks`, `tags`, and runtime budgets in addition to vendor-specific overrides.
183
184
  - Lane runtime policy can assign a default executor by role even when the wave omits `### Executor`.
184
185
  - Use `### Role prompts` for standing-role imports from `docs/agents/*.md`.
@@ -1,9 +1,9 @@
1
1
  # GitHub Packages Setup
2
2
 
3
- Use this package through GitHub Packages under the `@chllming` scope.
3
+ Use this page only if you intentionally want the legacy GitHub Packages install path.
4
4
 
5
5
  GitHub's npm registry still requires authentication for installs from `npm.pkg.github.com`, even when the package and backing repository are public.
6
- This remains the current install path for released versions until the npmjs publish workflow is used for a public npmjs release. If you want to prepare zero-token npmjs publishing for future releases, see [npmjs-trusted-publishing.md](./npmjs-trusted-publishing.md).
6
+ This is now the optional authenticated fallback path. The primary public install path is npmjs. Maintainer npm publishing setup is documented in [npmjs-trusted-publishing.md](./npmjs-trusted-publishing.md).
7
7
 
8
8
  ## `.npmrc`
9
9
 
@@ -1,54 +1,50 @@
1
- # npmjs Trusted Publishing
1
+ # npmjs Publishing
2
2
 
3
3
  This repo now includes a dedicated npmjs publish workflow at [publish-npm.yml](../../.github/workflows/publish-npm.yml).
4
4
 
5
- It is designed for npm trusted publishing from GitHub Actions, so the publish step does not need an `NPM_TOKEN`.
5
+ It currently publishes through a repository Actions secret named `NPM_TOKEN`.
6
6
 
7
7
  ## What This Repo Already Does
8
8
 
9
9
  - `package.json` no longer hardcodes GitHub Packages as the publish registry.
10
10
  - `publish-npm.yml` publishes tagged releases to `https://registry.npmjs.org`.
11
11
  - `publish-package.yml` still publishes to GitHub Packages explicitly, so both registries can coexist.
12
+ - `publish-npm.yml` expects `NPM_TOKEN` in GitHub Actions secrets.
12
13
 
13
14
  ## One-Time npm Setup
14
15
 
15
- 1. Open the package settings for `@chllming/wave-orchestration` on npmjs.com.
16
- 2. Go to `Settings` -> `Trusted publishing`.
17
- 3. Add a GitHub Actions trusted publisher with:
18
- - organization or user: `chllming`
19
- - repository: `wave-orchestration`
20
- - workflow filename: `publish-npm.yml`
21
- - environment name: leave empty unless you later add a protected GitHub environment to the workflow
22
- 4. Save the trusted publisher.
23
-
24
- If npmjs does not expose package settings for `@chllming/wave-orchestration` yet, complete the first npmjs publish manually once, then return and configure trusted publishing for later releases.
16
+ 1. Create an npm granular access token with:
17
+ - package or scope access for `@chllming/wave-orchestration`
18
+ - `Read and write` permission
19
+ - `Bypass 2FA` enabled
20
+ 2. In the GitHub repo `chllming/wave-orchestration`, add that token as an Actions secret named `NPM_TOKEN`.
21
+ 3. Rotate or revoke the token when no longer needed.
25
22
 
26
23
  ## GitHub Workflow Behavior
27
24
 
28
25
  The npmjs workflow:
29
26
 
30
27
  - runs on GitHub-hosted runners
31
- - requires `contents: read` and `id-token: write`
28
+ - requires `contents: read`
32
29
  - installs dependencies with `pnpm install --frozen-lockfile`
33
30
  - runs `pnpm test`
34
31
  - publishes with `pnpm publish --access public --no-git-checks`
35
-
36
- Trusted publishing depends on npm's OIDC support. The workflow is configured for Node 24 so the runner satisfies npm's current trusted-publishing requirements.
32
+ - authenticates with `NODE_AUTH_TOKEN=${{ secrets.NPM_TOKEN }}`
37
33
 
38
34
  ## Security Follow-Up
39
35
 
40
- After the first trusted publish succeeds:
36
+ After a successful npm publish:
41
37
 
42
- 1. Return to the npm package settings.
43
- 2. Restrict publishing access to trusted publishing / 2FA as appropriate for your account policy.
44
- 3. Remove any old publish-capable npm automation tokens that are no longer needed.
38
+ 1. Keep the token scoped only to this package or scope.
39
+ 2. Rotate the token periodically.
40
+ 3. Revoke emergency or temporary tokens once they are no longer needed.
45
41
 
46
- If this repo later needs private npm dependencies during CI, add a separate read-only install token for `pnpm install`. Trusted publishing only covers `npm publish` / `pnpm publish`.
42
+ If this repo later needs private npm dependencies during CI, consider a separate read-only install token rather than reusing the publish token.
47
43
 
48
44
  ## First Release Checklist
49
45
 
50
46
  1. Confirm [publish-npm.yml](../../.github/workflows/publish-npm.yml) is on the default branch.
51
- 2. Confirm the trusted publisher entry on npm matches `publish-npm.yml` exactly.
47
+ 2. Confirm `NPM_TOKEN` exists in the GitHub repo secrets.
52
48
  3. Confirm the package version has been bumped and committed.
53
49
  4. Push the release tag, for example `v0.4.1`.
54
50
  5. Verify the GitHub Actions run publishes successfully to npmjs.
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@chllming/wave-orchestration",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "license": "MIT",
5
5
  "description": "Generic wave-based multi-agent orchestration for repository work.",
6
- "packageManager": "pnpm@10.23.0",
7
6
  "repository": {
8
7
  "type": "git",
9
8
  "url": "git+https://github.com/chllming/wave-orchestration.git"
@@ -31,6 +30,12 @@
31
30
  "wave-dashboard": "scripts/wave-dashboard.mjs",
32
31
  "wave-local-executor": "scripts/wave-local-executor.mjs"
33
32
  },
33
+ "devDependencies": {
34
+ "@mozilla/readability": "^0.6.0",
35
+ "jsdom": "^29.0.1",
36
+ "pdfjs-dist": "^5.5.207",
37
+ "vitest": "3.2.4"
38
+ },
34
39
  "scripts": {
35
40
  "context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
36
41
  "research:import-agent-context": "node scripts/research/import-agent-context-archive.mjs scripts/research/manifests/harness-and-blackboard-2026-03-21.mjs",
@@ -43,11 +48,5 @@
43
48
  "wave:feedback": "node scripts/wave-human-feedback.mjs",
44
49
  "wave:launch": "node scripts/wave-launcher.mjs",
45
50
  "wave:local": "node scripts/wave-local-executor.mjs"
46
- },
47
- "devDependencies": {
48
- "@mozilla/readability": "^0.6.0",
49
- "jsdom": "^29.0.1",
50
- "pdfjs-dist": "^5.5.207",
51
- "vitest": "3.2.4"
52
51
  }
53
- }
52
+ }
@@ -2,6 +2,21 @@
2
2
  "schemaVersion": 1,
3
3
  "packageName": "@chllming/wave-orchestration",
4
4
  "releases": [
5
+ {
6
+ "version": "0.5.2",
7
+ "date": "2026-03-22",
8
+ "summary": "Closure-marker and deliverable-ownership hardening for wave exit validation.",
9
+ "features": [
10
+ "Structured wave markers now only count when emitted as real standalone signals, not when they appear inside fenced examples or prose snippets.",
11
+ "`### Deliverables` now requires repo-relative file outputs that stay within the agent's declared file ownership before implementation closure can pass.",
12
+ "Regression coverage now exercises the fenced-marker false-positive path and deliverable ownership violations directly."
13
+ ],
14
+ "manualSteps": [
15
+ "If a wave uses `### Deliverables`, rerun `pnpm exec wave launch --lane main --dry-run --no-dashboard` after upgrading and keep every deliverable inside that agent's `File ownership` block.",
16
+ "Do not rely on example `[wave-*]` snippets in agent output; only emitted standalone markers now count toward closure."
17
+ ],
18
+ "breaking": false
19
+ },
5
20
  {
6
21
  "version": "0.5.1",
7
22
  "date": "2026-03-22",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,3 +1,4 @@
1
+ import fs from "node:fs";
1
2
  import path from "node:path";
2
3
  import {
3
4
  REPO_ROOT,
@@ -34,6 +35,59 @@ const WAVE_GAP_REGEX =
34
35
  /^\[wave-gap\]\s*kind=(architecture|integration|durability|ops|docs)\s*(?:detail=(.*))?$/gim;
35
36
  const WAVE_COMPONENT_REGEX =
36
37
  /^\[wave-component\]\s*component=([a-z0-9._-]+)\s+level=([a-z0-9._-]+)\s+state=(met|gap)\s*(?:detail=(.*))?$/gim;
38
+ const STRUCTURED_SIGNAL_LINE_REGEX = /^\[wave-[^\]]+\].*$/;
39
+ const WRAPPED_STRUCTURED_SIGNAL_LINE_REGEX = /^`\[wave-[^`]+`$/;
40
+
41
+ function normalizeStructuredSignalText(text) {
42
+ if (!text) {
43
+ return "";
44
+ }
45
+ const normalizedLines = [];
46
+ let fenceLines = null;
47
+ for (const rawLine of String(text || "").split(/\r?\n/)) {
48
+ const trimmed = rawLine.trim();
49
+ if (/^```/.test(trimmed)) {
50
+ if (fenceLines === null) {
51
+ fenceLines = [];
52
+ continue;
53
+ }
54
+ const normalizedFenceLines = fenceLines
55
+ .map((line) => normalizeStructuredSignalLine(line))
56
+ .filter(Boolean);
57
+ if (normalizedFenceLines.length > 0 && normalizedFenceLines.length === fenceLines.length) {
58
+ normalizedLines.push(...normalizedFenceLines);
59
+ }
60
+ fenceLines = null;
61
+ continue;
62
+ }
63
+ if (fenceLines !== null) {
64
+ if (!trimmed) {
65
+ continue;
66
+ }
67
+ fenceLines.push(trimmed);
68
+ continue;
69
+ }
70
+ const normalized = normalizeStructuredSignalLine(trimmed);
71
+ if (normalized) {
72
+ normalizedLines.push(normalized);
73
+ }
74
+ }
75
+ return normalizedLines.join("\n");
76
+ }
77
+
78
+ function normalizeStructuredSignalLine(line) {
79
+ const trimmed = String(line || "").trim();
80
+ if (!trimmed) {
81
+ return null;
82
+ }
83
+ if (STRUCTURED_SIGNAL_LINE_REGEX.test(trimmed)) {
84
+ return trimmed;
85
+ }
86
+ if (WRAPPED_STRUCTURED_SIGNAL_LINE_REGEX.test(trimmed)) {
87
+ return trimmed.slice(1, -1);
88
+ }
89
+ return null;
90
+ }
37
91
 
38
92
  function cleanText(value) {
39
93
  return String(value || "").trim();
@@ -88,6 +142,51 @@ function findLatestComponentMatches(text) {
88
142
  return Array.from(byComponent.values());
89
143
  }
90
144
 
145
+ function detectTermination(logText, statusRecord) {
146
+ const patterns = [
147
+ { reason: "max-turns", regex: /(Reached max turns \(\d+\))/i },
148
+ { reason: "timeout", regex: /(timed out(?: after [^\n.]+)?)/i },
149
+ { reason: "session-missing", regex: /(session [^\n]+ disappeared before [^\n]+ was written)/i },
150
+ ];
151
+ for (const pattern of patterns) {
152
+ const match = String(logText || "").match(pattern.regex);
153
+ if (match) {
154
+ return {
155
+ reason: pattern.reason,
156
+ hint: cleanText(match[1] || match[0]),
157
+ };
158
+ }
159
+ }
160
+ const statusHint = cleanText(
161
+ statusRecord?.detail || statusRecord?.message || statusRecord?.error || statusRecord?.reason,
162
+ );
163
+ if (statusHint) {
164
+ return {
165
+ reason: "status-detail",
166
+ hint: statusHint,
167
+ };
168
+ }
169
+ const exitCode = Number.isFinite(Number(statusRecord?.code)) ? Number(statusRecord.code) : null;
170
+ if (exitCode !== null && exitCode !== 0) {
171
+ return {
172
+ reason: "exit-code",
173
+ hint: `Exit code ${exitCode}.`,
174
+ };
175
+ }
176
+ return {
177
+ reason: null,
178
+ hint: "",
179
+ };
180
+ }
181
+
182
+ function appendTerminationHint(detail, summary) {
183
+ const hint = cleanText(summary?.terminationHint || summary?.terminationReason);
184
+ if (!hint) {
185
+ return detail;
186
+ }
187
+ return `${detail} Termination: ${hint}`;
188
+ }
189
+
91
190
  function meetsOrExceeds(actual, required, orderMap) {
92
191
  if (!required) {
93
192
  return true;
@@ -153,6 +252,7 @@ export function readAgentExecutionSummary(summaryPathOrStatusPath) {
153
252
 
154
253
  export function buildAgentExecutionSummary({ agent, statusRecord, logPath, reportPath = null }) {
155
254
  const logText = readFileTail(logPath, 60000);
255
+ const signalText = normalizeStructuredSignalText(logText);
156
256
  const reportText =
157
257
  reportPath && readJsonOrNull(reportPath) === null
158
258
  ? readFileTail(reportPath, 60000)
@@ -160,38 +260,39 @@ export function buildAgentExecutionSummary({ agent, statusRecord, logPath, repor
160
260
  ? readFileTail(reportPath, 60000)
161
261
  : "";
162
262
  const reportVerdict = parseVerdictFromText(reportText, REPORT_VERDICT_REGEX);
163
- const logVerdict = parseVerdictFromText(logText, WAVE_VERDICT_REGEX);
263
+ const logVerdict = parseVerdictFromText(signalText, WAVE_VERDICT_REGEX);
164
264
  const verdict = reportVerdict.verdict ? reportVerdict : logVerdict;
265
+ const termination = detectTermination(logText, statusRecord);
165
266
  return {
166
267
  agentId: agent?.agentId || null,
167
268
  promptHash: statusRecord?.promptHash || null,
168
269
  exitCode: Number.isFinite(Number(statusRecord?.code)) ? Number(statusRecord.code) : null,
169
270
  completedAt: statusRecord?.completedAt || null,
170
- proof: findLastMatch(logText, WAVE_PROOF_REGEX, (match) => ({
271
+ proof: findLastMatch(signalText, WAVE_PROOF_REGEX, (match) => ({
171
272
  completion: match[1],
172
273
  durability: match[2],
173
274
  proof: match[3],
174
275
  state: match[4],
175
276
  detail: cleanText(match[5]),
176
277
  })),
177
- docDelta: findLastMatch(logText, WAVE_DOC_DELTA_REGEX, (match) => ({
278
+ docDelta: findLastMatch(signalText, WAVE_DOC_DELTA_REGEX, (match) => ({
178
279
  state: match[1],
179
280
  paths: parsePaths(match[2]),
180
281
  detail: cleanText(match[3]),
181
282
  })),
182
- docClosure: findLastMatch(logText, WAVE_DOC_CLOSURE_REGEX, (match) => ({
283
+ docClosure: findLastMatch(signalText, WAVE_DOC_CLOSURE_REGEX, (match) => ({
183
284
  state: match[1],
184
285
  paths: parsePaths(match[2]),
185
286
  detail: cleanText(match[3]),
186
287
  })),
187
- integration: findLastMatch(logText, WAVE_INTEGRATION_REGEX, (match) => ({
288
+ integration: findLastMatch(signalText, WAVE_INTEGRATION_REGEX, (match) => ({
188
289
  state: match[1],
189
290
  claims: Number.parseInt(String(match[2] || "0"), 10) || 0,
190
291
  conflicts: Number.parseInt(String(match[3] || "0"), 10) || 0,
191
292
  blockers: Number.parseInt(String(match[4] || "0"), 10) || 0,
192
293
  detail: cleanText(match[5]),
193
294
  })),
194
- gate: findLastMatch(logText, WAVE_GATE_REGEX, (match) => ({
295
+ gate: findLastMatch(signalText, WAVE_GATE_REGEX, (match) => ({
195
296
  architecture: match[1],
196
297
  integration: match[2],
197
298
  durability: match[3],
@@ -199,17 +300,25 @@ export function buildAgentExecutionSummary({ agent, statusRecord, logPath, repor
199
300
  docs: match[5],
200
301
  detail: cleanText(match[6]),
201
302
  })),
202
- components: findLatestComponentMatches(logText),
203
- gaps: findAllMatches(logText, WAVE_GAP_REGEX, (match) => ({
303
+ components: findLatestComponentMatches(signalText),
304
+ gaps: findAllMatches(signalText, WAVE_GAP_REGEX, (match) => ({
204
305
  kind: match[1],
205
306
  detail: cleanText(match[2]),
206
307
  })),
308
+ deliverables: Array.isArray(agent?.deliverables)
309
+ ? agent.deliverables.map((deliverable) => ({
310
+ path: deliverable,
311
+ exists: fs.existsSync(path.resolve(REPO_ROOT, deliverable)),
312
+ }))
313
+ : [],
207
314
  verdict: verdict.verdict
208
315
  ? {
209
316
  verdict: verdict.verdict,
210
317
  detail: cleanText(verdict.detail),
211
318
  }
212
319
  : null,
320
+ terminationReason: termination.reason,
321
+ terminationHint: termination.hint,
213
322
  logPath: path.relative(REPO_ROOT, logPath),
214
323
  reportPath: reportPath ? path.relative(REPO_ROOT, reportPath) : null,
215
324
  };
@@ -239,7 +348,7 @@ export function validateImplementationSummary(agent, summary) {
239
348
  return {
240
349
  ok: false,
241
350
  statusCode: "missing-wave-proof",
242
- detail: `Missing [wave-proof] marker for ${agent.agentId}.`,
351
+ detail: appendTerminationHint(`Missing [wave-proof] marker for ${agent.agentId}.`, summary),
243
352
  };
244
353
  }
245
354
  if (summary.proof.state !== "met") {
@@ -274,7 +383,7 @@ export function validateImplementationSummary(agent, summary) {
274
383
  return {
275
384
  ok: false,
276
385
  statusCode: "missing-doc-delta",
277
- detail: `Missing [wave-doc-delta] marker for ${agent.agentId}.`,
386
+ detail: appendTerminationHint(`Missing [wave-doc-delta] marker for ${agent.agentId}.`, summary),
278
387
  };
279
388
  }
280
389
  if (!meetsOrExceeds(summary.docDelta.state, contract.docImpact, DOC_IMPACT_ORDER)) {
@@ -319,6 +428,31 @@ export function validateImplementationSummary(agent, summary) {
319
428
  }
320
429
  }
321
430
  }
431
+ const deliverables = Array.isArray(agent?.deliverables) ? agent.deliverables : [];
432
+ if (deliverables.length > 0) {
433
+ const deliverableState = new Map(
434
+ Array.isArray(summary.deliverables)
435
+ ? summary.deliverables.map((deliverable) => [deliverable.path, deliverable])
436
+ : [],
437
+ );
438
+ for (const deliverablePath of deliverables) {
439
+ const deliverable = deliverableState.get(deliverablePath);
440
+ if (!deliverable) {
441
+ return {
442
+ ok: false,
443
+ statusCode: "missing-deliverable-summary",
444
+ detail: `Missing deliverable presence record for ${agent.agentId} path ${deliverablePath}.`,
445
+ };
446
+ }
447
+ if (deliverable.exists !== true) {
448
+ return {
449
+ ok: false,
450
+ statusCode: "missing-deliverable",
451
+ detail: `Agent ${agent.agentId} did not land required deliverable ${deliverablePath}.`,
452
+ };
453
+ }
454
+ }
455
+ }
322
456
  return {
323
457
  ok: true,
324
458
  statusCode: "pass",
@@ -331,7 +465,10 @@ export function validateDocumentationClosureSummary(agent, summary) {
331
465
  return {
332
466
  ok: false,
333
467
  statusCode: "missing-doc-closure",
334
- detail: `Missing [wave-doc-closure] marker for ${agent?.agentId || "A9"}.`,
468
+ detail: appendTerminationHint(
469
+ `Missing [wave-doc-closure] marker for ${agent?.agentId || "A9"}.`,
470
+ summary,
471
+ ),
335
472
  };
336
473
  }
337
474
  if (summary.docClosure.state === "delta") {
@@ -356,7 +493,10 @@ export function validateIntegrationSummary(agent, summary) {
356
493
  return {
357
494
  ok: false,
358
495
  statusCode: "missing-wave-integration",
359
- detail: `Missing [wave-integration] marker for ${agent?.agentId || "A8"}.`,
496
+ detail: appendTerminationHint(
497
+ `Missing [wave-integration] marker for ${agent?.agentId || "A8"}.`,
498
+ summary,
499
+ ),
360
500
  };
361
501
  }
362
502
  if (summary.integration.state !== "ready-for-doc-closure") {
@@ -380,14 +520,20 @@ export function validateEvaluatorSummary(agent, summary) {
380
520
  return {
381
521
  ok: false,
382
522
  statusCode: "missing-wave-gate",
383
- detail: `Missing [wave-gate] marker for ${agent?.agentId || "A0"}.`,
523
+ detail: appendTerminationHint(
524
+ `Missing [wave-gate] marker for ${agent?.agentId || "A0"}.`,
525
+ summary,
526
+ ),
384
527
  };
385
528
  }
386
529
  if (!summary?.verdict?.verdict) {
387
530
  return {
388
531
  ok: false,
389
532
  statusCode: "missing-evaluator-verdict",
390
- detail: `Missing Verdict line or [wave-verdict] marker for ${agent?.agentId || "A0"}.`,
533
+ detail: appendTerminationHint(
534
+ `Missing Verdict line or [wave-verdict] marker for ${agent?.agentId || "A0"}.`,
535
+ summary,
536
+ ),
391
537
  };
392
538
  }
393
539
  if (summary.verdict.verdict !== "pass") {
@@ -341,6 +341,14 @@ export function buildExecutionPrompt({
341
341
  "",
342
342
  ]
343
343
  : [];
344
+ const deliverableLines =
345
+ Array.isArray(agent.deliverables) && agent.deliverables.length > 0
346
+ ? [
347
+ "Deliverables required for this agent:",
348
+ ...agent.deliverables.map((deliverablePath) => `- ${deliverablePath}`),
349
+ "",
350
+ ]
351
+ : [];
344
352
 
345
353
  return [
346
354
  `Working directory: ${REPO_ROOT}`,
@@ -404,6 +412,7 @@ export function buildExecutionPrompt({
404
412
  ...exitContractLines,
405
413
  ...promotedComponentLines,
406
414
  ...ownedComponentLines,
415
+ ...deliverableLines,
407
416
  ...context7PromptLines,
408
417
  "Assigned implementation prompt:",
409
418
  "```",
@@ -63,30 +63,45 @@ function extractOwnedComponents(rawPrompt) {
63
63
  return components;
64
64
  }
65
65
 
66
- function extractDeliverables(promptText) {
66
+ function extractDeliverablesFromList(text, headingPattern) {
67
67
  const out = [];
68
- let inFileOwnership = false;
69
- for (const line of promptText.split(/\r?\n/)) {
70
- if (/^\s*File ownership\b/i.test(line)) {
71
- inFileOwnership = true;
68
+ let inList = false;
69
+ for (const line of String(text || "").split(/\r?\n/)) {
70
+ if (headingPattern.test(line)) {
71
+ inList = true;
72
72
  continue;
73
73
  }
74
- if (inFileOwnership && /^\s*[A-Za-z][A-Za-z0-9 _-]*:\s*$/.test(line)) {
75
- inFileOwnership = false;
74
+ if (inList && /^\s*[A-Za-z][A-Za-z0-9 _-]*:\s*$/.test(line)) {
75
+ inList = false;
76
76
  }
77
- if (inFileOwnership) {
78
- const bulletMatch = line.match(/^\s*-\s+(.+?)\s*$/);
79
- if (bulletMatch) {
80
- const cleaned = bulletMatch[1].replace(/[`"']/g, "").trim();
81
- if (
82
- cleaned.includes("/") ||
83
- /\.(md|mdx|js|mjs|ts|go|json|yaml|yml|sh|sql)$/.test(cleaned)
84
- ) {
85
- out.push(cleaned);
86
- continue;
87
- }
88
- }
77
+ if (!inList) {
78
+ continue;
79
+ }
80
+ const bulletMatch = line.match(/^\s*-\s+(.+?)\s*$/);
81
+ if (!bulletMatch) {
82
+ continue;
83
+ }
84
+ const cleaned = bulletMatch[1].replace(/[`"']/g, "").trim();
85
+ if (
86
+ cleaned.includes("/") ||
87
+ /\.(md|mdx|js|mjs|ts|go|json|yaml|yml|sh|sql)$/.test(cleaned)
88
+ ) {
89
+ out.push(cleaned);
89
90
  }
91
+ }
92
+ return out;
93
+ }
94
+
95
+ function extractDeliverables(rawPrompt, promptText) {
96
+ const explicit = extractDeliverablesFromList(
97
+ rawPrompt,
98
+ /^\s*Deliverables required for this agent:\s*$/i,
99
+ );
100
+ if (explicit.length > 0) {
101
+ return Array.from(new Set(explicit));
102
+ }
103
+ const out = extractDeliverablesFromList(promptText, /^\s*File ownership\b/i);
104
+ for (const line of String(promptText || "").split(/\r?\n/)) {
90
105
  const match = line.match(/^\s*\d+[.)]\s*(.+?)\s*$/);
91
106
  if (!match) {
92
107
  continue;
@@ -203,7 +218,7 @@ export function runLocalExecutorCli(argv) {
203
218
  const integrationAgent = agentId === integrationAgentId;
204
219
  const ownedComponents = extractOwnedComponents(rawPrompt);
205
220
  const assignedPrompt = extractAssignedPrompt(rawPrompt);
206
- const deliverables = extractDeliverables(assignedPrompt);
221
+ const deliverables = extractDeliverables(rawPrompt, assignedPrompt);
207
222
  if (deliverables.length === 0) {
208
223
  console.log("[local-executor] no deliverables detected; nothing to do.");
209
224
  if (evaluatorAgent) {
@@ -173,6 +173,77 @@ function parseComponentList(blockText, filePath, label) {
173
173
  return components;
174
174
  }
175
175
 
176
+ function parsePathList(blockText, filePath, label) {
177
+ if (!blockText) {
178
+ return [];
179
+ }
180
+ const paths = [];
181
+ const seen = new Set();
182
+ for (const line of String(blockText || "").split(/\r?\n/)) {
183
+ const trimmed = line.trim();
184
+ if (!trimmed) {
185
+ continue;
186
+ }
187
+ const bulletMatch = trimmed.match(/^-\s+(.+?)\s*$/);
188
+ if (!bulletMatch) {
189
+ throw new Error(`Malformed path entry "${trimmed}" in ${label} (${filePath})`);
190
+ }
191
+ const relPath = bulletMatch[1].replace(/[`"']/g, "").trim();
192
+ if (!isRepoContainedPath(relPath)) {
193
+ throw new Error(`Path "${relPath}" in ${label} (${filePath}) must stay within the repo root`);
194
+ }
195
+ if (seen.has(relPath)) {
196
+ throw new Error(`Duplicate path "${relPath}" in ${label} (${filePath})`);
197
+ }
198
+ seen.add(relPath);
199
+ paths.push(relPath);
200
+ }
201
+ return paths;
202
+ }
203
+
204
+ function normalizeRepoRelativePath(relPath) {
205
+ return String(relPath || "")
206
+ .replaceAll("\\", "/")
207
+ .replace(/^\.\/+/, "")
208
+ .trim();
209
+ }
210
+
211
+ function isOwnedDirectoryPath(relPath) {
212
+ return normalizeRepoRelativePath(relPath).endsWith("/");
213
+ }
214
+
215
+ function deliverableIsOwned(deliverablePath, ownedPath) {
216
+ const deliverable = normalizeRepoRelativePath(deliverablePath);
217
+ const owned = normalizeRepoRelativePath(ownedPath);
218
+ if (!deliverable || !owned) {
219
+ return false;
220
+ }
221
+ if (isOwnedDirectoryPath(owned)) {
222
+ return deliverable.startsWith(owned);
223
+ }
224
+ return deliverable === owned;
225
+ }
226
+
227
+ function validateAgentDeliverables(deliverables, ownedPaths, filePath, agentId) {
228
+ if (!Array.isArray(deliverables) || deliverables.length === 0) {
229
+ return;
230
+ }
231
+ const owned = Array.isArray(ownedPaths) ? ownedPaths : [];
232
+ for (const deliverablePath of deliverables) {
233
+ const normalized = normalizeRepoRelativePath(deliverablePath);
234
+ if (normalized.endsWith("/")) {
235
+ throw new Error(
236
+ `Deliverable "${deliverablePath}" for agent ${agentId} in ${filePath} must be a file path, not a directory path`,
237
+ );
238
+ }
239
+ if (!owned.some((ownedPath) => deliverableIsOwned(normalized, ownedPath))) {
240
+ throw new Error(
241
+ `Deliverable "${deliverablePath}" for agent ${agentId} in ${filePath} must stay within the agent's declared file ownership`,
242
+ );
243
+ }
244
+ }
245
+ }
246
+
176
247
  function extractFencedBlock(blockText, messagePrefix) {
177
248
  const fencedBlockMatch = String(blockText || "").match(
178
249
  /```(?:[a-zA-Z0-9_-]+)?\r?\n([\s\S]*?)\r?\n```/,
@@ -636,6 +707,13 @@ export function extractAgentCapabilitiesFromSection(sectionText, filePath, agent
636
707
  return parseComponentList(block, filePath, `agent ${agentId} capabilities`);
637
708
  }
638
709
 
710
+ export function extractAgentDeliverablesFromSection(sectionText, filePath, agentId) {
711
+ const block = extractSectionBody(sectionText, "Deliverables", filePath, agentId, {
712
+ required: false,
713
+ });
714
+ return parsePathList(block, filePath, `agent ${agentId} deliverables`);
715
+ }
716
+
639
717
  export function slugify(value) {
640
718
  return value
641
719
  .toLowerCase()
@@ -1202,6 +1280,11 @@ export function parseWaveContent(content, filePath, options = {}) {
1202
1280
  const executorConfig = extractExecutorConfigFromSection(sectionText, filePath, current.agentId);
1203
1281
  const components = extractAgentComponentsFromSection(sectionText, filePath, current.agentId);
1204
1282
  const capabilities = extractAgentCapabilitiesFromSection(sectionText, filePath, current.agentId);
1283
+ const deliverables = extractAgentDeliverablesFromSection(
1284
+ sectionText,
1285
+ filePath,
1286
+ current.agentId,
1287
+ );
1205
1288
  const promptOverlay = extractPromptFromSection(sectionText, filePath, current.agentId);
1206
1289
  const prompt = composeResolvedPrompt(
1207
1290
  rolePromptPaths,
@@ -1213,6 +1296,7 @@ export function parseWaveContent(content, filePath, options = {}) {
1213
1296
  },
1214
1297
  );
1215
1298
  const ownedPaths = extractOwnedPaths(promptOverlay);
1299
+ validateAgentDeliverables(deliverables, ownedPaths, filePath, current.agentId);
1216
1300
  agents.push({
1217
1301
  agentId: current.agentId,
1218
1302
  title: current.title,
@@ -1225,6 +1309,7 @@ export function parseWaveContent(content, filePath, options = {}) {
1225
1309
  executorConfig,
1226
1310
  components,
1227
1311
  capabilities,
1312
+ deliverables,
1228
1313
  ownedPaths,
1229
1314
  });
1230
1315
  }
package/scripts/wave.mjs CHANGED
File without changes