@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 +6 -0
- package/README.md +44 -33
- package/docs/plans/migration.md +10 -11
- package/docs/plans/wave-orchestrator.md +1 -0
- package/docs/reference/github-packages-setup.md +2 -2
- package/docs/reference/npmjs-trusted-publishing.md +17 -21
- package/package.json +8 -9
- package/releases/manifest.json +15 -0
- package/scripts/context7-api-check.sh +0 -0
- package/scripts/context7-export-env.sh +0 -0
- package/scripts/wave-autonomous.mjs +0 -0
- package/scripts/wave-dashboard.mjs +0 -0
- package/scripts/wave-human-feedback.mjs +0 -0
- package/scripts/wave-launcher.mjs +0 -0
- package/scripts/wave-local-executor.mjs +0 -0
- package/scripts/wave-orchestrator/agent-state.mjs +160 -14
- package/scripts/wave-orchestrator/coordination.mjs +9 -0
- package/scripts/wave-orchestrator/local-executor.mjs +35 -20
- package/scripts/wave-orchestrator/wave-files.mjs +85 -0
- package/scripts/wave.mjs +0 -0
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.
|
|
21
|
-
-
|
|
22
|
-
- Release: [v0.5.
|
|
23
|
-
- npmjs
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
537
|
-
|
|
538
|
-
- [Effective harnesses for long-running agents](
|
|
539
|
-
- [Harness engineering: leveraging Codex in an agent-first world](
|
|
540
|
-
- [Unlocking the Codex harness: how we built the App Server](
|
|
541
|
-
- [Building Effective AI Coding Agents for the Terminal: Scaffolding, Harness, Context Engineering, and Lessons Learned](
|
|
542
|
-
- [VeRO: An Evaluation Harness for Agents to Optimize Agents](
|
|
543
|
-
- [EvoClaw: Evaluating AI Agents on Continuous Software Evolution](
|
|
544
|
-
- [LLM-
|
|
545
|
-
- [Exploring Advanced LLM Multi-Agent Systems Based on Blackboard Architecture](
|
|
546
|
-
- [DOVA: Deliberation-First Multi-Agent Orchestration for Autonomous Research Automation](
|
|
547
|
-
- [Silo-Bench: A Scalable Environment for Evaluating Distributed Coordination in Multi-Agent LLM Systems](
|
|
548
|
-
- [SYMPHONY: Synergistic Multi-agent Planning with Heterogeneous Language Model Assembly](
|
|
549
|
-
- [An Open Agent Architecture](
|
|
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)
|
package/docs/plans/migration.md
CHANGED
|
@@ -2,18 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Default Adoption Path
|
|
4
4
|
|
|
5
|
-
1. Install
|
|
6
|
-
2.
|
|
7
|
-
3. For a
|
|
8
|
-
4.
|
|
9
|
-
5.
|
|
10
|
-
6.
|
|
11
|
-
7.
|
|
12
|
-
8.
|
|
13
|
-
9.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
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`
|
|
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
|
|
36
|
+
After a successful npm publish:
|
|
41
37
|
|
|
42
|
-
1.
|
|
43
|
-
2.
|
|
44
|
-
3.
|
|
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,
|
|
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
|
|
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.
|
|
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
|
+
}
|
package/releases/manifest.json
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
203
|
-
gaps: findAllMatches(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
66
|
+
function extractDeliverablesFromList(text, headingPattern) {
|
|
67
67
|
const out = [];
|
|
68
|
-
let
|
|
69
|
-
for (const line of
|
|
70
|
-
if (
|
|
71
|
-
|
|
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 (
|
|
75
|
-
|
|
74
|
+
if (inList && /^\s*[A-Za-z][A-Za-z0-9 _-]*:\s*$/.test(line)) {
|
|
75
|
+
inList = false;
|
|
76
76
|
}
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|