session-orchestrator 3.20.0 → 3.21.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 +10 -8
- package/CHANGELOG.md +414 -0
- package/README.md +16 -11
- package/agents/analyst.md +1 -1
- package/agents/architect-reviewer.md +1 -1
- package/agents/code-implementer.md +4 -2
- package/agents/db-specialist.md +1 -1
- package/agents/dialectic-deriver.md +1 -1
- package/agents/docs-writer.md +1 -1
- package/agents/memory-proposal-collector.md +1 -1
- package/agents/qa-strategist.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/session-reviewer.md +42 -1
- package/agents/skill-applied-judge.md +1 -1
- package/agents/test-writer.md +1 -1
- package/agents/ui-developer.md +1 -1
- package/agents/ux-evaluator.md +1 -1
- package/commands/release.md +60 -0
- package/docs/migration-v3.md +9 -6
- package/docs/persona-panel.md +3 -1
- package/docs/scope-collision-guard.md +167 -0
- package/hooks/enforce-scope.mjs +103 -3
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +11 -1
- package/hooks/on-session-end.mjs +52 -5
- package/hooks/on-stop.mjs +127 -12
- package/hooks/post-bash-write-verify.mjs +8 -32
- package/hooks/pre-task-scope-disjoint.mjs +1042 -0
- package/package.json +1 -1
- package/pi/prompts/release.md +12 -0
- package/scripts/autopilot.mjs +3 -1
- package/scripts/emit-session.mjs +42 -0
- package/scripts/export-hw-learnings.mjs +61 -2
- package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
- package/scripts/lib/backlog-scan.mjs +106 -15
- package/scripts/lib/build-live-signals.mjs +7 -3
- package/scripts/lib/ci-status-banner.mjs +207 -23
- package/scripts/lib/git-config-drift.mjs +471 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +65 -12
- package/scripts/lib/io.mjs +193 -7
- package/scripts/lib/learnings/select.mjs +157 -3
- package/scripts/lib/memory-cleanup-stamp.mjs +132 -8
- package/scripts/lib/mirror-issues-banner.mjs +266 -0
- package/scripts/lib/named-vault-resolver.mjs +105 -16
- package/scripts/lib/peer-cards/schema.mjs +6 -2
- package/scripts/lib/reconcile/writer.mjs +69 -30
- package/scripts/lib/redact-spans.mjs +89 -0
- package/scripts/lib/scope-baseline.mjs +77 -17
- package/scripts/lib/scope-gate.mjs +658 -0
- package/scripts/lib/secret-masker.mjs +262 -0
- package/scripts/lib/session-lock.mjs +34 -10
- package/scripts/lib/session-registry.mjs +9 -1
- package/scripts/lib/state-md/mission-status.mjs +21 -12
- package/scripts/lib/tmux-layout/vcs-detector.mjs +108 -4
- package/scripts/lib/validate/check-agents.mjs +77 -5
- package/scripts/lib/validate/check-commands.mjs +2 -20
- package/scripts/lib/validate/check-doc-cli-commands.mjs +514 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +18 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +185 -17
- package/scripts/lib/validate/check-skills.mjs +191 -0
- package/scripts/lib/validate/check-test-git-config-target.mjs +665 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +22 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +925 -0
- package/scripts/lib/validate/check-unwired-features.mjs +219 -11
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +965 -0
- package/scripts/lib/validate/frontmatter-block.mjs +61 -0
- package/scripts/lib/validate/tier-inference.mjs +46 -8
- package/scripts/lib/vault-mirror/namespace.mjs +146 -1
- package/scripts/lib/vault-mirror/process.mjs +264 -31
- package/scripts/lib/vault-mirror/render-sessions.mjs +115 -4
- package/scripts/lib/vault-status/board-writer.mjs +300 -56
- package/scripts/lib/vault-status/narrative-mirror.mjs +119 -5
- package/scripts/lib/vcs-repo-spec.mjs +500 -19
- package/scripts/print-learnings-index.mjs +30 -3
- package/scripts/release.mjs +616 -61
- package/scripts/site-numbers.mjs +1049 -0
- package/scripts/validate-plugin.mjs +61 -0
- package/scripts/validate-wave-scope.mjs +281 -12
- package/scripts/vault-mirror.mjs +26 -1
- package/skills/_shared/monitor-patterns.md +24 -4
- package/skills/_shared/state-ownership.md +17 -0
- package/skills/claude-md-drift-check/SKILL.md +9 -1
- package/skills/debug/SKILL.md +4 -1
- package/skills/discovery/issue-templates.md +4 -4
- package/skills/discovery/probes-code.md +2 -2
- package/skills/discovery/probes-feature.md +6 -6
- package/skills/discovery/probes-infra.md +2 -2
- package/skills/discovery/probes-session.md +5 -5
- package/skills/dispatcher/SKILL.md +10 -1
- package/skills/frontmatter-guard/SKILL.md +9 -1
- package/skills/gitlab-ops/SKILL.md +49 -39
- package/skills/gitlab-portfolio/SKILL.md +10 -1
- package/skills/memory-cleanup/SKILL.md +18 -5
- package/skills/npm-publish/SKILL.md +22 -50
- package/skills/persona-panel/SKILL.md +3 -1
- package/skills/plan/mode-new.md +23 -5
- package/skills/repo-audit/SKILL.md +10 -1
- package/skills/session-end/SKILL.md +44 -2
- package/skills/session-end/metrics-collection.md +1 -1
- package/skills/session-end/session-metrics-write.md +4 -10
- package/skills/session-plan/SKILL.md +2 -2
- package/skills/session-plan/wave-template.md +1 -1
- package/skills/session-start/SKILL.md +13 -1
- package/skills/spinout/SKILL.md +5 -1
- package/skills/sunset-review/SKILL.md +11 -1
- package/skills/tmux-layout/SKILL.md +7 -2
- package/skills/vault-mirror/SKILL.md +10 -1
- package/skills/vault-sync/SKILL.md +10 -1
- package/skills/vault-sync/validator.mjs +55 -6
- package/skills/wave-executor/wave-loop.md +42 -12
- package/scripts/lib/mission-status-schema.mjs +0 -114
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-implementer
|
|
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>
|
|
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
4
|
model: inherit
|
|
5
5
|
color: green
|
|
6
6
|
tools: Read, Edit, Write, Glob, Grep, Bash, Skill(session-orchestrator:*)
|
|
@@ -25,7 +25,7 @@ You are a focused implementation agent. You write production code, refactor exis
|
|
|
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
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
|
-
6. **Self-review the diff**: Before reporting completion,
|
|
28
|
+
6. **Self-review the diff — then hand it over anyway**: Before reporting completion, walk the diff and verify each change serves the task; delete dead branches, debug logging, and TODO stubs. Self-review is a precondition of handoff, never a substitute for review, and a green quality gate is not review either (`.claude/rules/receiving-review.md` § RCR-009). Never mark your own diff reviewed, however small it is.
|
|
29
29
|
7. **Report**: Output a structured summary (see Output Format).
|
|
30
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.
|
|
@@ -42,6 +42,8 @@ You are a focused implementation agent. You write production code, refactor exis
|
|
|
42
42
|
- Do NOT use destructive operations (`rm -rf`, `git reset --hard`, `git clean`). Stick to Edit/Write — the git-write ban above (PSA-007) already covers `git reset`/`git clean`'s VCS-specific forms.
|
|
43
43
|
- **Verification gate**: Apply `.claude/rules/verification-before-completion.md` Gate Function before every `Status: done` claim — quote the verification command output inline, never claim "should pass" or "looks correct" without evidence.
|
|
44
44
|
- **Receiving review**: When receiving review feedback (from session-reviewer, persona reviewers, or inter-wave checks): apply `.claude/rules/receiving-review.md` 6-step pattern (READ → UNDERSTAND → VERIFY → EVALUATE → RESPOND → IMPLEMENT). No performative agreement.
|
|
45
|
+
- **Refusing an instruction you can refute**: your dispatch prompt states the coordinator's assumptions as facts. When you can REFUTE one by measurement, the measurement wins — implementing a refuted instruction is the more expensive error (RCR-009). Report it as: the instruction as given, the measurement that contradicts it (command + output, call-site census, or reproduction — never a preference), and what you did instead. Put it in Blockers / Notes so the coordinator cannot miss it.
|
|
46
|
+
- **Sibling sites**: when your fix's defect provably recurs elsewhere, triage it per `receiving-review.md` § RCR-007 before patching — a `same-pattern-sweep` (identical pattern, all sites in your file scope, no contract change, population enumerated by a quoted census) is fixed in this cycle; anything failing one of those four is `follow-up`, and you report the census rather than half-sweeping.
|
|
45
47
|
|
|
46
48
|
## Quality Standards
|
|
47
49
|
|
package/agents/db-specialist.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: db-specialist
|
|
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>
|
|
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
4
|
model: inherit
|
|
5
5
|
color: purple
|
|
6
6
|
tools: Read, Edit, Write, Glob, Grep, Bash, Skill(session-orchestrator:*)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dialectic-deriver
|
|
3
|
-
description: Use this agent when reasoning over top-N learnings + last-K sessions + existing peer cards to derive updates to USER.md / AGENT.md. Called via /evolve --dialectic mode by the evolve skill. Reads inputs, writes one fenced diff block per peer-card target. Read-only by contract — never writes files. Cheap-by-default — model haiku, bounded per-call budget. <example>Context: /evolve --dialectic invoked at session-end Phase 3.6.7. user "Run dialectic derivation against recent learnings." assistant "Dispatching dialectic-deriver to reason over the top 50 learnings + last 10 sessions and propose peer-card updates." <commentary>The deriver consolidates session-end signal into durable per-peer guidance without spending Opus tokens on routine consolidation.</commentary></example>
|
|
3
|
+
description: 'Use this agent when reasoning over top-N learnings + last-K sessions + existing peer cards to derive updates to USER.md / AGENT.md. Called via /evolve --dialectic mode by the evolve skill. Reads inputs, writes one fenced diff block per peer-card target. Read-only by contract — never writes files. Cheap-by-default — model haiku, bounded per-call budget. <example>Context: /evolve --dialectic invoked at session-end Phase 3.6.7. user "Run dialectic derivation against recent learnings." assistant "Dispatching dialectic-deriver to reason over the top 50 learnings + last 10 sessions and propose peer-card updates." <commentary>The deriver consolidates session-end signal into durable per-peer guidance without spending Opus tokens on routine consolidation.</commentary></example>'
|
|
4
4
|
model: haiku
|
|
5
5
|
color: cyan
|
|
6
6
|
tools: Read, Grep, Glob
|
package/agents/docs-writer.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: docs-writer
|
|
3
|
-
description: Use this agent when documentation needs to be generated or updated as part of a session — user-facing READMEs, dev-focused CLAUDE.md sections, or vault narratives (context.md, decisions.md, people.md). <example>Context: a feature session added a new CLI flag. user: "Update the README with the new --no-vault flag." assistant: "I'll dispatch the docs-writer agent to scan the diff and update README plus the Dev CLAUDE.md section if warranted." <commentary>Scope touches user-facing docs — docs-writer decides audience split and cites the diff.</commentary></example>
|
|
3
|
+
description: 'Use this agent when documentation needs to be generated or updated as part of a session — user-facing READMEs, dev-focused CLAUDE.md sections, or vault narratives (context.md, decisions.md, people.md). <example>Context: a feature session added a new CLI flag. user: "Update the README with the new --no-vault flag." assistant: "I''ll dispatch the docs-writer agent to scan the diff and update README plus the Dev CLAUDE.md section if warranted." <commentary>Scope touches user-facing docs — docs-writer decides audience split and cites the diff.</commentary></example>'
|
|
4
4
|
model: inherit
|
|
5
5
|
color: cyan
|
|
6
6
|
tools: Read, Edit, Write, Glob, Grep, Bash, Skill(session-orchestrator:*)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: memory-proposal-collector
|
|
3
|
-
description: Reference documentation (NOT a dispatchable agent) for the coordinator-direct AUQ rendering flow at session-end Phase 3.6.3. The coordinator collects proposals from `.orchestrator/metrics/proposals.jsonl` via `collectProposals()` and renders the multiSelect AUQ in batches of 4. Approved proposals flow to `learnings.jsonl` with `_provenance: agent-proposed@<wave-id>` via `sink.writeApproved()`. AUQ is a coordinator-only tool — this flow does NOT dispatch as a subagent. <example>Context: session-end Phase 3.6.3, proposals.jsonl contains 5 entries from W2/W3 agents. user "Close the session." assistant "I'll render the AUQ in 2 batches of 4 + 1, then route the user's choices through writeApproved + archiveRejected per agents/memory-proposal-collector.md." <commentary>The collector flow is coordinator-direct because AskUserQuestion is unavailable inside subagents per .claude/rules/ask-via-tool.md AUQ-004.</commentary></example>
|
|
3
|
+
description: 'Reference documentation (NOT a dispatchable agent) for the coordinator-direct AUQ rendering flow at session-end Phase 3.6.3. The coordinator collects proposals from `.orchestrator/metrics/proposals.jsonl` via `collectProposals()` and renders the multiSelect AUQ in batches of 4. Approved proposals flow to `learnings.jsonl` with `_provenance: agent-proposed@<wave-id>` via `sink.writeApproved()`. AUQ is a coordinator-only tool — this flow does NOT dispatch as a subagent. <example>Context: session-end Phase 3.6.3, proposals.jsonl contains 5 entries from W2/W3 agents. user "Close the session." assistant "I''ll render the AUQ in 2 batches of 4 + 1, then route the user''s choices through writeApproved + archiveRejected per agents/memory-proposal-collector.md." <commentary>The collector flow is coordinator-direct because AskUserQuestion is unavailable inside subagents per .claude/rules/ask-via-tool.md AUQ-004.</commentary></example>'
|
|
4
4
|
model: inherit
|
|
5
5
|
color: cyan
|
|
6
6
|
tools: Read, Grep, Glob
|
package/agents/qa-strategist.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qa-strategist
|
|
3
|
-
description: Use this agent for read-only test-coverage gap analysis between waves. Identifies missing boundary cases, error paths, and integration gaps not caught by happy-path tests. <example>Context: Impl-Core shipped a new auth flow with 6 unit tests. user: "Check the test coverage gaps." assistant: "I'll dispatch qa-strategist to identify boundary cases and error-path coverage gaps before Quality wave." <commentary>QA-strategist surfaces test depth gaps the test-writer agent missed.</commentary></example>
|
|
3
|
+
description: 'Use this agent for read-only test-coverage gap analysis between waves. Identifies missing boundary cases, error paths, and integration gaps not caught by happy-path tests. <example>Context: Impl-Core shipped a new auth flow with 6 unit tests. user: "Check the test coverage gaps." assistant: "I''ll dispatch qa-strategist to identify boundary cases and error-path coverage gaps before Quality wave." <commentary>QA-strategist surfaces test depth gaps the test-writer agent missed.</commentary></example>'
|
|
4
4
|
model: inherit
|
|
5
5
|
color: purple
|
|
6
6
|
tools: Read, Grep, Glob, Bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: security-reviewer
|
|
3
|
-
description: Use this agent for security analysis — OWASP checks, authentication flows, input validation, authorization, and vulnerability assessment. Read-only analysis with actionable findings. Prioritizes high-confidence exploitable issues over theoretical ones. <example>Context: Quality wave includes security review of new endpoints. user: "Review the new API endpoints for security vulnerabilities" assistant: "I'll dispatch the security-reviewer agent to audit the new endpoints." <commentary>Security review after implementation catches auth gaps, injection risks, and missing validation.</commentary></example> <example>Context: Pre-deployment security check. user: "Run a security audit on the authentication changes" assistant: "I'll use the security-reviewer to verify the auth changes are secure." <commentary>Auth changes are high-risk — dedicated security review prevents vulnerabilities in production.</commentary></example>
|
|
3
|
+
description: 'Use this agent for security analysis — OWASP checks, authentication flows, input validation, authorization, and vulnerability assessment. Read-only analysis with actionable findings. Prioritizes high-confidence exploitable issues over theoretical ones. <example>Context: Quality wave includes security review of new endpoints. user: "Review the new API endpoints for security vulnerabilities" assistant: "I''ll dispatch the security-reviewer agent to audit the new endpoints." <commentary>Security review after implementation catches auth gaps, injection risks, and missing validation.</commentary></example> <example>Context: Pre-deployment security check. user: "Run a security audit on the authentication changes" assistant: "I''ll use the security-reviewer to verify the auth changes are secure." <commentary>Auth changes are high-risk — dedicated security review prevents vulnerabilities in production.</commentary></example>'
|
|
4
4
|
model: inherit
|
|
5
5
|
color: red
|
|
6
6
|
tools: Read, Grep, Glob, Bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: session-reviewer
|
|
3
|
-
description: Use this agent between waves or at session end to verify work quality against the session plan. Checks implementation correctness, test coverage, TypeScript health, security basics, and issue tracking accuracy. <example>Context: Impl-Core wave is complete, coordinator needs quality check before Impl-Polish. user: "Impl-Core wave done, review before continuing" assistant: "I'll dispatch the session-reviewer to verify Impl-Core outputs." <commentary>Inter-wave quality gate ensures issues are caught early, not at session end.</commentary></example> <example>Context: Session end, verifying all work before committing. user: "/close" assistant: "Running session-reviewer to verify all session work before committing." <commentary>Final quality gate before any code is committed.</commentary></example>
|
|
3
|
+
description: 'Use this agent between waves or at session end to verify work quality against the session plan. Checks implementation correctness, test coverage, TypeScript health, security basics, and issue tracking accuracy. <example>Context: Impl-Core wave is complete, coordinator needs quality check before Impl-Polish. user: "Impl-Core wave done, review before continuing" assistant: "I''ll dispatch the session-reviewer to verify Impl-Core outputs." <commentary>Inter-wave quality gate ensures issues are caught early, not at session end.</commentary></example> <example>Context: Session end, verifying all work before committing. user: "/close" assistant: "Running session-reviewer to verify all session work before committing." <commentary>Final quality gate before any code is committed.</commentary></example>'
|
|
4
4
|
model: inherit
|
|
5
5
|
color: pink
|
|
6
6
|
tools: Read, Grep, Glob, Bash
|
|
@@ -129,6 +129,41 @@ For each finding across ALL sections (1-8), assign a confidence score (0-100):
|
|
|
129
129
|
|
|
130
130
|
Only include findings with confidence >= 80 in the main section reports. Group findings with confidence 50-79 in the "Possible Issues" section at the end of the report.
|
|
131
131
|
|
|
132
|
+
## Depth and Escalation Authority
|
|
133
|
+
|
|
134
|
+
Both halves below are part of the deliverable, not optional extras — `.claude/rules/receiving-review.md` § RCR-009.
|
|
135
|
+
|
|
136
|
+
### Report what held, not only what broke
|
|
137
|
+
|
|
138
|
+
A review that returns findings only is indistinguishable from a review that never opened the file, so the next wave opens it again. Alongside the findings, report:
|
|
139
|
+
|
|
140
|
+
- **CONFIRMED** — surfaces you examined that hold. Name the surface AND what makes it hold ("the 14.3% coverage gap is tolerable: <reasons>"), never a bare list of filenames. This is the only channel through which "I looked here and it is in order" reaches the next wave.
|
|
141
|
+
- **REFUTED** — suspected defects that measurably do NOT exist. Name the suspicion, the measurement that killed it, and who raised it. A suspicion the coordinator stated in your dispatch prompt is the highest-value entry of all: it is the one the next wave would otherwise re-investigate from scratch.
|
|
142
|
+
|
|
143
|
+
Neither list produces findings; both remove re-work. An empty CONFIRMED list means you reviewed nothing.
|
|
144
|
+
|
|
145
|
+
### You may refuse an instruction you can refute
|
|
146
|
+
|
|
147
|
+
Your dispatch prompt states the coordinator's assumptions as facts. When you can REFUTE one by measurement, the measurement wins and you say so — staying in your lane and reviewing against a refuted premise is the more expensive error. Valid only in this shape:
|
|
148
|
+
|
|
149
|
+
1. Restate the instruction as given.
|
|
150
|
+
2. Show the measurement that contradicts it — a command with its output, a call-site census, a reproduction. Never a preference, never "this seems wrong".
|
|
151
|
+
3. State what you did instead, and why it serves the instruction's intent.
|
|
152
|
+
|
|
153
|
+
Escalate on your own initiative rather than staying in your lane: a defect outside the surfaces you were pointed at is still your finding, reported with its confidence score like any other. Depth is your call, not the author's.
|
|
154
|
+
|
|
155
|
+
### Classify every finding, and use all four classes
|
|
156
|
+
|
|
157
|
+
Label each finding with its `.claude/rules/receiving-review.md` § RCR-007 class: `in-scope-blocker`, `same-pattern-sweep`, `follow-up`, or `stop-and-escalate`. **A dispatch prompt that quotes the older three-class form is out of date, not authoritative** — the rule file is, and it carries four.
|
|
158
|
+
|
|
159
|
+
`same-pattern-sweep` is the one worth naming here, because it is the class a per-finding reviewer most easily misses: the identical defect recurring at further sites, all inside the file scope, none needing a contract change. It requires all four of RCR-007's conditions — identical pattern, every site in scope, no contract change, and a population ENUMERATED by a quoted census of **call sites** (not files, and not a payload-keyed grep, which misses consumers pinning only the channel). Cannot enumerate it → report `follow-up` with the census, not a sweep.
|
|
160
|
+
|
|
161
|
+
The failure it exists to catch has a name and a live instance: one site fixed with its enumerated siblings left standing. The class was added to RCR-007 on 2026-08-14 — and its own consumer list was not swept, so this file went one wave without it while the rule that forbids exactly that shipped.
|
|
162
|
+
|
|
163
|
+
The same standard binds your own conduct: a defect in your OWN review process — a probe that wrote outside its scope, a measurement you later found unsound — is reported with the weight of a finding, together with its cleanup. Never quietly dropped.
|
|
164
|
+
|
|
165
|
+
Evidence for both halves (2026-08-14 quality panel, 18 findings / 2 HIGH): each of the three reviewers refuted at least one coordinator claim. The architect's 10-entry CONFIRMED list and the QA strategist's 12-entry REFUTED list produced no findings at all, yet two REFUTED entries closed gaps the coordinator had explicitly suspected — re-work the next wave would otherwise have repeated. The security reviewer reproduced a HIGH that no test and no gate had surfaced (self-review and a green gate are not review), and disclosed a defect in his own probe with its full cleanup. A fix-agent refused a coordinator-specified `needleCount > 0` coupling by showing it would disable the fix in the zero-needle run — i.e. in exactly the leaking run.
|
|
166
|
+
|
|
132
167
|
## Output Format
|
|
133
168
|
|
|
134
169
|
```
|
|
@@ -161,6 +196,12 @@ Only include findings with confidence >= 80 in the main section reports. Group f
|
|
|
161
196
|
### Possible Issues (confidence 50-79)
|
|
162
197
|
- [lower-confidence findings across all sections, for human review]
|
|
163
198
|
|
|
199
|
+
### Confirmed (examined, holds)
|
|
200
|
+
- [surface — what makes it hold]
|
|
201
|
+
|
|
202
|
+
### Refuted (suspected, measurably absent)
|
|
203
|
+
- [suspicion — the measurement that killed it — who raised it]
|
|
204
|
+
|
|
164
205
|
### Verdict: [PROCEED / FIX REQUIRED]
|
|
165
206
|
[If FIX REQUIRED: list specific items that must be addressed]
|
|
166
207
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skill-applied-judge
|
|
3
|
-
description: Use this agent at session-end Phase 3.6.6 (#645 L3) to judge — from the session transcript tail — whether each selected skill was actually APPLIED and whether its work COMPLETED. Dispatched read-only by scripts/lib/skill-judge.mjs::runSkillJudge as Haiku with a bounded per-call budget. RETURNS one fenced json block of advisory per-skill judgments; the coordinator writes them. Read-only by contract — never writes files. Advisory-only — output never gates any action. <example>Context: session-end Phase 3.6.6 with skill-evolution.judge: true. user "Judge whether the skills this session selected were actually applied." assistant "Dispatching skill-applied-judge to read the transcript tail and emit advisory applied/completed judgments for each selected skill." <commentary>The judge produces a cheap advisory signal feeding the L3 skill-judgments sidecar — never an auto-action gate.</commentary></example>
|
|
3
|
+
description: 'Use this agent at session-end Phase 3.6.6 (#645 L3) to judge — from the session transcript tail — whether each selected skill was actually APPLIED and whether its work COMPLETED. Dispatched read-only by scripts/lib/skill-judge.mjs::runSkillJudge as Haiku with a bounded per-call budget. RETURNS one fenced json block of advisory per-skill judgments; the coordinator writes them. Read-only by contract — never writes files. Advisory-only — output never gates any action. <example>Context: session-end Phase 3.6.6 with skill-evolution.judge: true. user "Judge whether the skills this session selected were actually applied." assistant "Dispatching skill-applied-judge to read the transcript tail and emit advisory applied/completed judgments for each selected skill." <commentary>The judge produces a cheap advisory signal feeding the L3 skill-judgments sidecar — never an auto-action gate.</commentary></example>'
|
|
4
4
|
model: haiku
|
|
5
5
|
color: cyan
|
|
6
6
|
tools: Read, Grep, Glob
|
package/agents/test-writer.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: test-writer
|
|
3
|
-
description: Use this agent to close NAMED test gaps and to consolidate redundant tests. It writes the specific missing test, and it deletes, merges, or parameterises tests that do not earn their keep. <example>Context: Quality wave named one concrete gap — the invoice service never exercises the declined-payment branch. user: "The invoice service has no test for a declined payment" assistant: "I'll dispatch the test-writer to add that one error-path test and run the falsification check on it." <commentary>A named gap states a bug that would ship undetected; 'improve coverage' names nothing and is not a dispatchable task.</commentary></example> <example>Context: The auth test file has 14 tests — 6 assert the same validation branch and 3 only pin prose strings. user: "Clean up the auth test file" assistant: "I'll use the test-writer to merge the 6 duplicates into one parameterised test and delete the 3 prose-pinning tests — net test count falls 14 to 6 while catch-power rises, reported as test_delta.removed plus test_delta.consolidated." <commentary>Consolidation is a success outcome: fewer tests that each catch a distinct bug beat many that catch none, and a scope with no real gap legitimately ends with status no-tests-needed.</commentary></example>
|
|
3
|
+
description: 'Use this agent to close NAMED test gaps and to consolidate redundant tests. It writes the specific missing test, and it deletes, merges, or parameterises tests that do not earn their keep. <example>Context: Quality wave named one concrete gap — the invoice service never exercises the declined-payment branch. user: "The invoice service has no test for a declined payment" assistant: "I''ll dispatch the test-writer to add that one error-path test and run the falsification check on it." <commentary>A named gap states a bug that would ship undetected; ''improve coverage'' names nothing and is not a dispatchable task.</commentary></example> <example>Context: The auth test file has 14 tests — 6 assert the same validation branch and 3 only pin prose strings. user: "Clean up the auth test file" assistant: "I''ll use the test-writer to merge the 6 duplicates into one parameterised test and delete the 3 prose-pinning tests — net test count falls 14 to 6 while catch-power rises, reported as test_delta.removed plus test_delta.consolidated." <commentary>Consolidation is a success outcome: fewer tests that each catch a distinct bug beat many that catch none, and a scope with no real gap legitimately ends with status no-tests-needed.</commentary></example>'
|
|
4
4
|
model: inherit
|
|
5
5
|
color: orange
|
|
6
6
|
tools: Read, Edit, Write, Glob, Grep, Bash, Skill(session-orchestrator:*)
|
package/agents/ui-developer.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ui-developer
|
|
3
|
-
description: Use this agent for frontend implementation — UI components, pages, styling, accessibility, and responsive design. Handles React/Next.js components, CSS, and design system work. <example>Context: Implementation wave includes UI component work. user: "Build the invoice list page with filters and pagination" assistant: "I'll dispatch the ui-developer agent to implement the invoice list UI." <commentary>Frontend page implementation with interactive components is the ui-developer's specialty.</commentary></example> <example>Context: Accessibility improvements needed. user: "Fix WCAG violations in the dashboard components" assistant: "I'll use the ui-developer to audit and fix the accessibility issues." <commentary>WCAG compliance requires understanding semantic HTML, ARIA attributes, and keyboard navigation.</commentary></example>
|
|
3
|
+
description: 'Use this agent for frontend implementation — UI components, pages, styling, accessibility, and responsive design. Handles React/Next.js components, CSS, and design system work. <example>Context: Implementation wave includes UI component work. user: "Build the invoice list page with filters and pagination" assistant: "I''ll dispatch the ui-developer agent to implement the invoice list UI." <commentary>Frontend page implementation with interactive components is the ui-developer''s specialty.</commentary></example> <example>Context: Accessibility improvements needed. user: "Fix WCAG violations in the dashboard components" assistant: "I''ll use the ui-developer to audit and fix the accessibility issues." <commentary>WCAG compliance requires understanding semantic HTML, ARIA attributes, and keyboard navigation.</commentary></example>'
|
|
4
4
|
model: inherit
|
|
5
5
|
color: magenta
|
|
6
6
|
tools: Read, Edit, Write, Glob, Grep, Bash, Skill(session-orchestrator:*)
|
package/agents/ux-evaluator.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ux-evaluator
|
|
3
|
-
description: Use this agent for read-only UX evaluation of test-runner driver artifacts (Playwright AX-tree snapshots, screenshots, console output). Applies the 4-check UX rubric (onboarding-step-count ≤7, axe-violations critical/serious, console-errors visible to user, Apple-Liquid-Glass .glassEffect() conformance on SwiftUI 26+) and emits structured findings JSON with stable fingerprints. <example>Context: test-runner has just produced .orchestrator/metrics/test-runs/12345-1715688000123/ax-snapshots/. user: "Evaluate UX of the dashboard flow against rubric-v1." assistant: "I'll dispatch ux-evaluator to read the AX-tree snapshots and emit findings.json per skills/test-runner/rubric-v1.md." <commentary>ux-evaluator is the only agent that translates driver-captured artifacts into reconcilable findings; it never invokes drivers itself.</commentary></example>
|
|
3
|
+
description: 'Use this agent for read-only UX evaluation of test-runner driver artifacts (Playwright AX-tree snapshots, screenshots, console output). Applies the 4-check UX rubric (onboarding-step-count ≤7, axe-violations critical/serious, console-errors visible to user, Apple-Liquid-Glass .glassEffect() conformance on SwiftUI 26+) and emits structured findings JSON with stable fingerprints. <example>Context: test-runner has just produced .orchestrator/metrics/test-runs/12345-1715688000123/ax-snapshots/. user: "Evaluate UX of the dashboard flow against rubric-v1." assistant: "I''ll dispatch ux-evaluator to read the AX-tree snapshots and emit findings.json per skills/test-runner/rubric-v1.md." <commentary>ux-evaluator is the only agent that translates driver-captured artifacts into reconcilable findings; it never invokes drivers itself.</commentary></example>'
|
|
4
4
|
model: opus
|
|
5
5
|
color: blue
|
|
6
6
|
tools: Read, Grep, Glob, Bash
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Cut a release — the order the steps must run in, and the criteria that abort a release
|
|
3
|
+
disable-model-invocation: true
|
|
4
|
+
argument-hint: "[X.Y.Z]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Release
|
|
8
|
+
|
|
9
|
+
The user wants to cut a release of this package. Optional argument — the target version: **$ARGUMENTS**.
|
|
10
|
+
|
|
11
|
+
**The mechanism is `scripts/release.mjs`.** It exists, it is executable, and its pure half is unit-tested (`tests/scripts/release.test.mjs`). This command carries only the two things the script cannot carry: the **order**, and the **criteria that stop a release**. Do not restate the script's internals here — `node scripts/release.mjs --help` and the file header are the reference.
|
|
12
|
+
|
|
13
|
+
## Why the order is written down
|
|
14
|
+
|
|
15
|
+
`3.18.0` has a git tag, a GitHub release and a CHANGELOG entry — and the npm registry never saw it; a checklist line is not a mechanism.
|
|
16
|
+
|
|
17
|
+
Verify it yourself before trusting the paragraph above:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm view session-orchestrator versions --json # read 2026-08-19: 3.16.0, 3.17.0, 3.19.0, 3.20.0 — no 3.18.0
|
|
21
|
+
git for-each-ref --format='%(refname:short) %(creatordate:short)' refs/tags
|
|
22
|
+
gh release list --repo Kanevry/session-orchestrator --limit 12
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The same reading shows the other half: the GitHub releases for 3.15/3.18/3.19/3.20 were all created on 2026-08-19 within a three-second window — 5 to 31 days after their tags. Every step that lives only in prose gets skipped and backfilled later.
|
|
26
|
+
|
|
27
|
+
## The order
|
|
28
|
+
|
|
29
|
+
1. **Preconditions.** Working tree clean, on `main`, and `origin/main` **and** `github/main` both level with `HEAD`. The mirror is checked because the site deploy hangs off `github`, not `origin`.
|
|
30
|
+
2. **Set the version.** `node scripts/release.mjs --set-version X.Y.Z` — rewrites every version surface, syncs `package-lock.json`, re-stamps the measured census on the site.
|
|
31
|
+
3. **Write the editorial half.** The dated `## [X.Y.Z] - YYYY-MM-DD` CHANGELOG entry, `[Unreleased]` folded, README highlights. The script does not write these; `--check` enforces them.
|
|
32
|
+
4. **Preflight.** `node scripts/release.mjs --check --json` — every row green. This runs *after* step 2, never before: `--check` derives its target from `package.json`, so on the pre-bump version the registry- and tag-collision rows are red by construction.
|
|
33
|
+
5. **Gate, commit, push.** Full quality gate, then commit and push to **both** remotes.
|
|
34
|
+
6. **CI green — on the commit that will be published.** Not on its predecessor. A green pipeline from before step 5's commit is evidence about a different tree.
|
|
35
|
+
7. **Publish.** `node scripts/release.mjs --publish` — publishes, verifies the registry, tags **after** the verified publish, pushes `main` + tag to both remotes, then polls the live site. Add the GitHub release for the new tag (`gh release create`) as part of this step, not "later" — "later" is what produced the three-second backfill above.
|
|
36
|
+
|
|
37
|
+
Steps 2–7 are one continuous act. A release left parked between step 5 and step 7 is exactly the `3.18.0` state: every surface says released, the registry disagrees.
|
|
38
|
+
|
|
39
|
+
## Abort criteria
|
|
40
|
+
|
|
41
|
+
Stop and report. Do not work around, do not "fix it after the publish" — an npm publish is not revocable.
|
|
42
|
+
|
|
43
|
+
| Signal | Why it stops the release |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Any red row in `--check` | The preflight is the gate. A red row is a fact about this tree, not a formality. |
|
|
46
|
+
| `github/main` behind `origin/main` or `HEAD` | The mirror carries the site deploy and the GitHub release. Publishing over a lagging mirror is how the site falls a release behind. |
|
|
47
|
+
| `npm whoami` returns nothing or non-zero | The token is dead or absent. Publishing proceeds far enough to fail loudly *after* surfaces are committed. |
|
|
48
|
+
| CI not green on the exact commit being published | Green-on-the-previous-commit is the silent-regression class this repo exists to catch. |
|
|
49
|
+
| `--skip-ci` together with `--publish` | **Refused by the script** (`validateFlags`), not merely discouraged. `--skip-ci` marks the CI row green without checking anything; a green tick that verified nothing must never authorise an irreversible publish. It is an inspection aid for `--check`, never a release path. |
|
|
50
|
+
| Working tree dirty, or not on `main` | The published tarball would not correspond to any pushed commit. |
|
|
51
|
+
|
|
52
|
+
## After
|
|
53
|
+
|
|
54
|
+
`--publish` prints the remaining manual items (token rotation, async gallery indexing). Rotate the npm token — write tokens are short-lived by policy, and a token that transited a log or a chat is burned.
|
|
55
|
+
|
|
56
|
+
## See Also
|
|
57
|
+
|
|
58
|
+
- `scripts/release.mjs` — the mechanism; `SURFACES` and `LEAKAGE_PATTERNS` are the single sources of truth for version surfaces and the tarball leak classes.
|
|
59
|
+
- `skills/npm-publish/SKILL.md` — the human decisions: which version, what a leak means, when to abort instead of repair.
|
|
60
|
+
- `docs/distribution/npm-publish-checklist.md` — operator runbook and post-publish verification.
|
package/docs/migration-v3.md
CHANGED
|
@@ -32,12 +32,15 @@ Optional but recommended:
|
|
|
32
32
|
### 3a. Claude Code
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
# 1.
|
|
36
|
-
|
|
37
|
-
git
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
# 1. Update the plugin — run this INSIDE Claude Code, not in a shell:
|
|
36
|
+
# /plugin update session-orchestrator@kanevry
|
|
37
|
+
# A marketplace-installed plugin lives in a managed cache, not in a git checkout,
|
|
38
|
+
# so `git pull` does not apply to it.
|
|
39
|
+
|
|
40
|
+
# 2. Install Node dependencies. Claude Code has no `plugin dir` subcommand,
|
|
41
|
+
# so resolve the install path from the cache:
|
|
42
|
+
SO_DIR="$(dirname "$(find ~/.claude/plugins/cache -path '*session-orchestrator*' -name package.json 2>/dev/null | head -1)")"
|
|
43
|
+
cd "$SO_DIR" && npm install
|
|
41
44
|
|
|
42
45
|
# 3. Restart Claude Code so hooks.json is re-read
|
|
43
46
|
```
|
package/docs/persona-panel.md
CHANGED
|
@@ -33,7 +33,9 @@ mkdir -p .claude/personas
|
|
|
33
33
|
Templates ship with the plugin at `templates/personas/`. Copy one as a starting point:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
|
|
36
|
+
# Claude Code has no `plugin dir` subcommand — resolve the install path from the cache.
|
|
37
|
+
SO_DIR="$(dirname "$(find ~/.claude/plugins/cache -path '*session-orchestrator*' -name package.json 2>/dev/null | head -1)")"
|
|
38
|
+
cp "$SO_DIR/templates/personas/klima-physicist.v1.md" \
|
|
37
39
|
.claude/personas/klima-physicist.md
|
|
38
40
|
```
|
|
39
41
|
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Scope-Collision Guard — Pre-Dispatch File-Scope Deconfliction
|
|
2
|
+
|
|
3
|
+
> Reference for the mechanism that stops a wave from handing the SAME file to two agents (issue #1020).
|
|
4
|
+
> Four moving parts: the per-agent scope files, `scripts/validate-wave-scope.mjs` (`--assert-disjoint` / `--union`), `findScopeCollisions()` + `unionFileScopes()` in [`scripts/lib/scope-gate.mjs`](../scripts/lib/scope-gate.mjs), and the `PreToolUse` hook [`hooks/pre-task-scope-disjoint.mjs`](../hooks/pre-task-scope-disjoint.mjs).
|
|
5
|
+
> The coordinator-side **runbook** is `skills/wave-executor/wave-loop.md` § Scope Manifest 3.1–3.3 — this document does not restate it. What lives here instead: how the mechanism works, how it fails, what it deliberately does not see, and how to debug it.
|
|
6
|
+
|
|
7
|
+
## 1. What the pre-existing gates could not see
|
|
8
|
+
|
|
9
|
+
`assertFileScopeSubset()` (#796) checks each agent's scope **⊆** the wave's `allowedPaths` union, and `wave-scope-commit-guard` checks writes against that same union. A file claimed by two agents is a subset **twice over**, and the union grants it exactly once — so a double assignment is structurally invisible to both. It surfaced only afterwards, from an agent's own PSA-002 report (`findScopeCollisions()` header: `tests/scripts/sweep-expired-learnings-cli.test.mjs` handed to two agents of one wave). Per `.claude/rules/parallel-sessions.md` § Decision Tree, a file inside two declared scopes of one dispatch round is never a benign sibling signal — it is a deconfliction gap.
|
|
10
|
+
|
|
11
|
+
Two things follow, and both are the point of #1020:
|
|
12
|
+
|
|
13
|
+
- `allowedPaths` is **computed** from the per-agent declarations (`unionFileScopes()`), not transcribed by hand.
|
|
14
|
+
- Disjointness is asserted on the **declarations**, before the union exists.
|
|
15
|
+
|
|
16
|
+
## 2. The chain, in order
|
|
17
|
+
|
|
18
|
+
| # | Step | Artefact | Mechanism |
|
|
19
|
+
|---|------|----------|-----------|
|
|
20
|
+
| 1 | Declare | `<state-dir>/filescopes/wave-<N>/<agent-id>.json` (one per agent, plus `coordinator.json`) | written verbatim from the session plan |
|
|
21
|
+
| 2 | Assert disjointness | the sidecar array `[{id, files}, …]` | `validate-wave-scope.mjs --assert-disjoint` → `findScopeCollisions()` |
|
|
22
|
+
| 3 | Compute the union | stdout of `--union` → `allowedPaths` | `expandTestSiblings(unionFileScopes(scopes), { role })` |
|
|
23
|
+
| 4 | Inject | `FILE-SCOPE — exactly these:` + a fenced block in each agent prompt | the per-agent file from step 1 |
|
|
24
|
+
| 5 | Dispatch | `.orchestrator/wave-dispatch-scopes.json` (ledger) | `hooks/pre-task-scope-disjoint.mjs`, `PreToolUse` matcher `Agent` |
|
|
25
|
+
|
|
26
|
+
`<state-dir>` is the first of `.pi` / `.cursor` / `.codex` / `.claude` that carries a `wave-scope.json` — the same precedence `findScopeFile()` and the hook's `waveKeyOf()` use.
|
|
27
|
+
|
|
28
|
+
### 2.1 Why `--union` runs last
|
|
29
|
+
|
|
30
|
+
A union computed over colliding scopes **launders the defect into the artefact meant to prevent it**: `allowedPaths` then grants the contested file, and every later gate — `--assert-subset`, `enforce-scope` Gate 7, the commit guard — sees a perfectly legal write. `validate()` in `validate-wave-scope.mjs` enforces the order in code: `--assert-subset` → `--assert-disjoint` → `--union`, and `--union` returns early because it is a QUERY MODE that replaces the manifest echo on stdout.
|
|
31
|
+
|
|
32
|
+
The same ordering argument applies one level up: step 2 runs on the **declared** scopes, before step 3 expands test siblings. See § 6 for the limit that buys.
|
|
33
|
+
|
|
34
|
+
### 2.2 Why the scope files are not temp files
|
|
35
|
+
|
|
36
|
+
Steps 1, 2, 3, 4 and the `--assert-subset` assertion all read the *same* file, addressed by wave and agent id. A `$TMPDIR` copy is the one failure in this chain that **costs no error**: the injector finds nothing, no `FILE-SCOPE` block reaches the prompt, `extractScopeFromPrompt()` returns `[]`, and the hook allows the dispatch exactly as it did before #1020 — signal-free (matrix rows 5/6 below). The scope files are control state like `wave-scope.json` itself, never a wave territory; writing them legitimately trips `bash-write-verify` once per wave rollover, and widening `allowedPaths` to silence that would grant agents write access to the deconfliction record.
|
|
37
|
+
|
|
38
|
+
The coordinator's **own** planned direct edits belong in `coordinator.json` in the identical form. They are not dispatches, so the hook can never see them (§ 6); the CLI check is the only gate that covers them.
|
|
39
|
+
|
|
40
|
+
## 3. The collision algorithm
|
|
41
|
+
|
|
42
|
+
`findScopeCollisions(agentScopes, { knownFiles })` compares every cross-agent entry pair through `classifyEntryCollision()`, in three binding stages:
|
|
43
|
+
|
|
44
|
+
1. **Exact string equality** → kind `concrete`. The commonest real case, and the only stage that works for a file that **does not exist yet**.
|
|
45
|
+
2. **Concrete vs glob** via `pathMatchesPattern(concrete, glob)` → kind `concrete`. Exact and I/O-free. Two *distinct concrete* paths are disjoint and return immediately.
|
|
46
|
+
3. **Glob ∩ glob**, in two sub-stages:
|
|
47
|
+
- **3a — witness:** expand both entries against `KNOWN = knownFiles ∪ {every concrete entry of every agent}`; a non-empty intersection is `glob-expanded`. The second half of that union matters — a file the wave is about to *create* is not in `git ls-files`, but a concrete claim by one agent can still witness another's glob.
|
|
48
|
+
- **3b — prefix fallback:** for the intersection that exists only in files not yet on disk. Requires literal-prefix containment in either direction, at least one **recursive** entry (`**`, or a trailing `/`, which `pathMatchesPattern` matches by `startsWith` at any depth), and compatible literal suffixes. The suffix filter is a necessary condition, so it adds no false negative while removing `scripts/**/*.ts` vs `scripts/**/*.mjs`.
|
|
49
|
+
|
|
50
|
+
`knownFiles` is **injected, never discovered**: `scope-gate.mjs` is hook-safe (pure, sync, no I/O, no spawn) because `enforce-scope.mjs` reaches it on a hot path, and under the exit-0/stdout-JSON protocol a throw there reads as "no decision" = ALLOW. The CLI spawns `git ls-files` in `knownRepoFiles()`; the hook does the same in `listTrackedFiles()`, both resolving `git rev-parse --show-toplevel` first so a session started in a subdirectory produces repo-relative paths on both sides.
|
|
51
|
+
|
|
52
|
+
Duplicate agent ids are reported separately (`duplicateIds`), not as a self-collision. A record with no usable id runs as `<unnamed#i>` rather than being dropped — an unreviewed scope is exactly the one that collides.
|
|
53
|
+
|
|
54
|
+
### 3.1 Why `pathMatchesPattern` alone cannot do stage 3
|
|
55
|
+
|
|
56
|
+
The matcher is **directed**: argument 2 is compiled into a regex, argument 1 is tested as a literal string. Measured in this working tree on 2026-08-14:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
$ node --input-type=module -e "import { pathMatchesPattern } from './scripts/lib/scope-gate.mjs';
|
|
60
|
+
console.log(pathMatchesPattern('scripts/**/*.mjs','scripts/lib/*.mjs'));
|
|
61
|
+
console.log(pathMatchesPattern('scripts/lib/x.mjs','scripts/**/*.mjs'),
|
|
62
|
+
pathMatchesPattern('scripts/lib/x.mjs','scripts/lib/*.mjs'));"
|
|
63
|
+
false
|
|
64
|
+
true true
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Both globs match `scripts/lib/x.mjs`, yet the direct comparison says `false`. For `assertFileScopeSubset()` that inexactness is *safe*: its glob branch reduces to verbatim presence plus literal-prefix coverage and therefore **over-approximates coverage**, which at worst accepts a union it could not fully prove. For a **collision** check the sign flips — the same over-approximation becomes a **false negative**, i.e. a missed collision, i.e. the incident. That is why the two exact stages decide first and stage 3 is reached only for pairs neither can settle.
|
|
68
|
+
|
|
69
|
+
## 4. The hook
|
|
70
|
+
|
|
71
|
+
`hooks/pre-task-scope-disjoint.mjs` is a `PreToolUse` hook on matcher **`Agent`** (registered in `hooks/hooks.json`). It blocks a wave from handing the same file to two agents at the moment of dispatch, before either has written a byte.
|
|
72
|
+
|
|
73
|
+
It cannot compare a batch of siblings directly — the header records why, measured against 12 archived transcripts of this repo (147 dispatch `tool_use` blocks, 51 batches, measured 2026-08-14): the dispatch tool is named `Agent` and not `Task`; the payload carries **no** structured file scope (`files` / `file_scope` / `scope` → 441 probes, zero hits), so the scope exists only as prose inside `prompt`; and the not-yet-dispatched siblings of a batch are not visible in the transcript at dispatch time. What remains is a **ledger**: each dispatch records its scope under a wave key, and the next dispatch is checked against everything already recorded.
|
|
74
|
+
|
|
75
|
+
- **Ledger:** `.orchestrator/wave-dispatch-scopes.json` (gitignored), keyed `<session-id>|w<wave>|<role>` from `<state-dir>/wave-scope.json`.
|
|
76
|
+
- **Lock:** `.orchestrator/wave-dispatch-scopes.lock` — the read-modify-write cycle runs under `withFileLock()` (the primitive behind the PSA-005 STATE.md lock). `writeJsonAtomicSync` makes the write atomic, never the *cycle*.
|
|
77
|
+
- **Scope extraction:** `extractScopeFromPrompt()` finds a scope marker line and takes the FIRST fenced block after it, accepting only lines that survive a deliberately strict `looksLikeRepoPath()`. `normalizeScopeEntry()` then folds `./`, `//` and `/./` spellings together, and `promoteDirEntries()` rewrites `scripts/lib` → `scripts/lib/` **on evidence** (not a tracked file itself, at least one tracked file beneath it) — both because two spellings of one path previously compared as disjoint.
|
|
78
|
+
- **DENY** fires on exactly one condition: a collision involving THIS dispatch with a prior agent of the same wave that is **still in flight** (§ 5). The reason names the agent pair, the collision kind and the evidence paths; the suggestion is "give the file exactly ONE owner, or wait for the named agent(s) and re-dispatch", plus the ledger path to delete if the state is stale. The ledger is deliberately **not** persisted on a deny — the dispatch did not happen, so recording it would make the retry-after-fix look like a duplicate.
|
|
79
|
+
|
|
80
|
+
### 4.1 Error-class matrix — deliberately fail-**open**
|
|
81
|
+
|
|
82
|
+
The blast radius is asymmetric. A false positive on the dispatch path blocks every agent of the session — the guard becomes a session outage, and nothing downstream catches a dispatch that never happened. A false negative is a double assignment that three later gates still catch (`validate-wave-scope.mjs`, `enforce-scope.mjs` at write time, the W5 verification pass). Fail-closed is right for a WRITE guard (`enforce-scope.mjs` is), and wrong here. Each row is a choice, not an oversight:
|
|
83
|
+
|
|
84
|
+
| # | Condition | Decision |
|
|
85
|
+
|---|-----------|----------|
|
|
86
|
+
| 1 | disabled via profile/env | exit 0, silent |
|
|
87
|
+
| 2 | repo module failed to load | ALLOW + `GUARD INACTIVE` banner on stderr |
|
|
88
|
+
| 3 | stdin empty / not JSON | ALLOW |
|
|
89
|
+
| 4 | `tool_name` is not `Agent` | ALLOW |
|
|
90
|
+
| 5 | prompt carries no scope marker | ALLOW |
|
|
91
|
+
| 6 | scope block present but unparseable | ALLOW |
|
|
92
|
+
| 7 | ledger unreadable / corrupt | WARN + ALLOW + **self-heal** (the verdict carries a fresh ledger) |
|
|
93
|
+
| 8 | `git ls-files` failed | ALLOW, degraded (stage 3a loses witnesses; concrete collisions still found) |
|
|
94
|
+
| 9 | `findScopeCollisions` not evaluable | WARN + ALLOW |
|
|
95
|
+
| 10 | same agent id re-dispatched | ALLOW, ledger record replaced (a retry must not self-lock) |
|
|
96
|
+
| 10a | collision, but every colliding prior agent has FINISHED | ALLOW + prune those records |
|
|
97
|
+
| 11 | collision with a prior agent still IN FLIGHT | **DENY** |
|
|
98
|
+
| 12 | unexpected throw in `main()` | ALLOW + stderr |
|
|
99
|
+
| 13 | liveness probe throws / no evidence at all | treated as IN FLIGHT (bounded by the TTL, § 5) |
|
|
100
|
+
| 14 | ledger lock not acquirable within its budget | run the cycle UNLOCKED (degraded), never deny |
|
|
101
|
+
|
|
102
|
+
Two structural rules keep this matrix honest, both recorded in the hook header:
|
|
103
|
+
|
|
104
|
+
- `decide()` is a **pure function returning a verdict**; the module emits exactly once, at the end. `emitWarn`/`emitDeny` call `process.exit(0)` and never return, so a warn emitted from inside the checking flow would terminate the process before a later collision could be denied — and would skip the lock's release `finally`.
|
|
105
|
+
- Row 9's discriminator is **not** `ok !== true`. `ok` means *disjoint*, so `ok === false` is the normal result of a real collision; "not evaluable" is `ok === false` with BOTH result arrays empty. Reading `ok` as evaluability would turn every genuine collision into a warn, i.e. an allow — the exact fail-open the hook exists to prevent.
|
|
106
|
+
|
|
107
|
+
## 5. The liveness probe
|
|
108
|
+
|
|
109
|
+
A ledger with no notion of completion denies the wrong thing. Measured over 38 archived transcripts of this repo (346 `Agent` dispatch blocks; hook header, 2026-08-14): 0 of 4 same-batch overlaps and **2 of 2 cross-dispatch overlaps** would have been denied — and both cross-dispatch pairs were legitimate **sequential repair passes** (a dispatch and its later fix). Because a deny deliberately does not persist the ledger, the re-dispatch would have met the same stale record: a permanent block.
|
|
110
|
+
|
|
111
|
+
The discriminator is therefore neither time nor the agent's name, but whether the recorded agent is **still in flight**. Two transcript shapes carry that:
|
|
112
|
+
|
|
113
|
+
- **Synchronous dispatch** — the `tool_result` for the dispatch's `tool_use` id arrives when the agent is done (measured: five `Agent` rows within 0.44 s, their results 5–11 minutes later). At the fifth agent's `PreToolUse` none of the first four has a result → all in flight → a real same-batch overlap still denies.
|
|
114
|
+
- **Asynchronous dispatch** — the `tool_result` arrives in ~0.2 s and reads `Async agent launched successfully`. **That text is a launch receipt, not a completion.** Treating it as one would let every real background-batch collision through. The completion arrives later as a `<task-notification>` record carrying `<tool-use-id>` and `<status>completed</status>` (measured: launch 14:14:26.768 → notification 14:24:39.360).
|
|
115
|
+
|
|
116
|
+
`buildTranscriptIndex()` reads all three record shapes and counts a description as finished only when **every** one of its dispatch ids is finished. Cost containment: the transcript is read **only once a collision has already been found** — i.e. only on the path that is about to deny; the no-collision path pays nothing. Transcripts above 256 MiB are treated as *no evidence*, never as a completion.
|
|
117
|
+
|
|
118
|
+
**Blind fallback and its named ceiling (BV-004).** With no transcript, or none carrying a record of that agent, liveness falls back to the ledger entry's own age with `IN_FLIGHT_TTL_MS = 30 min`. The ceiling is derived: the largest **measured** same-batch dispatch spread is 95.7 s, so 30 min is ~19× headroom against the false-ALLOW direction, while both measured sequential repair gaps (36 min, 49 min) sit above it. **Revisit trigger:** a same-batch spread above ~5 min appearing in `.orchestrator/metrics/`, or a harness change that stops writing `transcript_path` — either invalidates the headroom the number rests on.
|
|
119
|
+
|
|
120
|
+
## 6. Named limits
|
|
121
|
+
|
|
122
|
+
Complete list of what this guard does **not** see, or sees only approximately:
|
|
123
|
+
|
|
124
|
+
1. **The blind TTL window.** Without transcript evidence the only liveness signal is the 30-minute TTL above. Inside that window a finished agent still blocks (false deny, recoverable by deleting the ledger); outside it a running agent no longer blocks (false allow). Revisit trigger as stated in § 5.
|
|
125
|
+
2. **Test-sibling collisions.** Disjointness is asserted on the **declared** scopes (step 2), before `expandTestSiblings()` runs (step 3). Two agents whose production files share a basename receive the *same* emitted sibling glob (`tests/**/{basename}*.test.mjs`), which a declared-scope check cannot see. Revisit if a wave is ever scoped by basename family instead of by directory.
|
|
126
|
+
3. **Prose extraction fails only toward ALLOW.** The hook's only channel is the `FILE-SCOPE` prose block. A missing marker, a missing fence, a decorated path that fails `looksLikeRepoPath()` — all resolve to allow (rows 5/6). Measured: 42 of 147 archived prompts (28.6 %) carried a scope marker at all, so denying the non-extractable case would have denied ~7 dispatches in 10. The CLI check (step 2) is the gate that does not depend on prose.
|
|
127
|
+
4. **Coordinator-direct edits are invisible to the hook.** They are not dispatches, so no ledger entry exists for them. They participate in the CLI check via `coordinator.json` only — and 2 of the 5 divergences that motivated #1020 were coordinator-direct edits.
|
|
128
|
+
5. **The wave-key fallback.** With no readable `wave-scope.json`, `waveKeyOf()` degrades to `<session>|w?|?` and the ledger spans the whole session, so a wave-3 dispatch is compared against wave-1 records. Bounded, not eliminated, by the liveness probe: a prior record binds only while its agent is in flight.
|
|
129
|
+
6. **Glob ∩ glob without witnesses.** Stage 3a needs tracked files; with git unavailable (row 8) or for files not yet on disk, only stage 3b's prefix fallback carries the load — and it requires at least one recursive entry, so two non-recursive globs that intersect only in an unborn file are not detected.
|
|
130
|
+
7. **Only collisions involving the current dispatch are actionable.** A pair among already-dispatched agents was either denied at its own dispatch or predates the guard; re-denying it would block an innocent third agent.
|
|
131
|
+
8. **Lock loss reopens the race.** On lock timeout the cycle runs unlocked (row 14) — two dispatches starting together can then read the same ledger state and one record is lost. That is the pre-lock behaviour, chosen over denying on a lock-file problem.
|
|
132
|
+
|
|
133
|
+
## 7. Debugging
|
|
134
|
+
|
|
135
|
+
**A dispatch was denied and you do not believe it.** Read `.orchestrator/wave-dispatch-scopes.json`: it carries `waveKey`, `updated`, and one `{id, desc, files, at}` record per already-dispatched agent. The deny reason names the other agent — find its record and compare its `files` to the ones in your prompt's `FILE-SCOPE` block. Three outcomes:
|
|
136
|
+
|
|
137
|
+
- The other agent is genuinely running and the overlap is real → fix the ownership in the session plan (one file, one agent), rewrite the affected `filescopes/wave-<N>/*.json`, re-assert, re-dispatch.
|
|
138
|
+
- The other agent has finished, but the ledger still binds it → the transcript carried no evidence (§ 5) and you are inside the TTL window. Delete `.orchestrator/wave-dispatch-scopes.json`; the next dispatch rebuilds it.
|
|
139
|
+
- The `waveKey` names an older wave → the `<session>|w?|?` fallback (limit 5). Check that `<state-dir>/wave-scope.json` exists and is readable, then delete the ledger.
|
|
140
|
+
|
|
141
|
+
**A dispatch was NOT denied and should have been.** Work down the allow rows: is `FILE-SCOPE` present in the prompt with a fenced block right after it (rows 5/6)? Is the hook armed at all (`GUARD INACTIVE` on stderr = row 2)? Did a `systemMessage` warning appear (rows 7/9)? Cross-check the same scopes through the CLI, which does not depend on prose:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
node scripts/validate-wave-scope.mjs --assert-disjoint "$WAVE_SCOPES_SIDECAR" < <state-dir>/wave-scope.json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Exit 1 prints one `ERROR:` line per collision (`agents "A" and "B" both claim [...]`) and one per duplicate id. Exit 0 — nothing on stderr, the manifest echoed back on stdout — means the declared scopes really are disjoint and the hook was right to allow; the divergence is then in the prompt, not in the plan.
|
|
148
|
+
|
|
149
|
+
**Reset.** Delete `.orchestrator/wave-dispatch-scopes.json` (and `.orchestrator/wave-dispatch-scopes.lock` if a dead holder is suspected). Both are gitignored, both are rebuilt on the next dispatch, and neither is shared with any other mechanism. After the final wave, `<state-dir>/filescopes/` is deleted along with `wave-scope.json` — a stale `wave-<N>/` directory left behind is a scope claim nobody re-verified.
|
|
150
|
+
|
|
151
|
+
**Disable.** The hook honours the repo's profile gate (`shouldRunHook('pre-task-scope-disjoint')`, row 1) — a silent exit 0, no decision at all.
|
|
152
|
+
|
|
153
|
+
## 8. Provenance of the numbers
|
|
154
|
+
|
|
155
|
+
Every figure above is quoted from a measurement recorded next to the code that carries it, with its date:
|
|
156
|
+
|
|
157
|
+
- Transcript-shape figures (147 dispatch blocks / 12 transcripts / 51 batches / 441 zero-hit probes / 28.6 % marker coverage; 346 blocks / 38 transcripts for liveness; the 95.7 s, 36 min and 49 min spreads; the 0.44 s and 10-minute observations) — header of `hooks/pre-task-scope-disjoint.mjs`, measured 2026-08-14 against this project's archived transcripts.
|
|
158
|
+
- The directedness transcript in § 3.1 — run in this working tree on 2026-08-14; the command is printed with it.
|
|
159
|
+
|
|
160
|
+
Re-measure before citing any of these downstream. A count re-briefed later is a claim about the past (`.claude/rules/parallel-sessions.md` § PSA-006).
|
|
161
|
+
|
|
162
|
+
## See Also
|
|
163
|
+
|
|
164
|
+
- `skills/wave-executor/wave-loop.md` § Scope Manifest — the coordinator runbook (steps 3.1–3.3) and § Pre-Dispatch: File-Scope Injection (the prompt block shape).
|
|
165
|
+
- `.claude/rules/parallel-sessions.md` § Decision Tree (why a file in two declared scopes is never a benign sibling signal), § PSA-006 (measurement discipline).
|
|
166
|
+
- `hooks/enforce-scope.mjs` — the write-time gate, fail-**closed**; the deliberate inversion of this hook's posture.
|
|
167
|
+
- [`docs/adr/0011-guard-degradation-semantics.md`](adr/0011-guard-degradation-semantics.md) — the exit-0 hook protocol (#906) and why a truncated stdout envelope reads as no-decision, i.e. as ALLOW.
|