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.
Files changed (187) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor/rules/030-wave-execution.mdc +17 -1
  5. package/CHANGELOG.md +105 -412
  6. package/README.md +12 -9
  7. package/SECURITY.md +190 -27
  8. package/agents/AGENTS.md +20 -3
  9. package/agents/code-implementer.md +6 -6
  10. package/agents/db-specialist.md +1 -1
  11. package/agents/qa-strategist.md +31 -6
  12. package/agents/schemas/qa-strategist.schema.json +27 -0
  13. package/agents/schemas/test-writer.schema.json +60 -2
  14. package/agents/security-reviewer.md +1 -1
  15. package/agents/session-reviewer.md +1 -1
  16. package/agents/test-writer.md +29 -10
  17. package/agents/ui-developer.md +1 -1
  18. package/commands/contract-version-bump.md +28 -0
  19. package/commands/portfolio.md +1 -1
  20. package/docs/USER-GUIDE.md +8 -3
  21. package/docs/ci-setup.md +121 -7
  22. package/docs/codex-setup.md +1 -1
  23. package/docs/components.md +6 -6
  24. package/docs/cursor-setup.md +22 -9
  25. package/docs/events-schema.md +5 -1
  26. package/docs/instruction-delivery.md +444 -0
  27. package/docs/rule-authoring.md +58 -9
  28. package/docs/session-config-reference.md +244 -9
  29. package/docs/session-config-template.md +39 -3
  30. package/hooks/_lib/guard-source-loader.mjs +467 -0
  31. package/hooks/_lib/lock-bootstrap.mjs +21 -0
  32. package/hooks/_lib/vcs-create-matcher.mjs +119 -0
  33. package/hooks/config-protection.mjs +0 -0
  34. package/hooks/enforce-commands.mjs +10 -2
  35. package/hooks/hooks-codex.json +1 -1
  36. package/hooks/hooks-cursor.json +11 -2
  37. package/hooks/hooks-pi.json +10 -0
  38. package/hooks/hooks.json +21 -1
  39. package/hooks/on-session-end.mjs +178 -18
  40. package/hooks/on-session-start.mjs +23 -0
  41. package/hooks/post-bash-write-verify.mjs +977 -0
  42. package/hooks/post-subagent-discovery-validator.mjs +256 -41
  43. package/hooks/pre-bash-destructive-guard.mjs +525 -160
  44. package/hooks/pre-bash-issue-budget.mjs +167 -0
  45. package/hooks/pre-bash-sessions-ledger-guard.mjs +627 -0
  46. package/hooks/pre-bash-templates-first.mjs +96 -63
  47. package/hooks/subagent-telemetry.mjs +527 -37
  48. package/package.json +5 -2
  49. package/pi/prompts/contract-version-bump.md +12 -0
  50. package/rules/README.md +32 -0
  51. package/scripts/archive-closed-prds.mjs +12 -22
  52. package/scripts/autopilot-multi.mjs +103 -20
  53. package/scripts/backfill-abandoned-sessions.mjs +160 -4
  54. package/scripts/check-doc-consistency.sh +17 -1
  55. package/scripts/eval-session.mjs +50 -9
  56. package/scripts/fleet-instruction-scan.mjs +141 -0
  57. package/scripts/lib/autopilot/mr-draft.mjs +31 -1
  58. package/scripts/lib/autopilot/worktree-pipeline.mjs +113 -5
  59. package/scripts/lib/backlog-scan.mjs +39 -6
  60. package/scripts/lib/blocked-commands-policy.mjs +340 -0
  61. package/scripts/lib/ci-status-banner.mjs +75 -12
  62. package/scripts/lib/claude-md-budget-lint.mjs +283 -34
  63. package/scripts/lib/command-blocker.mjs +1013 -58
  64. package/scripts/lib/config/config-protection.mjs +2 -1
  65. package/scripts/lib/config/drift-check.mjs +9 -1
  66. package/scripts/lib/config/gitlab-portfolio.mjs +1 -1
  67. package/scripts/lib/config/issue-budget.mjs +123 -0
  68. package/scripts/lib/config/reconcile.mjs +21 -0
  69. package/scripts/lib/config/section-extractor.mjs +121 -1
  70. package/scripts/lib/config-schema.mjs +23 -3
  71. package/scripts/lib/config.mjs +17 -0
  72. package/scripts/lib/convergence-monitor.mjs +49 -3
  73. package/scripts/lib/description-surface.mjs +535 -0
  74. package/scripts/lib/dispatcher/enumerate.mjs +26 -40
  75. package/scripts/lib/ecosystem-wizard/config-writer.mjs +26 -24
  76. package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +1 -1
  77. package/scripts/lib/eval/engine.mjs +47 -5
  78. package/scripts/lib/events.mjs +59 -7
  79. package/scripts/lib/gates/gate-full.mjs +15 -3
  80. package/scripts/lib/gates/gate-helpers.mjs +132 -6
  81. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +28 -8
  82. package/scripts/lib/gitlab-portfolio/aggregator.mjs +8 -2
  83. package/scripts/lib/gitlab-portfolio/cli.mjs +1 -1
  84. package/scripts/lib/handover-gate.mjs +7 -3
  85. package/scripts/lib/harness-audit/categories/category4.mjs +9 -3
  86. package/scripts/lib/instruction-budget-guard.mjs +402 -51
  87. package/scripts/lib/io.mjs +345 -10
  88. package/scripts/lib/issue-budget.mjs +269 -0
  89. package/scripts/lib/issue-close-strip-labels.mjs +39 -9
  90. package/scripts/lib/label-scope.mjs +47 -0
  91. package/scripts/lib/learnings/schema.mjs +43 -3
  92. package/scripts/lib/lock-reaper.mjs +1 -2
  93. package/scripts/lib/memory-proposals/schema.mjs +36 -1
  94. package/scripts/lib/peer-discovery.mjs +645 -0
  95. package/scripts/lib/pi-hook-bridge.mjs +146 -17
  96. package/scripts/lib/product-repo-detect.mjs +9 -8
  97. package/scripts/lib/project-hygiene.mjs +432 -0
  98. package/scripts/lib/quality-gate.mjs +167 -0
  99. package/scripts/lib/recommendations-v0.mjs +1 -1
  100. package/scripts/lib/reconcile/eligibility.mjs +1 -1
  101. package/scripts/lib/reconcile/emitter.mjs +23 -4
  102. package/scripts/lib/reconcile/engine.mjs +147 -39
  103. package/scripts/lib/reconcile/idempotency.mjs +114 -14
  104. package/scripts/lib/reconcile-nudge-banner.mjs +65 -9
  105. package/scripts/lib/resource-probe/evaluate.mjs +70 -4
  106. package/scripts/lib/resource-probe.mjs +19 -0
  107. package/scripts/lib/rule-loader.mjs +6 -0
  108. package/scripts/lib/scope-baseline.mjs +564 -0
  109. package/scripts/lib/scope-gate.mjs +399 -98
  110. package/scripts/lib/session-close-backfill.mjs +61 -6
  111. package/scripts/lib/session-end/phase-skip.mjs +1 -0
  112. package/scripts/lib/session-id.mjs +221 -41
  113. package/scripts/lib/session-lock.mjs +304 -6
  114. package/scripts/lib/session-schema/constants.mjs +22 -3
  115. package/scripts/lib/session-schema/validator.mjs +16 -0
  116. package/scripts/lib/sessions-integrity-banner.mjs +294 -0
  117. package/scripts/lib/sessions-staleness-banner.mjs +121 -12
  118. package/scripts/lib/skill-evolution/idempotency.mjs +135 -16
  119. package/scripts/lib/skill-evolution/mr-opener.mjs +9 -1
  120. package/scripts/lib/spiral-carryover.mjs +142 -30
  121. package/scripts/lib/state-md/mission-status.mjs +53 -3
  122. package/scripts/lib/subagents-schema.mjs +43 -9
  123. package/scripts/lib/test-runner/issue-reconcile.mjs +53 -13
  124. package/scripts/lib/tests-src-ratio.mjs +484 -0
  125. package/scripts/lib/validate/check-agents.mjs +56 -0
  126. package/scripts/lib/validate/check-hooks-symmetry.mjs +244 -10
  127. package/scripts/lib/validate/check-rules.mjs +217 -35
  128. package/scripts/lib/validate/check-test-value-bans.mjs +782 -0
  129. package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
  130. package/scripts/lib/validate-vendored-rules.mjs +10 -2
  131. package/scripts/lib/vault-archive.mjs +17 -2
  132. package/scripts/lib/vault-backfill/glab.mjs +8 -0
  133. package/scripts/lib/vault-mirror/process.mjs +30 -0
  134. package/scripts/lib/vault-mirror/render-sessions.mjs +293 -36
  135. package/scripts/lib/vcs-repo-spec.mjs +362 -0
  136. package/scripts/lib/wave-resource-gate.mjs +115 -11
  137. package/scripts/lib/worktree/listing.mjs +44 -7
  138. package/scripts/mcp-server.sh +17 -3
  139. package/scripts/measure-context-overhead.sh +151 -0
  140. package/scripts/memory-propose.mjs +72 -9
  141. package/scripts/print-applicable-rules.mjs +51 -12
  142. package/scripts/release.mjs +534 -0
  143. package/scripts/run-quality-gate.mjs +123 -5
  144. package/scripts/validate-wave-scope.mjs +182 -17
  145. package/scripts/vault-integration-watcher.mjs +32 -10
  146. package/skills/_shared/config-reading.md +2 -2
  147. package/skills/bootstrap/fast-template.md +1 -1
  148. package/skills/claude-md-drift-check/checker.mjs +145 -28
  149. package/skills/contract-version-bump/SKILL.md +219 -0
  150. package/skills/discovery/SKILL.md +4 -4
  151. package/skills/discovery/issue-templates.md +11 -11
  152. package/skills/discovery/probes-audit.md +1 -1
  153. package/skills/discovery/probes-feature.md +1 -1
  154. package/skills/discovery/probes-session.md +26 -5
  155. package/skills/ecosystem-health/SKILL.md +1 -1
  156. package/skills/ecosystem-health/wizard.md +4 -4
  157. package/skills/evolve/SKILL.md +1 -0
  158. package/skills/gitlab-ops/SKILL.md +20 -12
  159. package/skills/gitlab-portfolio/SKILL.md +2 -2
  160. package/skills/hook-development/SKILL.md +1 -1
  161. package/skills/mode-selector/SKILL.md +1 -1
  162. package/skills/npm-publish/SKILL.md +17 -1
  163. package/skills/plan/SKILL.md +5 -5
  164. package/skills/plan/mode-feature.md +4 -4
  165. package/skills/plan/mode-new.md +10 -10
  166. package/skills/plan/mode-retro.md +1 -1
  167. package/skills/quality-gates/SKILL.md +1 -1
  168. package/skills/reconcile/SKILL.md +21 -4
  169. package/skills/session-end/SKILL.md +34 -13
  170. package/skills/session-end/discovery-scan.md +4 -2
  171. package/skills/session-end/drift-operations.md +4 -4
  172. package/skills/session-end/metrics-collection.md +13 -0
  173. package/skills/session-end/phase-3-2-docs-verification.md +1 -1
  174. package/skills/session-end/phase-3-6-tail.md +2 -1
  175. package/skills/session-end/plan-verification.md +5 -2
  176. package/skills/session-end/vault-operations.md +1 -1
  177. package/skills/session-end/verification-checklist.md +1 -1
  178. package/skills/session-plan/SKILL.md +6 -2
  179. package/skills/session-plan/wave-template.md +2 -0
  180. package/skills/session-start/SKILL.md +73 -7
  181. package/skills/session-start/phase-4-5-resource-health.md +15 -2
  182. package/skills/test-runner/SKILL.md +2 -2
  183. package/skills/vault-sync/validator.mjs +108 -7
  184. package/skills/wave-executor/SKILL.md +5 -2
  185. package/skills/wave-executor/circuit-breaker.md +2 -0
  186. package/skills/wave-executor/wave-loop.md +163 -10
  187. package/templates/_shared/loop.md +4 -4
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Session Orchestrator
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
- [![Version](https://img.shields.io/badge/version-3.17.0-blue.svg)](CHANGELOG.md)
4
+ [![Version](https://img.shields.io/badge/version-3.19.0-blue.svg)](CHANGELOG.md)
5
5
  [![npm](https://img.shields.io/npm/v/session-orchestrator.svg)](https://www.npmjs.com/package/session-orchestrator)
6
- [![Tests](https://img.shields.io/badge/tests-10%2C000%2B-brightgreen.svg)](docs/telemetry/telemetry-claims.md)
6
+ [![Tests](https://img.shields.io/badge/tests-12%2C000%2B-brightgreen.svg)](docs/telemetry/telemetry-claims.md)
7
7
 
8
8
  Loop engineering for AI coding agents — turn ad-hoc sessions into a repeatable research → plan → wave-execute → close loop with verification gates. Runs on **Claude Code, Codex CLI, Cursor, and [Pi](docs/pi-setup.md)**.
9
9
 
@@ -127,16 +127,17 @@ The system is markdown-driven config plus a thin Node runtime — skills, comman
127
127
  - **Cross-session learning is opt-in and inspectable.** Every session writes a record; after 5+ sessions `/evolve analyze` extracts confidence-scored patterns you can read and prune. Nothing is hidden.
128
128
  - **VCS dual support, no lock-in.** Auto-detects GitLab or GitHub from your remote and drives the full lifecycle for both.
129
129
 
130
- ## Recent highlights (v3.17.0)
130
+ ## Recent highlights (v3.19.0)
131
131
 
132
- Every release is additive and backward-compatible. Highlights of the v3.17.0 line:
132
+ Every release is additive and backward-compatible. Highlights of the v3.19.0 line:
133
133
 
134
- - **Opt-in anonymous usage telemetry (#841)** consent-gated client (`scripts/lib/telemetry/`) with anonymous IDs, a local queue, and record_kind-generic ingest. Off by default; nothing leaves the machine without explicit opt-in.
135
- - **Vault-curation probes (#831 B2/B4/B5)** — MOC-staleness banner, context-coverage line, and a candidates-only worktree-orphan sweep (never deletes PSA-003), plus the completed abandoned-session sweep.
136
- - **Review-panel & gates hardening** — base-branch injection, fail-open quality gates, torn-write `jq`, vault-sync register/mode vocabulary, and enumerate scan depth fixed in one pass.
137
- - **npm distribution line complete** — first publish shipped (v3.16.0 on npm, #825); this line adds the `npm-publish` token-runbook skill and broadened registry metadata.
134
+ - **Destructive-command guard, hardened in depth** wrapper unwrapping (`sudo`/`doas`/`env`/`nohup`/`timeout`/`nice`/`stdbuf`, with depth-capped `-c` payload recursion) closes six measured bypasses (#982); the blocked-commands policy becomes a floor ∪ overlay model that can only escalate, never weaken (#972); shell redirects gain a target denylist an `&>` redirect into a protected instruction file was a silent allow-with-truncation before (#983); and the denylist now resolves absolute paths, not just repo-relative ones.
135
+ - **Session identity with proof** — the session lock persists an owner proof at genesis, so an ending session can no longer release a living foreign lock, and the abandoned-session backfiller no longer deletes the very alarm it exists to raise (#987, #926, #914).
136
+ - **Supply chain** — two high-severity transitive vulnerabilities that `npm audit fix` could not reach are closed via package overrides.
137
+ - **Release as one dispatch** — `scripts/release.mjs` (#978, local half): a single surfaces table drives version rewrite and preflight (12 version literals across 10 files, CHANGELOG gate, tag/registry collision, drift sweep over all tracked files, CI-green-on-HEAD, leakage gate), and the git tag is created only AFTER a verified npm publish. Found two real gaps on its first run: v3.18.0 had been tagged but never published to npm, and the hidden `.codex-plugin` manifest was invisible to a plain ripgrep census.
138
+ - **session-orchestrator.com redesigned** — terminal hero, wave pipeline, leaderboard shell.
138
139
 
139
- Previous line (v3.16.0): class-wide bold-key parser fix (`matchBlockHeader`), vault-namespace de-collapse (`VAULT_CLEAR_SLUGS`), vault-board TTL self-healing, provenance-honest `/bootstrap --refresh-lock`.
140
+ Previous line (v3.18.0): panel-follow-ups and consolidation — bash-write-verify self-silencing vectors closed (#938), credential stripping at the source (#907), instruction-corpus diet 471→~440 directives, mutation-sweep-backed test consolidation.
140
141
 
141
142
  Full version history: [CHANGELOG.md](CHANGELOG.md).
142
143
 
@@ -196,6 +197,8 @@ npm run typecheck # node --check on every .mjs file
196
197
 
197
198
  `.npmrc` ships with `ignore-scripts=true` (supply-chain defence), so Husky git hooks don't auto-wire on install — run `npx husky` once after cloning. `git commit` then runs gitleaks → owner-privacy scan → lint-staged → commitlint. CI re-runs everything, plus more.
198
199
 
200
+ Two directories share the name *rules* and play opposite roles: [`rules/`](rules/README.md) is the **deliverable rule library** shipped *out* to consumer repos via `/bootstrap --sync-rules`, while [`.claude/rules/`](.claude/rules/) is this repo's own always-on rule set.
201
+
199
202
  Contributor docs: [Plugin Architecture (v3)](docs/plugin-architecture-v3.md) · [CONTRIBUTING.md](CONTRIBUTING.md) · [agent authoring spec](agents/AGENTS.md).
200
203
 
201
204
  ## Support & scope
package/SECURITY.md CHANGED
@@ -4,8 +4,11 @@
4
4
 
5
5
  | Version | Supported |
6
6
  |---------|-----------|
7
- | 2.0.x-alpha | Yes |
8
- | 1.x | No |
7
+ | 3.x | Yes |
8
+ | 2.x and earlier | No |
9
+
10
+ Only the latest 3.x minor receives fixes. See [CHANGELOG.md](CHANGELOG.md) for the
11
+ current release.
9
12
 
10
13
  ## Reporting a Vulnerability
11
14
 
@@ -23,15 +26,41 @@ Please include:
23
26
 
24
27
  ## Scope
25
28
 
26
- Session Orchestrator is a **Claude Code plugin** composed entirely of Markdown instructions and shell scripts. It does not:
27
- - Run a web server or accept network connections
28
- - Store credentials or secrets
29
+ Session Orchestrator is a plugin for AI coding agents (Claude Code, Codex CLI,
30
+ Cursor, Pi), composed of Markdown instructions plus a Node ESM runtime under
31
+ `scripts/` and `hooks/`. It requires Node 24+.
32
+
33
+ What it **does** touch:
34
+
35
+ - **Local filesystem** — reads/writes repo files, `.orchestrator/` runtime state,
36
+ and (when vault integration is enabled) a host-local vault directory under `$HOME`.
37
+ - **Your VCS CLI** — spawns `glab` / `gh` using your existing authentication.
38
+ - **Credentials via environment** — reads `GITLAB_TOKEN` (baseline fetch),
39
+ `CLANK_EVENT_SECRET` (event-bus bearer token), and `NPM_TOKEN` from a gitignored
40
+ `.env.local` (publish flow only). It never persists them; `CLANK_EVENT_SECRET` is
41
+ transmitted as a bearer token when the event bus is configured. See
42
+ [.env.example](.env.example).
43
+ - **Outbound network, all opt-in and off by default** — event-bus webhook POSTs
44
+ (only when `CLANK_EVENT_URL` *and* `CLANK_EVENT_SECRET` are set), GitLab API
45
+ baseline fetches (only with `GITLAB_TOKEN`), and anonymous usage telemetry
46
+ (strictly opt-in, one-time consent, whitelist-projected — see
47
+ [docs/telemetry.md](docs/telemetry.md)).
48
+ - **An optional self-hosted ingest server** — `server/ingest/` is a small HTTP
49
+ service (with `Dockerfile`, rate limiting, SQLite persistence, retention) that an
50
+ operator may deploy to *receive* telemetry. It is not run by the plugin and is not
51
+ required to use it.
52
+
53
+ What it does **not** do:
29
54
  - Execute user-provided code directly
55
+ - Store or forward credentials to any third party
56
+ - Send anything over the network without an explicit opt-in
30
57
 
31
58
  Security concerns are most likely to involve:
32
- - **Hook scripts** (`hooks/enforce-scope.sh`, `hooks/enforce-commands.sh`) — command injection via crafted file paths or tool input
59
+ - **Hook scripts** (`hooks/*.mjs`) — command injection via crafted file paths or tool input
33
60
  - **Skill instructions** — prompt injection that could bypass scope enforcement or safety constraints
34
61
  - **Agent dispatch** — unintended tool access or scope escalation during wave execution
62
+ - **Session Config** — the `*-command` keys are executed via a shell; see
63
+ [Session Config Command Trust](#session-config-command-trust) below
35
64
 
36
65
  ## Disclosure
37
66
 
@@ -39,52 +68,186 @@ We follow [responsible disclosure](https://en.wikipedia.org/wiki/Responsible_dis
39
68
 
40
69
  ## Enforcement Architecture
41
70
 
42
- Session Orchestrator uses a two-layer PreToolUse hook system to constrain agent behavior during wave execution.
71
+ Enforcement is a set of `PreToolUse` / `PreToolUse`-adjacent hooks registered via
72
+ `hooks/hooks.json` (with per-platform variants for Codex, Cursor, and Pi). All are
73
+ Node ESM; the former Bash implementations were removed in the v3.0.0 native
74
+ migration (#137/#138).
75
+
76
+ ### Wave-scoped enforcement
43
77
 
44
- ### File scope enforcement (`enforce-scope.sh`)
78
+ Both wave guards read a `wave-scope.json` that the wave-executor writes before
79
+ dispatching each wave. Its location is **platform-resolved** by
80
+ `scripts/lib/scope-gate.mjs` — `.claude/`, `.codex/`, `.cursor/`, or `.pi/` under
81
+ the project root, depending on the host agent.
45
82
 
46
- Intercepts **Edit** and **Write** tool calls. Validates the target `file_path` against the `allowedPaths` array in `.claude/wave-scope.json`. Paths are matched via prefix (directory), glob (`*`, `**`), or exact literal. Symlinks are resolved with `realpath` before comparison to prevent symlink bypass. Files outside the project root are always flagged.
83
+ **File scope (`hooks/enforce-scope.mjs`)** intercepts `Edit`, `Write`, and
84
+ `MultiEdit`. It resolves the target through `realpath` (symlink-bypass defense) and
85
+ matches the result against `allowedPaths` — prefix, glob, or exact literal. An
86
+ explicit *absolute* allowlist entry is honoured before the project-root containment
87
+ check (#792), so a deliberate out-of-repo grant is reachable; every other
88
+ out-of-repo path is denied.
47
89
 
48
- ### Command enforcement (`enforce-commands.sh`)
90
+ **Command scope (`hooks/enforce-commands.mjs`)** intercepts `Bash` and matches the
91
+ command against `blockedCommands[]` using word-boundary matching (so `rm` does not
92
+ match `format`). When `blockedCommands` is empty or absent, a hardcoded fallback
93
+ list applies (`rm -rf`, `git push --force` / `-f`, `git reset --hard`,
94
+ `drop table`, `git checkout -- .`).
49
95
 
50
- Intercepts **Bash** tool calls. Checks the command string against the `blockedCommands` array in `wave-scope.json` using word-boundary matching (prevents partial matches like "rm" matching "format"). When `blockedCommands` is empty or absent, a hardcoded fallback safety list is enforced: `rm -rf`, `git push --force`, `git reset --hard`, `DROP TABLE`, `git checkout -- .`.
96
+ Both wave guards **fail closed**: any unhandled internal error emits a deny,
97
+ never a silent allow. (The session-level destructive-command guard makes the
98
+ opposite, documented choice — see
99
+ [Policy floor (blocked-commands)](#policy-floor-blocked-commands).)
51
100
 
52
101
  ### Enforcement levels
53
102
 
54
- Both hooks read the `enforcement` field from `wave-scope.json`:
103
+ Both wave guards read the `enforcement` field from `wave-scope.json`:
55
104
 
56
105
  | Level | Behavior | Exit code |
57
106
  |-------|----------|-----------|
58
- | `strict` | Deny the operation, return `permissionDecision: deny` | 2 |
107
+ | `strict` | Deny the operation via `hookSpecificOutput.permissionDecision: deny` on stdout | 0 |
59
108
  | `warn` | Allow the operation, emit stderr warning | 0 |
60
109
  | `off` | Skip all checks | 0 |
61
110
 
62
- **Default is `strict`** (fail-closed) when the `enforcement` field is missing from `wave-scope.json`.
111
+ A deny exits **0**, not 2 (#906). Per the Claude Code hook contract a hook signals
112
+ either by exit code alone or by exit 0 plus structured JSON — never both; under
113
+ `exit 2` stdout JSON is discarded, which previously swallowed every deny reason.
114
+ The emitter is `scripts/lib/io.mjs#emitDeny`; see its JSDoc for the full payload.
63
115
 
64
- ### Dynamic per-wave scoping
116
+ **Default is `strict`** (fail-closed) when the `enforcement` field is missing.
117
+
118
+ ### Policy floor (blocked-commands)
119
+
120
+ Since #972 the destructive-command guard (`pre-bash-destructive-guard.mjs`) no
121
+ longer reads a single policy file first-hit-wins — it merges two policy roles
122
+ via `scripts/lib/blocked-commands-policy.mjs` (`loadEffectivePolicy`):
123
+
124
+ | Role | Source | Semantics |
125
+ |---|---|---|
126
+ | **Floor** | `$CLAUDE_PLUGIN_ROOT/.orchestrator/policy/blocked-commands.json` | The plugin's own blocklist — immutable baseline. |
127
+ | **Overlay** | First existing of `<cwd>`, `$CLAUDE_PROJECT_DIR` (`.orchestrator/policy/blocked-commands.json`) | The consumer repo's policy — strictly additive. |
128
+
129
+ Roles are resolved purely by path — there is no marker field in the file.
130
+ Precedence: `cwd` → `CLAUDE_PROJECT_DIR` for the overlay; the plugin root
131
+ always supplies the floor. When both roles resolve to the same file (e.g.
132
+ inside the plugin repo itself), that one file *is* the policy.
65
133
 
66
- The wave-executor writes `.claude/wave-scope.json` before dispatching each wave. This means scope constraints change between waves:
134
+ **Merge contract** union by rule `id`, floor rules first, escalate-only:
67
135
 
68
- - **Discovery waves** use empty `allowedPaths` (deny-all writes) combined with explicit read-only agent instructions (dual enforcement — hook-level + prompt-level).
69
- - **Quality waves** use two-phase scope: production file patterns for simplification passes, then test-only patterns (`**/*.test.*`, `**/*.spec.*`) for test/review passes.
136
+ - An overlay-only `id` is appended (additive).
137
+ - On an `id` collision with a floor rule of severity `block`, the **whole floor
138
+ rule wins** — no field merge (an overlay-supplied `pattern` or
139
+ `path-allowlist` would be a bypass); the overlay definition is dropped with a
140
+ stderr warning.
141
+ - A floor rule of severity `warn` escalates monotonically:
142
+ `max(floor, overlay)` with `warn < block`; unknown overlay severity → floor
143
+ kept + warning.
144
+
145
+ **Failure semantics** — the authoritative contract for this hook. Unlike the
146
+ wave guards above, it is deliberately *not* fail-closed on internal error:
147
+
148
+ | Condition | Effective policy |
149
+ |---|---|
150
+ | Overlay malformed / missing `.rules` / empty `rules: []` | **Fail-to-floor**: floor alone + stderr warning (an empty overlay cannot disarm the floor) |
151
+ | Floor unresolvable (no plugin root, or file missing) | Overlay alone + warning |
152
+ | Both missing | Fail-open (guard skipped) + warning — unchanged pre-#972 behavior |
153
+ | Internal error in the hook | `exit 0` fail-open — a documented decision; the merge module's functions are total (never throw) precisely so policy input can never reach this path |
154
+
155
+ **Trust note:** whoever controls `CLAUDE_PLUGIN_ROOT` controls the floor. That
156
+ is harness trust — the same party already controls the hook code itself — not a
157
+ new attack surface.
158
+
159
+ ### Dynamic per-wave scoping
160
+
161
+ Scope constraints change between waves:
162
+
163
+ - **Discovery waves** use empty `allowedPaths` (deny-all writes) combined with
164
+ explicit read-only agent instructions (dual enforcement — hook-level + prompt-level).
165
+ - **Quality waves** use two-phase scope: production file patterns for simplification
166
+ passes, then test-only patterns (`**/*.test.*`, `**/*.spec.*`) for test/review passes.
167
+
168
+ ### Session-level guards (active outside waves)
169
+
170
+ | Hook | Protects against |
171
+ |---|---|
172
+ | `pre-bash-destructive-guard.mjs` | Destructive shell commands in the **main** session, per the effective `blocked-commands.json` policy — at least the 14 floor rules from the plugin's policy, plus any additive overlay rules from the repo's policy (`block` or `warn` severity, each citing its source rule; see [Policy floor (blocked-commands)](#policy-floor-blocked-commands)). Bypass requires `allow-destructive-ops: true` in Session Config. |
173
+ | `config-protection.mjs` | Edits that *loosen* a quality gate — lowered thresholds, added `eslint-disable`/`@ts-ignore`, rules flipped to `off`, widened `.gitleaks.toml` allowlists, relaxed tsconfig strictness. Warn-by-default, fail-open on internal error. |
174
+ | `pre-bash-staging-fence.mjs` + `wave-scope-commit-guard.mjs` | Concurrent `git add` races between parallel wave-agents, and lint-staged sweeps that re-stage files outside wave scope. Rejects the commit rather than the edit. |
175
+
176
+ The behavioural rules these guards mechanise are documented in
177
+ [`.claude/rules/parallel-sessions.md`](.claude/rules/parallel-sessions.md) (PSA-003,
178
+ PSA-004, PSA-007).
179
+
180
+ ## Supply Chain
181
+
182
+ - `.npmrc` sets `ignore-scripts=true` — no dependency may run install/postinstall
183
+ scripts. This is the primary defense against postinstall-style attacks (SEC-020).
184
+ - CI enforces `npm audit --omit=dev --audit-level=high`.
185
+ - **gitleaks** (37 rules) and an **owner-leakage scanner**
186
+ (`scripts/lib/validate/check-owner-leakage.mjs`) both run in CI and as `.husky/`
187
+ pre-commit stages, so a secret or private-path leak is normally blocked before it
188
+ reaches a public branch. Note the asymmetry: the owner-leakage stage is
189
+ unconditional, whereas the local gitleaks stage is **skipped silently when gitleaks
190
+ is not installed** — CI remains the backstop for that one, and `--no-verify`
191
+ bypasses both.
192
+ - This repo is **npm-canonical**: `package-lock.json` is the committed lockfile and
193
+ `scripts/check-package-manager.mjs` guards against a foreign lockfile appearing.
194
+
195
+ ## Session Config Command Trust
196
+
197
+ The Session Config keys `test-command`, `typecheck-command`, `lint-command`, and
198
+ `custom-phases[].command` are executed via a shell. A malicious commit to
199
+ `CLAUDE.md` could therefore inject commands — which is **RCE-equivalent within the
200
+ repo's existing trust model**, not a new attack surface: anyone able to commit can
201
+ already achieve the same through `package.json` scripts, `.husky/` hooks, or a test
202
+ file.
203
+
204
+ The mitigation is review, not sandboxing: treat Session Config like code. Any diff
205
+ touching a command-bearing key must show before/after. `custom-phases` additionally
206
+ rejects shell metacharacters as defense-in-depth. Full trust model in
207
+ [`.claude/rules/security.md`](.claude/rules/security.md).
70
208
 
71
209
  ## Prerequisites
72
210
 
73
- - **`jq` is required** for hook enforcement. If `jq` is not installed, both enforcement hooks silently allow all operations (graceful degradation with stderr warning). The wave-executor checks for `jq` before wave dispatch and warns the user.
74
- - Hooks run in the Claude Code harness environment as PreToolUse interceptors, not as standalone scripts.
211
+ - **Node.js 24+.** v3.x is Node ESM throughout; there is no Bash runtime dependency
212
+ for the hooks (`jq` is used only by a few `scripts/` validators, never by
213
+ `hooks/*.mjs`).
214
+ - Hooks run inside the host agent's harness as tool-use interceptors, not as
215
+ standalone scripts.
75
216
 
76
217
  ## Known Limitations
77
218
 
78
- 1. **No enforcement outside active waves** — when no `wave-scope.json` exists (between sessions, before the first wave, after cleanup), hooks exit 0 and allow all operations.
219
+ 1. **No wave enforcement outside active waves** — when no `wave-scope.json` exists
220
+ (between sessions, before the first wave, after cleanup), the two wave guards exit 0.
221
+ The session-level guards in the table above remain active.
222
+
223
+ 2. **Prompt injection via VCS content** — issue titles and descriptions fetched via
224
+ `glab`/`gh` are consumed in agent prompts without sanitization. A malicious issue
225
+ body could inject instructions into an agent's context. Mitigated by: issues are
226
+ typically user-created in controlled workflows, and wave scope enforcement limits
227
+ the blast radius of any injected instructions.
79
228
 
80
- 2. **Prompt injection via VCS content** — issue titles and descriptions fetched via `glab`/`gh` are consumed in agent prompts without sanitization. A malicious issue body could inject instructions into an agent's context. Mitigated by: issues are typically user-created in controlled workflows, and wave scope enforcement limits the blast radius of any injected instructions.
229
+ 3. **Plugin relies on the host harness** — tool-level enforcement is provided by the
230
+ agent's hook system. The plugin cannot enforce restrictions if hooks are bypassed
231
+ at the harness level (`disableAllHooks`, `--no-verify`, etc.).
81
232
 
82
- 3. **Plugin relies on Claude Code harness** — tool-level enforcement is provided by Claude Code's PreToolUse hook system. The plugin cannot enforce restrictions if hooks are bypassed at the harness level.
233
+ 4. **Session Config values are only partially validated** — `health-endpoints`,
234
+ `plan-baseline-path`, and `cross-repos` accept arbitrary strings. Do not embed
235
+ credentials in these fields (see Credential Safety below).
83
236
 
84
- 4. **Session Config values are not validated** — `health-endpoints`, `plan-baseline-path`, and `cross-repos` accept arbitrary string values. Do not embed credentials in these fields (see Credential Safety below).
237
+ 5. **Guards are heuristics, not proofs** — `config-protection.mjs` is a
238
+ low-false-positive line/regex heuristic, not an exhaustive AST gate, and fails
239
+ open by design so a guard bug never blocks legitimate work.
85
240
 
86
241
  ## Credential Safety
87
242
 
88
- - This plugin does not store, process, or transmit credentials.
89
- - VCS CLI tools (`glab`, `gh`) use the user's existing authentication. Credential security for these tools is the user's responsibility.
90
- - **Do not embed API keys, passwords, or auth tokens** in Session Config fields (especially `health-endpoints` URLs). These values are stored in `CLAUDE.md` which may be committed to version control.
243
+ - Secrets are read from the environment or a gitignored `.env.local`, never written
244
+ into repo files by the plugin.
245
+ - VCS CLI tools (`glab`, `gh`) use your existing authentication; their credential
246
+ security is your responsibility.
247
+ - **Do not embed API keys, passwords, or auth tokens** in Session Config fields
248
+ (especially `health-endpoints` URLs). Session Config lives in `CLAUDE.md`, which is
249
+ normally committed.
250
+ - **Do not paste tokens into permission allowlists** in `.claude/settings.json` or
251
+ `.claude/settings.local.json` (SEC-021). `settings.local.json` is conventionally
252
+ untracked, so the pre-commit leakage scanner — which enumerates tracked files —
253
+ structurally cannot see a token pasted there.
package/agents/AGENTS.md CHANGED
@@ -1,3 +1,9 @@
1
+ ---
2
+ name: agents-authoring-spec
3
+ description: NOT A DISPATCHABLE AGENT — never select this. It is the authoring specification that the agent definitions in this directory must follow, loaded as a nested instruction file. Claude Code's plugin loader registers every agents/*.md as an agent by directory convention, and the manifest's `agents` key is additive-only, so it cannot exclude a path. Without this frontmatter the file registered as an unnamed agent with FULL tool access; the minimal `tools` line below is what bounds that. If you need agent-authoring rules, read this file — do not dispatch it.
4
+ tools: Read
5
+ ---
6
+
1
7
  # `agents/` — Sub-Agent Authoring Conventions
2
8
 
3
9
  > Nested instruction file for the `agents/` subtree. Claude Code / Cursor IDE
@@ -9,7 +15,18 @@
9
15
  > This is **not** an agent definition — it is the authoring spec the agent
10
16
  > `*.md` definitions in this directory must follow. The plugin validator
11
17
  > (`scripts/lib/validate/check-agents.mjs`) excludes `AGENTS.md` / `CLAUDE.md`
12
- > from agent-frontmatter validation by name.
18
+ > from agent-frontmatter validation by name, and `measureDescriptionSurface`
19
+ > excludes them from its walked corpus (#878).
20
+ >
21
+ > **Claude Code's plugin loader makes no such exception.** It registers every
22
+ > `agents/*.md` as a dispatchable agent by directory convention, and the
23
+ > manifest's `agents` key is documented as *additive* ("in addition to those in
24
+ > the `agents/` directory"), so it cannot exclude a path. With no frontmatter
25
+ > this file therefore registered as an agent named `AGENTS` with **full tool
26
+ > access**. The frontmatter above is the containment: it names the file for what
27
+ > it is, states in the `description` that it must never be dispatched, and caps
28
+ > `tools` at `Read`. Do not remove it — and if you add another non-agent doc to
29
+ > this directory, give it the same treatment.
13
30
  >
14
31
  > Sibling spec: for `.claude/rules/*.md` frontmatter (conditional loading via
15
32
  > globs/mode/host-class/expiry, plus the never-always-on invariant for
@@ -63,8 +80,8 @@ tools: Read, Grep, Glob, Bash # comma-separated string OR JSON array (bo
63
80
  `model:` is not just a compatibility field — it is a cost/quality dial per agent. Pick deliberately, don't default to `sonnet` everywhere.
64
81
 
65
82
  - **`haiku`** — cheap, fast, advisory/judge roles where the task is narrow classification or a single yes/no verdict, not open-ended reasoning. Precedent: `dialectic-deriver`, `skill-applied-judge`. Pin `haiku` when the agent's whole job is "read a short input, emit a structured verdict."
66
- - **`sonnet`** — the default workhorse tier. Use for implementation, test-writing, and review agents that need real reasoning over a non-trivial diff or codebase slice (code-implementer, test-writer, security-reviewer, and most of the catalog).
67
- - **`inherit`** — let the coordinator/session's active model tier flow through. Default for read-only reviewer/analysis agents that benefit from running at whatever tier the operator picked for the session (e.g. an Opus session should give its reviewers Opus-quality judgment too), and for agents with no strong cost/quality reason to diverge from the session.
83
+ - **`sonnet`** — an explicit cost cap, NOT a default. Pin it only when an agent must stay on the workhorse tier even in an Opus session, and state the reason inline. A bare `sonnet` on a working agent silently overrides the operator's session model the failure mode that moved the implementation and review catalog to `inherit`.
84
+ - **`inherit`** — **the default for the working catalog.** Lets the coordinator/session's active model tier flow through, so the agent runs at whatever tier the operator picked (an Opus session gives its implementers AND its reviewers Opus-quality judgment). Use it for implementation, test-writing, and review agents alike `code-implementer`, `test-writer`, `security-reviewer`, `session-reviewer`, `db-specialist`, `ui-developer`, and the read-only analysis agents. Diverge only for a named cost/quality reason.
68
85
  - **`opus`** — reserve for agents whose task genuinely needs the strongest available reasoning (e.g. `ux-evaluator`) and where the cost is justified because the agent runs rarely (dispatched solo, not fanned out across a wave).
69
86
  - **Full model-ID pinning** (`claude-opus-4-7`, `claude-sonnet-5`, `claude-fable-5-20260101`, …) — only when the agent has a demonstrated dependency on a SPECIFIC model version, e.g. a reproducibility requirement that needs a dated snapshot, or a known behavioral regression on newer models for this agent's exact prompt. Pinning trades away automatic model-family upgrades — document the reason inline as a frontmatter comment when you do this.
70
87
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: code-implementer
3
3
  description: Use this agent for feature implementation, API development, refactoring, and general code changes. Handles backend logic, API routes, service layers, and cross-cutting concerns. <example>Context: Wave plan assigns a new API endpoint implementation. user: "Implement CRUD API for invoices" assistant: "I'll dispatch the code-implementer agent to build the invoice API endpoints." <commentary>Feature implementation with multiple files is the code-implementer's core strength.</commentary></example> <example>Context: Refactoring task in an implementation wave. user: "Extract shared validation logic into a utility module" assistant: "I'll use the code-implementer to extract and refactor the validation logic." <commentary>Cross-file refactoring requires systematic reading, extraction, and verification.</commentary></example>
4
- model: sonnet
4
+ model: inherit
5
5
  color: green
6
6
  tools: Read, Edit, Write, Glob, Grep, Bash, Skill(session-orchestrator:*)
7
7
  sandbox-tier: repo-write
@@ -24,17 +24,17 @@ You are a focused implementation agent. You write production code, refactor exis
24
24
  2. **Confirm scope**: The wave plan task definition is your contract. If the task is ambiguous (e.g., "add validation" without specifying where), pause and report rather than guess.
25
25
  3. **Match conventions**: Match existing style for naming (camelCase vs snake_case), error patterns (typed errors vs result objects), and module structure (default vs named exports).
26
26
  4. **Implement minimally**: Touch only files in the assigned file scope. Do not refactor adjacent code that "could be cleaner" — that is out of scope unless the task explicitly says so.
27
- 5. **Run a fast feedback loop**: After substantive edits, run the project's typecheck (`tsgo --noEmit`, `tsc --noEmit`, or the configured command) to catch type errors early. Do not run the full test suite — that is the Quality wave's responsibility.
27
+ 5. **Run a fast feedback loop**: After substantive edits, run the project's typecheck (`tsgo --noEmit`, `tsc --noEmit`, or the configured command) to catch type errors early. Do not run the full test suite as a routine loop — that is the Quality wave's responsibility. You MAY still run the typecheck/lint command or targeted tests to gather evidence for a `Status: done` claim (Verification gate below) — the bar is on the *routine full-suite run*, not on verifying your own scope.
28
28
  6. **Self-review the diff**: Before reporting completion, mentally walk the diff and verify each change serves the task. Delete dead branches, debug logging, and TODO stubs.
29
29
  7. **Report**: Output a structured summary (see Output Format).
30
- - **Bite-sized plan**: If a bite-sized executable plan path is provided in your prompt (`docs/plans/<feature>.md`, see `skills/write-executable-plan/SKILL.md`), follow its 5-step structure per Task (test-first confirm fail implement verify pass commit-stop).
30
+ - **Bite-sized plan**: If a bite-sized executable plan path is provided in your prompt (`docs/plans/<feature>.md`, see `skills/write-executable-plan/SKILL.md`), you own the **implement** and **verify-pass** steps of each Task's 5-step structure: write the production code, then run the Task's exact verification command. The **test-first** and **confirm-fail** steps belong to the test-writer and the **commit-stop** step to the coordinator — in this bite-sized structure the test is authored ahead of you, and you never run git-write operations (see Rules below), so do not attempt those three steps yourself. (Outside a bite-sized plan, a need-gated regression test for a bug you fix IS yours — see Rules.)
31
31
  - **Bugfix prerequisite**: For bugfix-classified tasks: reference an existing `.orchestrator/debug/<session>-<n>.md` Phase-1 artifact (per `skills/debug/SKILL.md` Iron Law). If no artifact exists, invoke `/debug` first.
32
32
 
33
33
  ## Rules
34
34
 
35
- - Do NOT write teststhat is the test-writer's job. Production code only.
36
- - Do NOT modify test files unless the task explicitly requires it.
37
- - Do NOT add documentation beyond inline comments where logic is non-obvious. README and CLAUDE.md are owned by docs-writer.
35
+ - Tests are **need-gated, not banned**: when you fix a bug, author exactly ONE test that names the bug and fails without your fix (the fake-regression proof the wave-executor's per-agent testing expectation requires — `skills/wave-executor/SKILL.md` § Agent Prompt Best Practices, point 5). Do NOT write speculative test volume, and do NOT own test consolidation or coverage sweeps those belong to the **test-writer**. (In a bite-sized plan's 5-step structure, the `test-first`/`confirm-fail` steps are the test-writer's — see Bite-sized plan above.)
36
+ - Do NOT modify test files beyond the one need-gated test your task's bug requires.
37
+ - Do NOT add standalone or narrative documentation (README, CLAUDE.md, guides), and never write docs for code that does not exist yet — those surfaces are owned by docs-writer. Inline code-surface docs ARE yours: comments where logic is non-obvious, plus JSDoc/TSDoc on public functions you author (per `.claude/rules/development.md` § Documentation).
38
38
  - Do NOT introduce new runtime dependencies without explicit instruction. If a new dependency seems necessary, pause and report rather than installing.
39
39
  - Do NOT run ANY git write operation (`git add`, `git commit`, `git stash`, `git mv`, `git rm`, `git push`, `git reset`) — the git index and stash are shared session resources (PSA-007); the coordinator handles ALL VCS operations.
40
40
  - Do NOT touch unrelated files in the same directory just because they share a folder.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: db-specialist
3
3
  description: Use this agent for database work — schema design, migrations, queries, indexes, and database functions. Handles SQL, ORMs, and database architecture decisions. <example>Context: New feature requires database schema changes. user: "Create the migration for the invoice tables with proper indexes" assistant: "I'll dispatch the db-specialist agent to design the schema and create the migration." <commentary>Schema design requires understanding normalization, indexing, and the existing data model.</commentary></example> <example>Context: Performance issue with database queries. user: "Optimize the slow invoice listing query" assistant: "I'll use the db-specialist to analyze and optimize the query with proper indexing." <commentary>Query optimization requires understanding execution plans, indexes, and data access patterns.</commentary></example>
4
- model: sonnet
4
+ model: inherit
5
5
  color: purple
6
6
  tools: Read, Edit, Write, Glob, Grep, Bash, Skill(session-orchestrator:*)
7
7
  sandbox-tier: repo-write
@@ -10,10 +10,14 @@ output-schema: schemas/qa-strategist.schema.json
10
10
 
11
11
  # QA Strategist Agent
12
12
 
13
- You are a senior QA engineer conducting a read-only test-coverage gap analysis between waves. You identify what is NOT tested boundary conditions, error paths, integration contracts, and silent failures. You do NOT write tests or fix code. You produce a prioritised gap report.
13
+ You are a senior QA engineer conducting a read-only test-suite analysis between waves. You identify **both** failure modes of a suite: what is NOT tested (boundary conditions, error paths, integration contracts, silent failures) **and what is OVER-tested** (redundant, tautological, prose-pinning, and framework-verifying tests). You do NOT write tests or fix code. You produce a prioritised report.
14
+
15
+ Under-testing and over-testing are symmetric defects, not one real problem and one nitpick. A suite of 400 tests where 120 assert nothing costs real CI minutes, blocks refactors it should permit, and manufactures false confidence — that is a genuine finding, and the correct recommendation is deletion or consolidation, never "add more tests". Report a suite that needs shrinking as clearly as one that needs growing.
14
16
 
15
17
  ## Core Responsibilities
16
18
 
19
+ ### A. Under-testing (gaps)
20
+
17
21
  1. **Happy-path-only suites**: Identify test files that only test the success path and lack any negative or edge-case coverage
18
22
  2. **Boundary conditions**: Flag missing tests for limit values (empty inputs, max-length strings, zero, negative numbers, null/undefined)
19
23
  3. **Error-path coverage**: Detect unhandled or silently-swallowed errors (catch blocks with no assertion, error callbacks never invoked in tests)
@@ -21,6 +25,13 @@ You are a senior QA engineer conducting a read-only test-coverage gap analysis b
21
25
  5. **Integration gaps**: Identify points where unit tests exist but no integration or contract test verifies the full call chain
22
26
  6. **Flaky-prone patterns**: Flag time-dependent tests, tests that rely on ordering, or tests with hardcoded dates/ports
23
27
 
28
+ ### B. Over-testing (redundancy)
29
+
30
+ 7. **Duplicate tests**: Two or more tests that exercise the same branch with equivalent inputs — deleting all but one loses no catch-power. Count in `redundancy_counts.duplicate`.
31
+ 8. **Worthless tests**: Tests that survive the falsification check trivially — they still pass when the function body is replaced with `throw new Error()`. Includes tautological computations (`expect(calcTax(p, r)).toBe(p * r)`), assert-nothing bodies, and overly-generous assertions (`toBeTruthy()` on an object). Count in `redundancy_counts.worthless`.
32
+ 9. **Framework-only / prose-pinning tests**: Tests that verify the language, the framework, or the presence of a string in a document rather than this repo's behaviour — `expect(typeof fn).toBe('function')`, property-assignment round-trips, "the README contains heading X", enum-case counts. Count in `redundancy_counts.framework_only`.
33
+ 10. **Test-to-source ratio**: Compute `test_to_src_ratio` = test LOC ÷ source LOC over the reviewed scope. It is a signal, not a verdict: a high ratio over logic-dense code is healthy; a high ratio driven by categories 7–9 is bloat. Always interpret it against those counts, never on its own.
34
+
24
35
  ## Workflow
25
36
 
26
37
  1. **Read changed source files** from the wave scope. Understand what each module does: what inputs it accepts, what errors it can throw or return, what external calls it makes.
@@ -43,6 +54,8 @@ You are a senior QA engineer conducting a read-only test-coverage gap analysis b
43
54
  - HIGH gaps: N
44
55
  - MEDIUM gaps: N
45
56
  - LOW gaps: N
57
+ - Redundant tests: N duplicate / N worthless / N framework-only
58
+ - Test-to-source ratio: N.N (test LOC ÷ source LOC over the reviewed scope)
46
59
 
47
60
  ## Coverage Gaps
48
61
 
@@ -53,15 +66,24 @@ You are a senior QA engineer conducting a read-only test-coverage gap analysis b
53
66
  - **Missing scenario**: Describe the specific input/state/sequence not covered
54
67
  - **Risk**: What breaks in production if this path is never exercised
55
68
 
69
+ ## Redundancy Findings
70
+
71
+ ### [HIGH|MEDIUM|LOW] <title>
72
+ - **Test file**: path/to/source.test.ts:line
73
+ - **Category**: duplicate | worthless | framework-only
74
+ - **Evidence**: Quote the assertion(s). For `worthless`, state the falsification result — "still passes when the body is replaced with `throw new Error()`".
75
+ - **Recommendation**: delete | merge into <test name> | parameterise <N> cases into one
76
+ - **Payoff**: What the suite gains — CI time, refactor freedom, removal of false confidence
77
+
56
78
  ## Well-covered areas
57
79
  <list source files or functions with adequate test coverage>
58
80
  ```
59
81
 
60
82
  ## Severity Calibration
61
83
 
62
- - **HIGH**: Untested error path that hides data corruption, auth bypass, or data loss; production silent failure
63
- - **MEDIUM**: Missing boundary test for a public API; mocked integration with no assertion
64
- - **LOW**: Missing a convenience edge case, cosmetic gap, or low-impact optional behaviour
84
+ - **HIGH**: Untested error path that hides data corruption, auth bypass, or data loss; production silent failure. On the redundancy side: a worthless test that is the ONLY test for a behaviour — it reads as covered but catches nothing, which is worse than a visible gap.
85
+ - **MEDIUM**: Missing boundary test for a public API; mocked integration with no assertion. On the redundancy side: a cluster of duplicates or framework-only tests large enough to slow CI or block a legitimate refactor.
86
+ - **LOW**: Missing a convenience edge case, cosmetic gap, or low-impact optional behaviour; one-off redundant test with negligible cost.
65
87
 
66
88
  ## Refusal Rule
67
89
 
@@ -76,18 +98,21 @@ After the human-readable gap report, append a fenced ```json block matching `age
76
98
  "verdict": "PROCEED|PROCEED_WITH_FOLLOWUPS|FIX_REQUIRED|BLOCKED",
77
99
  "report_path": ".orchestrator/audits/wave-reviewer-N-qa-strategist.md",
78
100
  "gap_counts": {"high": 0, "med": 0, "low": 0},
101
+ "redundancy_counts": {"duplicate": 0, "worthless": 0, "framework_only": 0},
102
+ "test_to_src_ratio": 1.4,
79
103
  "source_files_reviewed": 0,
80
104
  "test_files_reviewed": 0,
81
105
  "blockers": []
82
106
  }
83
107
  ```
84
108
 
85
- Required: `verdict` (enum PROCEED|PROCEED_WITH_FOLLOWUPS|FIX_REQUIRED|BLOCKED), `report_path`, `gap_counts`, `source_files_reviewed`, `test_files_reviewed`. Optional: `blockers`. The coordinator's `validateAgentOutput()` parses the LAST fenced ```json block; place it at the end of your response.
109
+ Required: `verdict` (enum PROCEED|PROCEED_WITH_FOLLOWUPS|FIX_REQUIRED|BLOCKED), `report_path`, `gap_counts`, `source_files_reviewed`, `test_files_reviewed`. Optional: `redundancy_counts`, `test_to_src_ratio`, `blockers`. Emit `redundancy_counts` and `test_to_src_ratio` on every run — omitting them reads as "no redundancy analysis performed", not as "zero redundancy found". The coordinator's `validateAgentOutput()` parses the LAST fenced ```json block; place it at the end of your response.
86
110
 
87
111
  Verdict variants (concrete examples per scenario):
88
- - Coverage strong, no gaps → `{"verdict": "PROCEED", "gap_counts": {"high": 0, "med": 0, "low": 0}}`
112
+ - Coverage strong, no gaps, no bloat → `{"verdict": "PROCEED", "gap_counts": {"high": 0, "med": 0, "low": 0}, "redundancy_counts": {"duplicate": 0, "worthless": 0, "framework_only": 0}}`
89
113
  - Coverage adequate, advisory gaps only → `{"verdict": "PROCEED_WITH_FOLLOWUPS", "gap_counts": {"high": 0, "med": 3, "low": 4}}`
90
114
  - HIGH-risk gap that would let real bug ship → `{"verdict": "FIX_REQUIRED", "gap_counts": {"high": 2, "med": 5, "low": 3}}`
115
+ - **Redundancy dominates — no gap, but the suite must SHRINK** → `{"verdict": "FIX_REQUIRED", "gap_counts": {"high": 0, "med": 1, "low": 2}, "redundancy_counts": {"duplicate": 14, "worthless": 9, "framework_only": 22}, "test_to_src_ratio": 3.8}`. The recommendation is consolidation, not writing: 9 worthless tests pass with the implementation deleted, 22 pin framework or prose behaviour, and 14 duplicate a sibling — the suite claims coverage it does not have. `FIX_REQUIRED` is correct here even with zero HIGH gaps, because the false confidence is itself the defect. Hand the test-writer a delete/merge list, never a "write more tests" instruction.
91
116
  - Strategy review cannot complete → `{"verdict": "BLOCKED", "blockers": ["sources missing"]}`
92
117
 
93
118
  ## Edge Cases
@@ -27,6 +27,33 @@
27
27
  "low": { "type": "integer", "minimum": 0 }
28
28
  }
29
29
  },
30
+ "redundancy_counts": {
31
+ "type": "object",
32
+ "additionalProperties": false,
33
+ "description": "Over-testing findings — the symmetric counterpart to `gap_counts`. A high count here calls for consolidation/deletion, never for writing more tests.",
34
+ "properties": {
35
+ "duplicate": {
36
+ "type": "integer",
37
+ "minimum": 0,
38
+ "description": "Tests exercising the same branch with equivalent inputs; all but one can be deleted without losing catch-power."
39
+ },
40
+ "worthless": {
41
+ "type": "integer",
42
+ "minimum": 0,
43
+ "description": "Tests that still pass when the implementation body is replaced with `throw new Error()` — tautological computations, assert-nothing bodies, overly-generous assertions."
44
+ },
45
+ "framework_only": {
46
+ "type": "integer",
47
+ "minimum": 0,
48
+ "description": "Tests verifying the language, the framework, or the presence of a string in a document rather than this repo's behaviour."
49
+ }
50
+ }
51
+ },
52
+ "test_to_src_ratio": {
53
+ "type": "number",
54
+ "minimum": 0,
55
+ "description": "Test LOC divided by source LOC over the reviewed scope. A signal, not a verdict — interpret only alongside `redundancy_counts`."
56
+ },
30
57
  "source_files_reviewed": {
31
58
  "type": "integer",
32
59
  "minimum": 0,