session-orchestrator 3.17.0 → 3.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/030-wave-execution.mdc +17 -1
- package/CHANGELOG.md +105 -412
- package/README.md +12 -9
- package/SECURITY.md +190 -27
- package/agents/AGENTS.md +20 -3
- package/agents/code-implementer.md +6 -6
- package/agents/db-specialist.md +1 -1
- package/agents/qa-strategist.md +31 -6
- package/agents/schemas/qa-strategist.schema.json +27 -0
- package/agents/schemas/test-writer.schema.json +60 -2
- package/agents/security-reviewer.md +1 -1
- package/agents/session-reviewer.md +1 -1
- package/agents/test-writer.md +29 -10
- package/agents/ui-developer.md +1 -1
- package/commands/contract-version-bump.md +28 -0
- package/commands/portfolio.md +1 -1
- package/docs/USER-GUIDE.md +8 -3
- package/docs/ci-setup.md +121 -7
- package/docs/codex-setup.md +1 -1
- package/docs/components.md +6 -6
- package/docs/cursor-setup.md +22 -9
- package/docs/events-schema.md +5 -1
- package/docs/instruction-delivery.md +444 -0
- package/docs/rule-authoring.md +58 -9
- package/docs/session-config-reference.md +244 -9
- package/docs/session-config-template.md +39 -3
- package/hooks/_lib/guard-source-loader.mjs +467 -0
- package/hooks/_lib/lock-bootstrap.mjs +21 -0
- package/hooks/_lib/vcs-create-matcher.mjs +119 -0
- package/hooks/config-protection.mjs +0 -0
- package/hooks/enforce-commands.mjs +10 -2
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +11 -2
- package/hooks/hooks-pi.json +10 -0
- package/hooks/hooks.json +21 -1
- package/hooks/on-session-end.mjs +178 -18
- package/hooks/on-session-start.mjs +23 -0
- package/hooks/post-bash-write-verify.mjs +977 -0
- package/hooks/post-subagent-discovery-validator.mjs +256 -41
- package/hooks/pre-bash-destructive-guard.mjs +525 -160
- package/hooks/pre-bash-issue-budget.mjs +167 -0
- package/hooks/pre-bash-sessions-ledger-guard.mjs +627 -0
- package/hooks/pre-bash-templates-first.mjs +96 -63
- package/hooks/subagent-telemetry.mjs +527 -37
- package/package.json +5 -2
- package/pi/prompts/contract-version-bump.md +12 -0
- package/rules/README.md +32 -0
- package/scripts/archive-closed-prds.mjs +12 -22
- package/scripts/autopilot-multi.mjs +103 -20
- package/scripts/backfill-abandoned-sessions.mjs +160 -4
- package/scripts/check-doc-consistency.sh +17 -1
- package/scripts/eval-session.mjs +50 -9
- package/scripts/fleet-instruction-scan.mjs +141 -0
- package/scripts/lib/autopilot/mr-draft.mjs +31 -1
- package/scripts/lib/autopilot/worktree-pipeline.mjs +113 -5
- package/scripts/lib/backlog-scan.mjs +39 -6
- package/scripts/lib/blocked-commands-policy.mjs +340 -0
- package/scripts/lib/ci-status-banner.mjs +75 -12
- package/scripts/lib/claude-md-budget-lint.mjs +283 -34
- package/scripts/lib/command-blocker.mjs +1013 -58
- package/scripts/lib/config/config-protection.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +9 -1
- package/scripts/lib/config/gitlab-portfolio.mjs +1 -1
- package/scripts/lib/config/issue-budget.mjs +123 -0
- package/scripts/lib/config/reconcile.mjs +21 -0
- package/scripts/lib/config/section-extractor.mjs +121 -1
- package/scripts/lib/config-schema.mjs +23 -3
- package/scripts/lib/config.mjs +17 -0
- package/scripts/lib/convergence-monitor.mjs +49 -3
- package/scripts/lib/description-surface.mjs +535 -0
- package/scripts/lib/dispatcher/enumerate.mjs +26 -40
- package/scripts/lib/ecosystem-wizard/config-writer.mjs +26 -24
- package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +1 -1
- package/scripts/lib/eval/engine.mjs +47 -5
- package/scripts/lib/events.mjs +59 -7
- package/scripts/lib/gates/gate-full.mjs +15 -3
- package/scripts/lib/gates/gate-helpers.mjs +132 -6
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +28 -8
- package/scripts/lib/gitlab-portfolio/aggregator.mjs +8 -2
- package/scripts/lib/gitlab-portfolio/cli.mjs +1 -1
- package/scripts/lib/handover-gate.mjs +7 -3
- package/scripts/lib/harness-audit/categories/category4.mjs +9 -3
- package/scripts/lib/instruction-budget-guard.mjs +402 -51
- package/scripts/lib/io.mjs +345 -10
- package/scripts/lib/issue-budget.mjs +269 -0
- package/scripts/lib/issue-close-strip-labels.mjs +39 -9
- package/scripts/lib/label-scope.mjs +47 -0
- package/scripts/lib/learnings/schema.mjs +43 -3
- package/scripts/lib/lock-reaper.mjs +1 -2
- package/scripts/lib/memory-proposals/schema.mjs +36 -1
- package/scripts/lib/peer-discovery.mjs +645 -0
- package/scripts/lib/pi-hook-bridge.mjs +146 -17
- package/scripts/lib/product-repo-detect.mjs +9 -8
- package/scripts/lib/project-hygiene.mjs +432 -0
- package/scripts/lib/quality-gate.mjs +167 -0
- package/scripts/lib/recommendations-v0.mjs +1 -1
- package/scripts/lib/reconcile/eligibility.mjs +1 -1
- package/scripts/lib/reconcile/emitter.mjs +23 -4
- package/scripts/lib/reconcile/engine.mjs +147 -39
- package/scripts/lib/reconcile/idempotency.mjs +114 -14
- package/scripts/lib/reconcile-nudge-banner.mjs +65 -9
- package/scripts/lib/resource-probe/evaluate.mjs +70 -4
- package/scripts/lib/resource-probe.mjs +19 -0
- package/scripts/lib/rule-loader.mjs +6 -0
- package/scripts/lib/scope-baseline.mjs +564 -0
- package/scripts/lib/scope-gate.mjs +399 -98
- package/scripts/lib/session-close-backfill.mjs +61 -6
- package/scripts/lib/session-end/phase-skip.mjs +1 -0
- package/scripts/lib/session-id.mjs +221 -41
- package/scripts/lib/session-lock.mjs +304 -6
- package/scripts/lib/session-schema/constants.mjs +22 -3
- package/scripts/lib/session-schema/validator.mjs +16 -0
- package/scripts/lib/sessions-integrity-banner.mjs +294 -0
- package/scripts/lib/sessions-staleness-banner.mjs +121 -12
- package/scripts/lib/skill-evolution/idempotency.mjs +135 -16
- package/scripts/lib/skill-evolution/mr-opener.mjs +9 -1
- package/scripts/lib/spiral-carryover.mjs +142 -30
- package/scripts/lib/state-md/mission-status.mjs +53 -3
- package/scripts/lib/subagents-schema.mjs +43 -9
- package/scripts/lib/test-runner/issue-reconcile.mjs +53 -13
- package/scripts/lib/tests-src-ratio.mjs +484 -0
- package/scripts/lib/validate/check-agents.mjs +56 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +244 -10
- package/scripts/lib/validate/check-rules.mjs +217 -35
- package/scripts/lib/validate/check-test-value-bans.mjs +782 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate-vendored-rules.mjs +10 -2
- package/scripts/lib/vault-archive.mjs +17 -2
- package/scripts/lib/vault-backfill/glab.mjs +8 -0
- package/scripts/lib/vault-mirror/process.mjs +30 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +293 -36
- package/scripts/lib/vcs-repo-spec.mjs +362 -0
- package/scripts/lib/wave-resource-gate.mjs +115 -11
- package/scripts/lib/worktree/listing.mjs +44 -7
- package/scripts/mcp-server.sh +17 -3
- package/scripts/measure-context-overhead.sh +151 -0
- package/scripts/memory-propose.mjs +72 -9
- package/scripts/print-applicable-rules.mjs +51 -12
- package/scripts/release.mjs +534 -0
- package/scripts/run-quality-gate.mjs +123 -5
- package/scripts/validate-wave-scope.mjs +182 -17
- package/scripts/vault-integration-watcher.mjs +32 -10
- package/skills/_shared/config-reading.md +2 -2
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +145 -28
- package/skills/contract-version-bump/SKILL.md +219 -0
- package/skills/discovery/SKILL.md +4 -4
- package/skills/discovery/issue-templates.md +11 -11
- package/skills/discovery/probes-audit.md +1 -1
- package/skills/discovery/probes-feature.md +1 -1
- package/skills/discovery/probes-session.md +26 -5
- package/skills/ecosystem-health/SKILL.md +1 -1
- package/skills/ecosystem-health/wizard.md +4 -4
- package/skills/evolve/SKILL.md +1 -0
- package/skills/gitlab-ops/SKILL.md +20 -12
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/hook-development/SKILL.md +1 -1
- package/skills/mode-selector/SKILL.md +1 -1
- package/skills/npm-publish/SKILL.md +17 -1
- package/skills/plan/SKILL.md +5 -5
- package/skills/plan/mode-feature.md +4 -4
- package/skills/plan/mode-new.md +10 -10
- package/skills/plan/mode-retro.md +1 -1
- package/skills/quality-gates/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +21 -4
- package/skills/session-end/SKILL.md +34 -13
- package/skills/session-end/discovery-scan.md +4 -2
- package/skills/session-end/drift-operations.md +4 -4
- package/skills/session-end/metrics-collection.md +13 -0
- package/skills/session-end/phase-3-2-docs-verification.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +2 -1
- package/skills/session-end/plan-verification.md +5 -2
- package/skills/session-end/vault-operations.md +1 -1
- package/skills/session-end/verification-checklist.md +1 -1
- package/skills/session-plan/SKILL.md +6 -2
- package/skills/session-plan/wave-template.md +2 -0
- package/skills/session-start/SKILL.md +73 -7
- package/skills/session-start/phase-4-5-resource-health.md +15 -2
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/vault-sync/validator.mjs +108 -7
- package/skills/wave-executor/SKILL.md +5 -2
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/wave-loop.md +163 -10
- package/templates/_shared/loop.md +4 -4
|
@@ -31,6 +31,7 @@ The coordinator determines each agent's status after the wave completes:
|
|
|
31
31
|
1. **Read the agent's final output** and look for the `STATUS:` line
|
|
32
32
|
2. **Map to status**:
|
|
33
33
|
- `STATUS: done` → agent completed successfully
|
|
34
|
+
- `STATUS: no-tests-needed` → **SUCCESS variant, treat exactly like `done`.** test-writer-specific: the agent found no nameable bug the existing suite misses, so writing a test was the wrong move (`.claude/rules/test-value.md`; enum in `agents/schemas/test-writer.schema.json`). NEVER map this to `failed` or carry it over.
|
|
34
35
|
- `STATUS: partial` → agent hit turn limit or couldn't finish (PARTIAL)
|
|
35
36
|
- `STATUS: failed` → agent encountered an error it couldn't recover from (FAILED)
|
|
36
37
|
- No STATUS line found → infer from output: if agent produced changes, mark as `done`; if it reported errors, mark as `failed`; if output is truncated, mark as `partial`
|
|
@@ -45,6 +46,7 @@ The coordinator determines each agent's status after the wave completes:
|
|
|
45
46
|
| Status | Meaning | Trigger | Recovery |
|
|
46
47
|
|--------|---------|---------|----------|
|
|
47
48
|
| **done** | Agent completed all assigned work | Agent reports `STATUS: done` | None needed |
|
|
49
|
+
| **no-tests-needed** | SUCCESS variant of **done** — test-writer found no nameable gap, so no test was the correct outcome | Agent reports `STATUS: no-tests-needed` | None needed — never a failure, never a carryover |
|
|
48
50
|
| **partial** | Agent made progress but couldn't finish | Turn limit hit, or agent reports `STATUS: partial` | Carry forward remaining work to next wave with context |
|
|
49
51
|
| **failed** | Agent couldn't make meaningful progress | Tool errors, invalid assumptions, agent reports `STATUS: failed` | Re-dispatch with corrected instructions and narrower scope |
|
|
50
52
|
| **spiral** | Agent got stuck in an edit loop | Same file edited 3+ times (detected post-wave) | Revert agent's changes, narrow scope, split task if needed |
|
|
@@ -29,6 +29,34 @@ For each wave, resolve its assigned role(s) from the session plan's role-to-wave
|
|
|
29
29
|
3. Proceed to next wave immediately
|
|
30
30
|
4. Do NOT write wave-scope.json for skipped waves
|
|
31
31
|
|
|
32
|
+
### 0a. Scope Baseline Freeze (S2 — #896)
|
|
33
|
+
|
|
34
|
+
Run this ONCE, immediately after the Self-Report above and before Wave 1 dispatches — never per-wave, same "before the first wave" anchor as the empty-waves rule above. Freezes the session's scope baseline into STATE.md frontmatter so the drift tripwire in step 7a below has a denominator to compare the rest of the session against.
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import { writeBaseline } from '$PLUGIN_ROOT/scripts/lib/scope-baseline.mjs';
|
|
38
|
+
|
|
39
|
+
const result = await writeBaseline({
|
|
40
|
+
repoRoot: process.cwd(),
|
|
41
|
+
intent: '<one-line session intent, from the agreed session plan>',
|
|
42
|
+
ownerBoundary: '<the plan\'s file-scope boundary, e.g. the union of declared agent file scopes>',
|
|
43
|
+
plannedFiles: <the RAW array of declared agent file-scope paths, unfiltered
|
|
44
|
+
— the UNION of every wave's per-agent "Files:" specs. Pass the array as-is;
|
|
45
|
+
`writeBaseline()` filters it internally via `DRIFT_EXCLUDE_PATTERNS`
|
|
46
|
+
(the same `filterExcluded()` helper the S2 drift tripwire's numerator
|
|
47
|
+
uses in step 7 below), so both sides of the ratio are produced by ONE
|
|
48
|
+
code path (#894 review finding F1 — the coordinator no longer has to
|
|
49
|
+
remember to pre-filter in prose). MUST be an array — issue #903 removed
|
|
50
|
+
the previously-accepted plain pre-counted-number call shape (it was an
|
|
51
|
+
unverified re-entry vector for the same F1 filter-bypass bug); anything
|
|
52
|
+
else is rejected up front with `reason: 'invalid-planned-files'`.>,
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Best-effort — never blocks Wave 1 from dispatching. `result.written === false` with `reason: 'already-frozen'` is expected and silent (a prior wave-executor pass in this same session already froze the baseline — do not re-freeze, do not log). Log any OTHER `reason` (`invalid-planned-files`, `no-state-md`, `unreadable-state-md`, `lock-timeout`, `lock-fs-error`, `unexpected-error`, `size-ceiling`, `frontmatter-unsafe`) as an informational note in the wave progress update — none of these block dispatch.
|
|
57
|
+
|
|
58
|
+
Skip entirely when `persistence: false` in Session Config (no STATE.md exists in that mode).
|
|
59
|
+
|
|
32
60
|
### 0.5. Pre-Dispatch Resource Gate (#193)
|
|
33
61
|
|
|
34
62
|
Before dispatching agents, the coordinator runs a resource gate to decide whether the wave should proceed as planned, reduce its agent count, or escalate to coordinator-direct. Gated on `$CONFIG["resource-awareness"]` (default: true).
|
|
@@ -179,6 +207,49 @@ Before dispatching each agent whose fileScope contains a NEW (non-existent) file
|
|
|
179
207
|
|
|
180
208
|
**If 0 candidates:** dispatch unchanged — same silent-no-op convention as Grounding Injection / Frontmatter-Guard above. Never blocks dispatch.
|
|
181
209
|
|
|
210
|
+
#### Pre-Dispatch: Fact-Staleness Annotation (#908)
|
|
211
|
+
|
|
212
|
+
Facts an earlier wave measured get quoted into this wave's prompts as briefing truth — and they decay. In the #908 incident the impl agents found 14 commits where the brief said 9, a clean tree where it said 5 dirty, 92 learnings where it said 40, a file that no longer existed, and a closed epic briefed as critical-open; the worst class was line numbers, which drifted three times and forced 225 citations onto symbol+grep form. Annotating a fact costs one prompt line. Re-briefing a wave on wrong numbers costs the wave.
|
|
213
|
+
|
|
214
|
+
**What is a fact here:** any repo-state value carried from an earlier wave's report into this prompt — counts (commits, files, tests, issues, learnings), line numbers, file existence, "session X is running", issue/epic open-closed state. NOT design decisions, task assignments, or judgements: those do not decay.
|
|
215
|
+
|
|
216
|
+
**Trigger — annotate when ANY of these holds (no judgement call):**
|
|
217
|
+
|
|
218
|
+
1. `now − measured_at ≥ 5 min`
|
|
219
|
+
2. `measured_at` is absent
|
|
220
|
+
3. the peer probe below reported `live: true` for the repo the fact is about
|
|
221
|
+
|
|
222
|
+
Threshold derived from `.orchestrator/metrics/subagents.jsonl` (n=340 wave boundaries, agent runtime median 3.5 min): a fact's age at its FIRST cross-wave citation brackets [median 2.5 min, median 9.9 min] depending on where in the producing agent's run it was measured. 5 min sits at the conservative end of that bracket; the cost asymmetry breaks the tie downward. **Corollary: a fact from an earlier wave almost always trips rule 1 — when in doubt, annotate.** `measured_at` comes from the producing agent's report, which `hooks/post-subagent-discovery-validator.mjs` already asks for (PSA-006 point 4).
|
|
223
|
+
|
|
224
|
+
**Peer signal — once per wave, plus once per distinct foreign repo cited:**
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
node "$PLUGIN_ROOT/scripts/lib/peer-discovery.mjs" --check-live "<repoRoot>" --json
|
|
228
|
+
# → {"live":false,"reason":"no-lock","probe":"lock-only","peerCount":0,"peer":null}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Read `live` from the payload; the exit code reports whether the probe RAN (`0` verdict produced, `1` usage error, `2` internal failure), never the verdict itself. Probe selection is automatic and needs no flag: the coordinator's own working copy takes the `full` probe (worktrees + registry + STATE.md), any other repo takes `lock-only` (two sync calls, no git). Own-vs-foreign is decided by repo IDENTITY, not path nesting — a parent directory that is itself a repo (`~/Projects/<workspace>`) is foreign, not "mine".
|
|
232
|
+
|
|
233
|
+
Call it for the coordinator's own repo even when every cited fact is about that repo — the own-repo probe self-excludes this session and answers "is another operator session writing into my working copy right now", which is exactly the #908 "14 vs 9 commits" class. `live: true` sets the threshold to **0** for that repo: every state fact about it is asserted, never established, however fresh. The probe is fail-safe (unmeasurable ⇒ `live: true`, including `probe: "full-degraded"` when the peer surfaces returned demonstrably incomplete data), so a probe failure annotates more, never less — and so does a non-zero exit: treat exit `1`/`2` as `live: true`.
|
|
234
|
+
|
|
235
|
+
**Annotation format** — in the agent prompt, replace the bare value with:
|
|
236
|
+
|
|
237
|
+
ASSERTED (age <N> min, source W<k>/<agent>): <value>. Verify command: <cmd>. Run it before relying on this.
|
|
238
|
+
|
|
239
|
+
**When no measurement command can be named** (rule 2, and the case the validator is meant to catch upstream), do not restate the value at all — a number nobody can re-derive is not a fact:
|
|
240
|
+
|
|
241
|
+
UNVERIFIED (no measurement command, source W<k>/<agent>): <claim>. Establish it yourself before relying on this.
|
|
242
|
+
|
|
243
|
+
**Worked examples:**
|
|
244
|
+
|
|
245
|
+
| Fact | Decision |
|
|
246
|
+
|---|---|
|
|
247
|
+
| "13 broken paths", W1-D2, `measured_at` 10:35, cited at 11:20 | Rule 1 (45 min ≥ 5) → `ASSERTED (age 45 min, source W1/D2): 13 broken paths. Verify command: <the grep D2 ran>. Run it before relying on this.` |
|
|
248
|
+
| "the coordinator mis-measured 4 numbers" — no measurement command | Rule 2 → `UNVERIFIED` form; the value is dropped, the claim becomes the agent's own task |
|
|
249
|
+
| Any count about a foreign repo whose peer probe reports `live: true` | Rule 3 → annotate regardless of age; age may still be printed but is not the reason |
|
|
250
|
+
|
|
251
|
+
**Never blocks dispatch** — same silent-no-op convention as the injectors above. When facts cannot be annotated for any reason, dispatch proceeds; annotating more is always the safe direction.
|
|
252
|
+
|
|
182
253
|
#### Agent-Type Resolution
|
|
183
254
|
|
|
184
255
|
Each agent in the session plan specifies a `subagent_type`. Use that value directly when dispatching:
|
|
@@ -192,6 +263,7 @@ For each agent in this wave:
|
|
|
192
263
|
- Which files to read/modify (exact paths)
|
|
193
264
|
- Acceptance criteria (how to verify done)
|
|
194
265
|
- Relevant patterns — injected automatically as the <APPLICABLE-RULES> block (see Pre-Dispatch: Glob-Scoped Rule Injection below)
|
|
266
|
+
- Any repo-state fact carried from an earlier wave: in the ASSERTED/UNVERIFIED form, never as a bare value (see Pre-Dispatch: Fact-Staleness Annotation above)
|
|
195
267
|
- VCS issue reference if applicable
|
|
196
268
|
- What NOT to touch (other agents' files)
|
|
197
269
|
>",
|
|
@@ -322,6 +394,8 @@ Behaviour change: agents writing vault notes now receive the canonical schema en
|
|
|
322
394
|
|
|
323
395
|
After `wave-scope.json` is written for this wave and before assembling the `Agent()` prompt, inject the wave's applicable rule set into each dispatched agent's prompt. This wires the `loadApplicableRules()` loader (`scripts/lib/rule-loader.mjs`) — dormant since #336 — into the live per-wave prompt assembly via the thin CLI `scripts/print-applicable-rules.mjs`.
|
|
324
396
|
|
|
397
|
+
> **⚠ Measure before you inject — on Claude Code this step is usually a NET LOSS (#931b).** `docs/instruction-delivery.md` measured the delivery path on 2026-07-30: every `.claude/rules/*.md` already reaches a dispatched agent through Claude Code's **native project-instruction loading**, so a `$RULES_BLOCK` prepended on top arrives a *second* time. Measured on a real wave: the scoped block was 122,875 B against a 169,961 B corpus — glob scoping saved **4.0%**, of which 85.5% came from the tier axis alone, while injecting alongside undiminished native delivery cost **+72%** (292,836 B). The coordinator SHOULD therefore check the block's size before prepending it, and MAY skip the injection with a logged Deviation when the harness already delivers the corpus natively — that is not a shortcut, it is the measured decision. Inject unconditionally only on a harness that does NOT auto-load `.claude/rules/` (Codex CLI, Pi, Cursor), where this block is the sole delivery path and the saving is real. See `docs/instruction-delivery.md` §1.2 and §5.
|
|
398
|
+
|
|
325
399
|
**Gate:** runs when `.claude/rules/` exists. When it does not, the CLI prints nothing and exits 0 — zero behaviour change. This step never blocks dispatch: any non-zero exit or empty output means "inject nothing, continue" (same best-effort framing as Pre-Dispatch Grounding Injection above).
|
|
326
400
|
|
|
327
401
|
**Per-wave scoping (not per-agent):** the rule set is computed ONCE per wave from the wave's `allowedPaths` union (the same `wave-scope.json` source used elsewhere), not per agent. The CLI resolves `scopePaths` from `allowedPaths`, `mode` from the `session-type:` frontmatter in `.claude/STATE.md`, and `hostClass` from `.orchestrator/host.json` — all overridable, all degrading to "no gating" when unreadable.
|
|
@@ -552,7 +626,7 @@ Log every non-`pass` result as an event to `.orchestrator/metrics/events.jsonl`
|
|
|
552
626
|
- After **Impl-Polish**: Incremental quality checks + integration verification
|
|
553
627
|
- **Simplification pass** (at the start of the Quality wave, before test/review agents):
|
|
554
628
|
1. Identify all files changed in this session: `git diff --name-only $SESSION_START_REF..HEAD`
|
|
555
|
-
2.
|
|
629
|
+
2. Partition the list into **production files** (exclude `*.test.*`, `*.spec.*`, `__tests__/`) and **test files** (exactly that excluded set). Both branches below are independent: skip a branch when its partition is empty; skip the pass entirely only when BOTH partitions are empty — then proceed directly to test/review agents.
|
|
556
630
|
3. Dispatch 1-2 simplification agents with:
|
|
557
631
|
- Changed file list (production files only — exclude `*.test.*`, `*.spec.*`, `__tests__/`)
|
|
558
632
|
- Reference: `slop-patterns.md` from the discovery skill directory — include the actual patterns in the agent prompt
|
|
@@ -561,7 +635,16 @@ Log every non-`pass` result as an event to `.orchestrator/metrics/events.jsonl`
|
|
|
561
635
|
- Instruction: "Review each changed file for AI-generated code patterns. Apply targeted simplifications: remove unnecessary try-catch around non-throwing operations, delete over-documentation (params that repeat the name, returns that say 'the result'), replace re-implemented stdlib functions with standard alternatives, simplify redundant boolean logic (if/else returning true/false, double negation, explicit boolean comparisons). Do NOT change functionality. Do NOT touch files you weren't given. Do NOT commit."
|
|
562
636
|
- Tools: Read, Edit, Grep, Glob
|
|
563
637
|
- Model: sonnet
|
|
564
|
-
4.
|
|
638
|
+
4. **Test-consolidation branch** — in the SAME dispatch round as step 3, dispatch exactly 1 test-consolidation agent with:
|
|
639
|
+
- File list: the test partition from step 2 (this session's changed test files) plus their immediate neighbours (sibling test files covering the same module — resolve via the production file's basename, e.g. `foo.mjs` → `tests/**/foo*.test.mjs`)
|
|
640
|
+
- Instruction: "Consolidate this test corpus. (a) Merge duplicated tests that differ only in input/expected values into ONE parameterized test (table-driven / `it.each`). (b) DELETE any test that fails the falsification check — ask for each test: *would this test go RED if a real bug were introduced in the code it claims to cover?* If no, it catches nothing; remove it. (c) DELETE getter/setter tests, framework-behaviour tests, and prose-presence tests (assertions that a doc/skill file merely CONTAINS a phrase) — see `.claude/rules/testing.md` § 'Test Quality — False-Positive Prevention' and § 'When NOT to Write Tests'. Do NOT touch production files. Do NOT commit."
|
|
641
|
+
- **Contract**: the set of bugs the suite catches may only stay the same or GROW. Never delete a test that is the sole falsifier of a real behaviour — when in doubt, keep and report it. Deletions are a SUCCESS outcome, not a regression: a net-negative test LOC with an unchanged bug-catch set is the intended result of this branch.
|
|
642
|
+
- **Report**: the agent MUST emit `test_delta: {added, removed, consolidated, net_loc}` in its report so the coordinator can record the pass's effect.
|
|
643
|
+
- Tools: Read, Edit, Grep, Glob
|
|
644
|
+
- Model: sonnet
|
|
645
|
+
5. After the simplification and test-consolidation agents complete, proceed to Quality test/review agents
|
|
646
|
+
- **Review panel = primary bug-catch mechanism (Quality wave)**: the Quality wave's central verification instrument is a multi-persona review panel — `security-reviewer`, `qa-strategist`, `architect-reviewer` — dispatched read-only (Read/Grep/Glob, no Edit/Write) and scoped to the FULL session diff `$SESSION_START_REF..HEAD`, not to a single wave's file scope. Test-writing in this wave is need-gated, not default (see `SKILL.md` § "Agent Prompt Best Practices" point 5): an agent writes a test only for a bug it can name.
|
|
647
|
+
Rationale — 2026-07 evidence: the HIGH/MED product bugs actually caught in this repo's sessions came from panel review (argument injection in a base-branch value, a fail-open config gate, a never-wired max-proposals cap, a glob-metacharacter bypass), not from growth of the test corpus. Panel breadth over the full diff also catches coordinator-written code, which per-wave agent scopes never cover.
|
|
565
648
|
- After **Quality**: Full Gate quality checks per quality-gates (typecheck + test + lint, must all pass)
|
|
566
649
|
(Full Gate is NEVER skipped regardless of cache state — this is the close-safety invariant. As of #724 this mandate is MECHANICAL, not prose-only: the Baseline cache check above passes `waveRole: 'Quality'`, so `shouldSkipIncremental` hard-returns `skip: false` before any cache/diff logic. A targeted/incremental pass is necessary but NOT sufficient — the Quality-wave completion requires the full typecheck + test + lint run.)
|
|
567
650
|
- After **Finalization**: final git status check
|
|
@@ -732,8 +815,37 @@ If the commit itself fails (e.g., nothing to commit, pre-commit hook rejects), d
|
|
|
732
815
|
- `planned_files_count`: size of this wave's Planned set (union of agent file scopes) as computed in step 3c File-level grounding above. Reuse that value — do not recompute.
|
|
733
816
|
- `over_delivery_ratio`: files_changed / max(planned_files_count, 1), rounded to 2 decimals. > 1 = agents touched more files than briefed (under-sizing signal, #730/H4). Omit both fields when `grounding-check: false`.
|
|
734
817
|
- `quality_check`: incremental check result (pass/fail/skipped)
|
|
818
|
+
- `suite_passed` / `suite_failed` (+ optional `suite_platform`): the full-suite counts feeding the § 3a Wave History header `— suite <passed>/<failed> on <platform>`. `quality_check` is a traffic light; these are the number the light was derived from, and unlike STATE.md (gitignored, demoted to `## Previous Session` and then overwritten) the metrics record survives the session.
|
|
819
|
+
**Copy the two counts off the gate's own event — do not re-read them from the terminal (#966 step 3).** `scripts/run-quality-gate.mjs`, the wrapper that fires between waves, emits `orchestrator.quality_gate.{passed,failed}` carrying a machine-measured `counts: {passed, failed, total}` (admitted by `admitSuiteCounts()`) plus the `wave_number` it resolved from `wave-scope.json`. Payload fields are flat at the record's top level:
|
|
820
|
+
|
|
821
|
+
```bash
|
|
822
|
+
jq -c --argjson w <wave_number> --arg s "<semantic_session_id>" '
|
|
823
|
+
select(.event | startswith("orchestrator.quality_gate."))
|
|
824
|
+
| select(.semantic_session_id == $s and .wave_number == $w and .counts != null)
|
|
825
|
+
| .counts' .orchestrator/metrics/events.jsonl | tail -1
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
The session filter is not optional — `events.jsonl` accumulates across sessions and every past session also had a wave with this number.
|
|
829
|
+
**OMIT all three when that selector returns nothing** — absent = "not measured", `suite_failed: 0` = "measured, zero failures". Never write `0` for a suite that did not run. The event enforces the same distinction at the producer: `counts` is omitted, never zero-filled, when the run fail-fast'd before the test gate or its output carried no parseable count.
|
|
830
|
+
> **What is NOT on the event, and stays hand-written:** `suite_platform` — the payload has no platform field, so keep writing it from the § 3a header as before. Likewise, the auto-fix-loop producer (`scripts/lib/quality-gate.mjs`, active only under `verification-auto-fix.enabled: true`) emits `counts` WITHOUT `wave_number`, so its retry records correctly never match the selector above; they are mid-wave attempts, not the wave's verdict. If the wave's gate ran outside `run-quality-gate.mjs` entirely, no event exists — fall back to the gate output you read, and say so in the progress update. The reader side (`skills/session-end/metrics-collection.md` § 1.7) reads the event first and this hand-written trio second, so keep writing the trio: it is the compatibility path for those two cases and for sessions already in flight.
|
|
735
831
|
Append this wave record to the session metrics `waves` array.
|
|
736
832
|
|
|
833
|
+
7a. **Scope drift tripwire (S2 — #896, warn-only)**: distinct from `over_delivery_ratio` above — that metric is per-wave and unfiltered; this one is session-cumulative (since `session-start-ref`) and filtered through `DRIFT_EXCLUDE_PATTERNS`, so the two numbers are NOT expected to agree. Call `computeDrift()` from the same `scripts/lib/scope-baseline.mjs` module as § 0a Scope Baseline Freeze above. Never blocks — exit code stays 0 and the next wave is dispatched regardless of the result.
|
|
834
|
+
|
|
835
|
+
```js
|
|
836
|
+
import { computeDrift } from '$PLUGIN_ROOT/scripts/lib/scope-baseline.mjs';
|
|
837
|
+
|
|
838
|
+
const drift = computeDrift({ repoRoot: process.cwd(), threshold: 2.0 });
|
|
839
|
+
if (drift.skipped === false && drift.breached) {
|
|
840
|
+
console.warn(
|
|
841
|
+
`⚠ Scope drift: filesRatio ${drift.filesRatio} (${drift.actualFiles} actual / ${drift.plannedFiles} planned files) ` +
|
|
842
|
+
`>= threshold ${drift.threshold} — session has grown beyond its frozen scope baseline.`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
Include the WARN line verbatim in the wave progress update when `breached` is true — name `filesRatio`, `plannedFiles`, `actualFiles`, and the configured `threshold`, not merely the word "drift". `drift.skipped === true` (`no-state-md`, `unreadable-state-md`, `no-baseline`, `stale-baseline`, or `unresolvable-ref` — see `computeDrift()`'s JSDoc for the precedence order) is silent: no WARN, no progress-update line. `persistence: false` implies `no-state-md`, so this step degrades to a silent no-op in that mode without a separate gate check.
|
|
848
|
+
|
|
737
849
|
### 3. Adapt Plan (if needed)
|
|
738
850
|
|
|
739
851
|
After reviewing wave results, decide:
|
|
@@ -774,8 +886,12 @@ After each wave completes and before the progress update, update `<state-dir>/ST
|
|
|
774
886
|
1. **Frontmatter**: set `current-wave` to the just-completed wave number; set `status` to `active` (or `paused` if waiting on user input)
|
|
775
887
|
2. **`## Current Wave`**: replace contents with next wave info — wave number, role, agents to dispatch and count
|
|
776
888
|
3. **`## Wave History`**: append an entry for the completed wave (the `(planned … → actual …, over-delivery …)` parenthetical is omitted when `grounding-check: false`, since the counts are unavailable):
|
|
889
|
+
> **Record the SUITE COUNT, not just "gates green" — and name the platform (#944).** The wave line MUST carry the full-suite pass/fail count from the gate that just ran (`<passed>/<failed>`), not merely that typecheck and lint were clean. A deep session on 2026-07-30 logged typecheck/lint/validate-plugin for every wave and no suite count; a test that had been vacuous for its entire life sat red on HEAD through three waves and was found only by the review panel — in a session whose own premise was turning CI from red to green.
|
|
890
|
+
>
|
|
891
|
+
> **A green gate on one platform is not evidence for another.** That same session's local gate reported 541/541 three times on a tree CI could not build: two tests encoded macOS assumptions (a `TMPDIR` that carries a trailing slash; an `ARG_MAX` that tolerates a 200 KB argv entry). Both passed locally and failed on the Linux runner. When the wave touched anything platform-sensitive — spawn/argv shapes, `os.tmpdir()`, path separators, file modes, `$PATH` lookups of external binaries — say so in the wave line, and treat CI, not the local run, as the verdict.
|
|
892
|
+
|
|
777
893
|
```
|
|
778
|
-
### Wave N — <Role> (planned <P> files → actual <A>, over-delivery <R>)
|
|
894
|
+
### Wave N — <Role> (planned <P> files → actual <A>, over-delivery <R>) — suite <passed>/<failed> on <platform>
|
|
779
895
|
- Agent "<description>": <done|partial|failed> — <files changed> — <1-line note>
|
|
780
896
|
- Agent "<description>": <done|partial|failed> — <files changed> — <1-line note>
|
|
781
897
|
```
|
|
@@ -951,7 +1067,7 @@ This frames the nudge: the persistent artefacts (plan, scope, STATE.md, git diff
|
|
|
951
1067
|
|
|
952
1068
|
| Wave boundary (completed → next) | Compact? | Why |
|
|
953
1069
|
|---|---|---|
|
|
954
|
-
| Discovery → Impl-Core | Yes | Research/audit context is bulky
|
|
1070
|
+
| Discovery → Impl-Core | Yes — **but only after** Discovery's repo-state facts are written into the plan in annotated form (value + measurement command + `measured_at`) | Research/audit context is bulky and the plan + wave-scope.json is the distilled output. But compacting discards the raw evidence and leaves the briefing text as the only source of truth — the structural amplifier of the #908 damage. Un-annotated facts: re-measure or record them first, else **No**. See Pre-Dispatch: Fact-Staleness Annotation. |
|
|
955
1071
|
| Impl-Core → Impl-Polish (long Core) | Maybe | Compact only if Polish targets different files; keep if Polish builds on Core's changes. |
|
|
956
1072
|
| Impl-Polish → Quality | No | Quality references the just-written code; losing it is costly. |
|
|
957
1073
|
| Quality → Finalization | No | Finalization needs the full session diff. |
|
|
@@ -990,13 +1106,17 @@ Before each wave dispatch:
|
|
|
990
1106
|
1. **Write `<state-dir>/wave-scope.json`** with the wave's scope:
|
|
991
1107
|
> (Platform-specific: `.claude/wave-scope.json` on Claude Code, `.codex/wave-scope.json` on Codex CLI, `.cursor/wave-scope.json` on Cursor IDE)
|
|
992
1108
|
|
|
993
|
-
**Deriving `blockedCommands` (policy
|
|
1109
|
+
**Deriving `blockedCommands` (effective floor∪overlay policy, #155/#972):** Before writing `wave-scope.json`, derive the blocked patterns from the EFFECTIVE policy via the shared merge module — the plugin's floor policy united with the repo's overlay policy. (A bare `jq` over the repo-local policy file alone under-counts the merged result since #972.)
|
|
994
1110
|
```bash
|
|
995
|
-
BLOCKED=$(
|
|
1111
|
+
BLOCKED=$(node --input-type=module -e "
|
|
1112
|
+
import { loadEffectivePolicy } from '$PLUGIN_ROOT/scripts/lib/blocked-commands-policy.mjs';
|
|
1113
|
+
const { rules } = await loadEffectivePolicy({ cwd: process.cwd(), projectDir: process.env.CLAUDE_PROJECT_DIR ?? null, pluginRoot: '$PLUGIN_ROOT' });
|
|
1114
|
+
console.log(JSON.stringify((rules ?? []).filter(r => r.severity === 'block').map(r => r.pattern)));
|
|
1115
|
+
")
|
|
996
1116
|
```
|
|
997
|
-
Use `$BLOCKED` as the `blockedCommands` value in `wave-scope.json`.
|
|
1117
|
+
Use `$BLOCKED` as the `blockedCommands` value in `wave-scope.json`. Since #972 this is the effective floor∪overlay policy — identical to what the destructive-guard hook enforces.
|
|
998
1118
|
|
|
999
|
-
**Fallback:** If
|
|
1119
|
+
**Fallback:** If the command fails or prints `[]` (neither the plugin's floor policy nor a repo policy resolvable — pre-#155 setup), use the legacy hardcoded array and log a warning in the wave progress update:
|
|
1000
1120
|
```bash
|
|
1001
1121
|
BLOCKED='["rm -rf", "git push --force", "DROP TABLE", "git reset --hard", "git checkout -- ."]'
|
|
1002
1122
|
# Warning: policy file .orchestrator/policy/blocked-commands.json not found — using legacy hardcoded blocklist
|
|
@@ -1008,7 +1128,7 @@ Before each wave dispatch:
|
|
|
1008
1128
|
"role": "<role>",
|
|
1009
1129
|
"enforcement": "<from Session Config, default: warn>",
|
|
1010
1130
|
"allowedPaths": ["<from agent specs in session plan>"],
|
|
1011
|
-
"blockedCommands": "<derived dynamically from
|
|
1131
|
+
"blockedCommands": "<derived dynamically from the effective floor∪overlay policy via loadEffectivePolicy (severity: block rules, #972); falls back to legacy 5-element array if no policy resolves>",
|
|
1012
1132
|
"gates": "<copy of enforcement-gates from Session Config, or omit if unset>"
|
|
1013
1133
|
}
|
|
1014
1134
|
```
|
|
@@ -1017,20 +1137,53 @@ Before each wave dispatch:
|
|
|
1017
1137
|
3. `allowedPaths` is the UNION of all agent file scopes for this wave
|
|
1018
1138
|
To compute `allowedPaths`: read each agent's specification from the session plan. Each agent lists its "Files:" scope (e.g., `skills/session-end/SKILL.md`, `scripts/*.sh`). Collect all file paths and glob patterns from all agents in this wave into a single flat array. Deduplicate entries. If an agent's scope uses globs (e.g., `scripts/*.sh`), include the glob pattern as-is — the enforcement hook resolves globs at check time.
|
|
1019
1139
|
|
|
1140
|
+
**Test-Sibling Expansion (#970):** an `allowedPaths` entry that names a production file but NOT its test sibling makes the wave's own regression test unwritable — the scope guard then mechanically enforces exactly the inconsistency the quality gate exists to catch. Cross-repo evidence, three occurrences in ONE session: a migrations glob without the SQL-test directory (the regression test could not be written); a lone `.actions.ts` file (the wave's cross-tenant security test stayed red); a dead-export deletion whose importing test lay outside every scope (the suite ended red). Do NOT hand-derive the sibling paths — expand through the shared helper so the hook, the validator and this prose state one rule:
|
|
1141
|
+
|
|
1142
|
+
```js
|
|
1143
|
+
import { expandTestSiblings } from '$PLUGIN_ROOT/scripts/lib/scope-gate.mjs';
|
|
1144
|
+
|
|
1145
|
+
// unionScopes: the deduplicated flat array from the paragraph above.
|
|
1146
|
+
// role: this wave's role, verbatim from the session plan — the helper GATES on it.
|
|
1147
|
+
const allowedPaths = expandTestSiblings(unionScopes, { role });
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
The helper is pure (same input → same output, no filesystem writes) and is also surfaced by `scripts/validate-wave-scope.mjs`. **The role decides, inside the helper** — `scripts/lib/scope-gate.mjs` `TEST_SIBLING_EXPANSION_ROLES` is THE list (currently `Impl-Core`, `Impl-Polish` — exactly where the incident occurred), and #5/#6 below describe that gate rather than restating it. Pass the role string; do not pre-filter by role in prose, and do not hand-roll the equivalent `{ enabled: … }`. Matching is trimmed + case-insensitive, so `impl-core` behaves as `Impl-Core`.
|
|
1151
|
+
|
|
1152
|
+
> **Fail-closed:** an ABSENT or unrecognised `role` does **not** expand. Omitting it fails loudly (an agent's write to its own test is blocked, recoverable by one re-union); the opposite default would silently hand a Quality phase-1 simplification agent write access to the suite. `{ enabled: false }` is the unconditional opt-out and `{ enabled: true }` the explicit opt-in — both override the role.
|
|
1153
|
+
|
|
1154
|
+
**It emits a GLOB, never a computed concrete path.** Resolve via the production file's basename, e.g. `foo.mjs` → `tests/**/foo*.test.mjs` — the same form `§ 4. Test-consolidation branch` already uses, stated once. Measured over all **439** tracked production `.mjs` in THIS repo (production = `scripts/**` + `hooks/**` + `skills/**`; tests = a top-level `tests/**` mirror with the `scripts/` prefix dropped): a same-basename test exists somewhere under `tests/` for **375/439 (85.4%)**, whereas a naive 1:1 mirror path resolves for only **272/439 (62.0%)**. So the glob is right ~85% of the time and *harmless* when wrong — it grants write access to a path that may not exist; a computed concrete path would be wrong ~38% of the time **and still deny the real test**. The ~15% residual is real, mostly semantic naming (`scripts/lib/learnings/*.mjs` → `tests/unit/learnings.test.mjs`): when an agent's test sibling does not match the glob, add it by hand to that agent's "Files:" scope in the session plan. This is an 85% default, not a guarantee.
|
|
1155
|
+
|
|
1156
|
+
> Measured at `HEAD=730ee9d`, 2026-08-03, clean-tree, via `git ls-files | grep -E '^(scripts|hooks|skills)/.*\.mjs$'` for the denominator, matched against `git ls-files | grep -E '^tests/.*\.test\.mjs$'` by basename (85.4% figure) and by mirrored path (62.0% figure). Re-measure before citing these downstream — a count re-briefed later is a claim about the past (`.claude/rules/parallel-sessions.md` § PSA-006).
|
|
1157
|
+
|
|
1158
|
+
**The sibling rule is repo-configurable, not a hardcoded layout.** THIS repo has zero `__tests__/` directories and no co-located tests; consumer-repo shapes (`<file>.test.*` beside the source, `<dir>/__tests__/**`, `supabase/migrations/** → supabase/tests/**`) are configured per repo and do not apply here.
|
|
1159
|
+
|
|
1160
|
+
Three ordering constraints, all load-bearing:
|
|
1161
|
+
- Expand each agent's `fileScope` **before** the overlap/deconfliction check, so a test file newly shared by two agents is visible to the check that exists to catch that collision.
|
|
1162
|
+
- Expand **before** `wave-scope.json` is written, in ONE pass. `hooks/post-bash-write-verify.mjs` fingerprints `allowedPaths` via `scopeSignature()` and fires a control notice on change, so a later mutation reads as tampering.
|
|
1163
|
+
- Skip **absolute** entries entirely — expanding a Gate-5b out-of-repo grant would sprout a synthetic `tests/**` sibling outside the repo.
|
|
1164
|
+
|
|
1020
1165
|
**Pre-Dispatch Scope-Union Assertion (#796):** `wave-scope.json` is GLOBAL per wave — `hooks/enforce-scope.mjs` Gate 7 checks EVERY agent against the same `allowedPaths` union, so a union that (re)written for only ONE agent silently denies its siblings' legitimate writes. Before each `Agent()` batch, mechanically assert — for EVERY agent in the batch — that its fileScope ⊆ `wave-scope.allowedPaths`. Write the agent's "Files:" scope as a JSON array of strings to a temp file (`$AGENT_FILESCOPE_JSON`) and run:
|
|
1021
1166
|
|
|
1022
1167
|
```bash
|
|
1023
1168
|
node "$PLUGIN_ROOT/scripts/validate-wave-scope.mjs" \
|
|
1024
|
-
--assert-subset "$AGENT_FILESCOPE_JSON"
|
|
1169
|
+
--assert-subset "$AGENT_FILESCOPE_JSON" --expand-test-siblings \
|
|
1170
|
+
< <state-dir>/wave-scope.json
|
|
1025
1171
|
```
|
|
1026
1172
|
|
|
1173
|
+
`--expand-test-siblings` (#970) is the mechanical half of the Test-Sibling Expansion rule above: it re-derives each agent's siblings and requires the union to grant them, so "the coordinator ran the expansion" stops being a matter of prose compliance. Pass it on **every** batch — the flag is gated on the manifest's own `role` through the same `TEST_SIBLING_EXPANSION_ROLES` predicate the helper uses, so it is a self-announcing no-op (`WARN: … skipped for role "Quality"`) wherever expansion does not fire. Do not add a role condition in the shell; that would put the role list back in prose.
|
|
1174
|
+
|
|
1175
|
+
It only ever ADDS a requirement, so a manifest that passed the plain subset check can now fail — that is the point. On exit 1 (`allowedPaths does not grant the test sibling … missing: [...]`): the union was not produced by `expandTestSiblings`. Re-run the Scope Manifest step, rewrite `wave-scope.json`, re-assert. Do NOT hand-add the missing glob and move on — the next agent in the batch will hit the same gap. (If a legitimate test sibling does not match the emitted glob — the ~15% residual — it belongs in that agent's "Files:" scope in the session plan, which puts it in the union and satisfies the check honestly.)
|
|
1176
|
+
|
|
1027
1177
|
On exit 1 (`agent fileScope not ⊆ allowedPaths — missing: [...]`): re-union `allowedPaths` across ALL agents that will be in-flight — **including still-running siblings from this wave** — re-write `wave-scope.json`, then re-run the assertion before dispatching. `allowedPaths` MUST NEVER shrink while sibling agents of the same wave are still running. This applies to EVERY batch — including fix-pass and re-dispatch batches, the incident class that motivated #796 (a fix-pass batch rewrote the union for a single agent and denied a sibling's legitimate writes). The assertion runs uniformly, even for single-agent waves — cost is negligible and the invariant is the same.
|
|
1028
1178
|
4. Read `enforcement` from Session Config (default: `warn`). The `enforcement` field is REQUIRED in `wave-scope.json` — always write it explicitly. The hooks default to `warn` if the field is missing, which would silently degrade strict enforcement. If jq was confirmed missing in Pre-Execution Check step 4, set `enforcement` to `off` and include a comment in the progress update noting that enforcement is disabled.
|
|
1029
1179
|
5. For **Discovery** role waves, set `allowedPaths` to `[]` (empty array) — Discovery agents are read-only and must not modify files. Also add to each Discovery agent prompt: "You are READ-ONLY. Do NOT use Edit or Write tools."
|
|
1030
1180
|
> **Defense in depth:** The empty `allowedPaths` enforcement hook is the PRIMARY barrier (blocks Write/Edit at the tool level). The prompt instruction is a SECONDARY safeguard. If jq is unavailable (enforcement set to `off`), the prompt instruction becomes the ONLY barrier — log a warning in this case.
|
|
1181
|
+
> **Test-sibling expansion (#970) cannot reach here, twice over:** `Discovery` is not in `TEST_SIBLING_EXPANSION_ROLES`, and `expandTestSiblings([], …)` returns `[]` STRUCTURALLY — before any gate, so the empty case holds even for a caller that opts in explicitly. Discovery's deny-all is a contract with its own regression test (`tests/hooks/enforce-scope.test.mjs`, "enforces Discovery-wave deny-all semantics… (issue #256 NO-OP contract)") — nothing may re-populate it.
|
|
1031
1182
|
6. For **Quality** role waves, use two-phase scope enforcement:
|
|
1032
1183
|
- **Phase 1 (Simplification)**: Before dispatching simplification agents, set `allowedPaths` to the production files changed this session (`git diff --name-only $SESSION_START_REF..HEAD`, excluding test files). After simplification agents complete, **delete** `<state-dir>/wave-scope.json` before proceeding to Phase 2.
|
|
1184
|
+
Test-sibling expansion (#970) does not fire here, because `Quality` is not in `TEST_SIBLING_EXPANSION_ROLES` — passing `{ role: 'Quality' }` is enough and no extra opt-out is needed. This phase's scope is production files with tests explicitly excluded, under a "do NOT change functionality" brief — expanding would hand simplification agents write access to the suite, which is the "agent deletes a dead export, then edits the test to match" failure mode. The pre-dispatch `--expand-test-siblings` assertion is gated on the same role and self-announces its skip, so it cannot block this phase either.
|
|
1033
1185
|
- **Phase 2 (Test/Review)**: Before dispatching test and review agents, regenerate `<state-dir>/wave-scope.json` with `allowedPaths` restricted to test file patterns (`**/*.test.*`, `**/*.spec.*`, `**/__tests__/**`, plus test config files). Quality test/review agents must not modify production source code.
|
|
1186
|
+
Test-sibling expansion (#970) is **inert** here — the scope is already test patterns. It must never add production paths; the inverse expansion (test → production sibling) does not exist and must not be introduced.
|
|
1034
1187
|
|
|
1035
1188
|
**Phase transition sequence:**
|
|
1036
1189
|
1. Compute production file list: `git diff --name-only $SESSION_START_REF..HEAD | grep -v -E '\.(test|spec)\.' | grep -v '__tests__/'`
|
|
@@ -56,15 +56,15 @@ absent, skip silently — do not assume any host path.
|
|
|
56
56
|
- If `> 0`, surface the count and remind that the session-end auto-commit and
|
|
57
57
|
`/evolve` handle catch-up. Do **not** commit the vault yourself.
|
|
58
58
|
|
|
59
|
-
## 4. Top-3 priority
|
|
59
|
+
## 4. Top-3 priority::high backlog
|
|
60
60
|
|
|
61
61
|
Detect the host from `git remote -v` (as in check 2) and query, without `-R`/owner flags:
|
|
62
62
|
|
|
63
|
-
- GitLab: `glab issue list --label priority
|
|
64
|
-
- GitHub: `gh issue list --label priority
|
|
63
|
+
- GitLab: `glab issue list --label priority::high --state opened --per-page 3 --output json`
|
|
64
|
+
- GitHub: `gh issue list --label priority::high --state open --limit 3`
|
|
65
65
|
|
|
66
66
|
Surface the issue id + title — up to three lines. Skip silently when the query
|
|
67
|
-
returns zero results or the CLI errors. Surface any `priority
|
|
67
|
+
returns zero results or the CLI errors. Surface any `priority::high` issue that
|
|
68
68
|
exists, even a single one.
|
|
69
69
|
|
|
70
70
|
## 5. Idle path
|