opencode-thrum-pro 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/assets/commands/thrum-_stamp-protocol.md +65 -0
  2. package/assets/skills/brainstorm-steward/SKILL.md +15 -0
  3. package/assets/skills/choosing-subagent-models/SKILL.md +19 -11
  4. package/assets/skills/conversation-only-context/SKILL.md +186 -0
  5. package/assets/skills/coordinator-dispatching-work/SKILL.md +28 -8
  6. package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +68 -0
  7. package/assets/skills/coordinator-merging-code/SKILL.md +24 -0
  8. package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +11 -7
  9. package/assets/skills/efficient-multi-agent-research/SKILL.md +7 -6
  10. package/assets/skills/persistent-watcher-archetype/SKILL.md +68 -9
  11. package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +123 -0
  12. package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +379 -0
  13. package/assets/skills/prime-agent/SKILL.md +2 -4
  14. package/assets/skills/project-setup/SKILL.md +118 -21
  15. package/assets/skills/update-queue/SKILL.md +3 -1
  16. package/assets/thrum_allowlist.json +34 -0
  17. package/dist/allowlist-sync.test.d.ts +1 -0
  18. package/dist/allowlist-sync.test.js +44 -0
  19. package/dist/index.js +24 -1
  20. package/dist/installer.d.ts +29 -0
  21. package/dist/installer.js +356 -1
  22. package/dist/installer.permissions.test.d.ts +1 -0
  23. package/dist/installer.permissions.test.js +602 -0
  24. package/dist/permission-fixture.integration.test.d.ts +1 -0
  25. package/dist/permission-fixture.integration.test.js +506 -0
  26. package/package.json +3 -2
@@ -84,6 +84,71 @@ parseable with `grep -F` semantics without regex. The reader-side regex in
84
84
  `verify-against-plan` anchors on this fixed format; do not reorder or recase any
85
85
  field.
86
86
 
87
+ ## Review-object binding (THRUM-REVIEW stamp — plan/prompt binding)
88
+
89
+ A review stamp's PRESENCE proves a review happened; it does not prove the
90
+ stamp still binds the artifact that was reviewed. An edit made after a
91
+ `Ready:Yes` verdict leaves a stamp that still reads `Ready:Yes` while
92
+ describing a document that no longer exists — the same class of staleness the
93
+ Authored-against stamp solves for the tree, applied here to the artifact
94
+ itself.
95
+
96
+ The `THRUM-REVIEW` stamp carries two extra keys per stage, binding it to the
97
+ exact git blob it reviewed and the exact source it was reviewed against:
98
+
99
+ | Stage | Self-binding key | Source-lock key |
100
+ | --- | --- | --- |
101
+ | `stage=plan` | `plan_obj` | `src_lock` |
102
+ | `stage=prompt` | `prompt_obj` | `plan_lock` |
103
+
104
+ ### Derivation (strip-then-hash, run at stamping time)
105
+
106
+ ```sh
107
+ # self-object blob: strip EVERY THRUM-REVIEW line, hash the remainder, filters OFF
108
+ obj=$(grep -v '^<!-- THRUM-REVIEW:' "$FILE" | git hash-object --no-filters --stdin)
109
+ # source-lock blob: the source file is not mutated by the stamp -> plain hash
110
+ lock=$(git hash-object --no-filters "$SOURCE_FILE")
111
+ ```
112
+
113
+ - Strip set = every line matching `^<!-- THRUM-REVIEW:` (all cycles) — NOT
114
+ `THRUM-GATE`/`THRUM-DEFER`. The stamp cannot bind a hash that includes
115
+ itself, so its own lines are removed before hashing (chicken-and-egg).
116
+ - `--no-filters --stdin` bypasses gitattributes clean filters / autocrlf so
117
+ the value emitted here and the value re-derived at read time agree
118
+ byte-for-byte.
119
+ - Hash the **working-tree** file, not a committed blob — an uncommitted
120
+ post-review edit must be caught before it is ever committed.
121
+ - For `stage=plan`: `$SOURCE_FILE` is the LOCKED brainstorm (plus design spec,
122
+ if any); `src_lock` proves the plan was reviewed against the source that was
123
+ current at review time.
124
+ - For `stage=prompt`: `$SOURCE_FILE` is the LOCKED plan; `plan_lock` proves the
125
+ prompt was reviewed against the plan that was current at review time.
126
+ - When multiple cycles' `THRUM-REVIEW: stage=<S>` lines coexist, the
127
+ **highest-cycle (last-appended)** line is the binding verdict; earlier
128
+ lines are history, never re-checked. Stamps are only ever appended, never
129
+ reordered, so append-order == cycle-order — select with `tail -1`, never a
130
+ sort (`cycle=10` must beat `cycle=9`, which a lexical sort inverts).
131
+
132
+ ### Null/unresolvable guard
133
+
134
+ Same failure class as the Authored-against stamp above: if either `obj` or
135
+ `lock` comes back empty or `null`, STOP and report — do NOT emit a stamp with
136
+ an empty/`null` binding key. A `THRUM-REVIEW` line with `plan_obj=` (or any of
137
+ the four binding keys) empty or literal `null` still matches a reader's regex
138
+ and reads as a valid binding — the value a reader would trust to prove binding
139
+ is exactly the value that never resolved.
140
+
141
+ ### Extended stamp shape (fixed key order preserved)
142
+
143
+ ```text
144
+ <!-- THRUM-REVIEW: stage=plan verdict=Ready:Yes cycle=<N> date=<YYYY-MM-DD> verify=<...> plan_obj=<blob> src_lock=<blob> [rev=... notes="..."] -->
145
+ <!-- THRUM-REVIEW: stage=prompt verdict=Ready:Yes cycle=<N> date=<YYYY-MM-DD> prompt_obj=<blob> plan_lock=<blob> -->
146
+ ```
147
+
148
+ `plan_obj`/`src_lock` and `prompt_obj`/`plan_lock` are appended after the
149
+ stage's existing keys, in this order — keeping the fixed-key-order convention
150
+ this file already establishes for the Authored-against stamp above.
151
+
87
152
  ## Read-time provenance re-derivation
88
153
 
89
154
  Both checks below are computable from the two fields the stamp already carries
@@ -26,6 +26,10 @@ tree rule (a roster member that is a coordinator escalates to the human, not
26
26
  to that coordinator). Everything below is additive — the brainstorm-specific
27
27
  duties layered on top of that base.
28
28
 
29
+ Your roster is entirely remote (other boxes), so the base skill's remote
30
+ modal-unblock note applies to every roster member by default — never reach
31
+ for `thrum tmux key`, which is local-socket-only.
32
+
29
33
  ## Roster = brainstormers
30
34
 
31
35
  Your `watch_params.roster` is the list of brainstormer agent names you
@@ -63,6 +67,17 @@ thread:
63
67
  detection would miss it entirely if the send failed, was mis-addressed,
64
68
  or the brainstormer simply froze before sending.
65
69
 
70
+ This backstop's actual capture loop runs as the same `thrum monitor`-
71
+ scheduled script the base `persistent-watcher-archetype` skill ships —
72
+ `resources/thrum-watch-pane-capture.sh` — pointed at your own roster of
73
+ brainstormers via your own `watch_params.json`. One parameterized script
74
+ serves both archetypes; only the roster contents and cadence differ, and
75
+ those are already `watch_params.json` fields, not script forks. Follow
76
+ that skill's "Driving your cycle" section for the setup-copy step and the
77
+ exact `thrum monitor start` registration — **`--notify-on-success` is
78
+ mandatory there too**, for the identical reason: a `--schedule`d job
79
+ delivers nothing on `--match` alone (thrum-ruz1z §5c).
80
+
66
81
  ## The queue (your pane console)
67
82
 
68
83
  Render one unified list across every roster member, oldest-first:
@@ -52,24 +52,30 @@ These are the tiers now:
52
52
 
53
53
  ## Worked examples — LITERAL ARGUMENTS, AND EFFORT IS TOOL-DEPENDENT
54
54
 
55
- **Pass `model` AND `effort` on every spawn, whichever mechanism you use.** Syntax
56
- per mechanism — re-check the schema in front of you rather than restating from
57
- memory, because it can change:
55
+ **Pin `model` on every spawn. Pass `effort` wherever the mechanism exposes it.**
56
+ Syntax per mechanism — re-check the schema in front of you rather than restating
57
+ from memory, because it can change:
58
58
 
59
59
  | Mechanism | `model` | `effort` |
60
60
  |---|---|---|
61
- | **Agent tool** | ✅ settable | ✅ **settable — pass it** |
61
+ | **Agent tool** | ✅ settable | ✖ **not exposed — do not pass it** |
62
62
  | **Workflow `agent()`** (opts: label, phase, schema, model, effort, isolation, agentType) | ✅ settable | ✅ settable |
63
63
  | **Agent DEFINITION** (`.claude/agents/*.md` / plugin `agents/*.md` frontmatter) | ✅ | ✅ — sets the default for that agent type |
64
+ | **`thrum tmux create` / `launch`** | ✅ `--model` | ✅ `--effort` — pass on BOTH |
64
65
 
65
- **Agent tool — pass `effort` as a literal argument, alongside `model`.**
66
+ **Agent tool — `model` only. It takes no `effort` argument.**
66
67
 
67
68
  ```python
68
- Agent(subagent_type="general-purpose", model="sonnet", effort="low",
69
+ Agent(subagent_type="general-purpose", model="sonnet",
69
70
  description="Code-quality review of <branch>",
70
71
  prompt="...")
71
72
  ```
72
73
 
74
+ **The tier philosophy still governs even where the Agent tool cannot express it:**
75
+ sonnet-low for investigation and mechanical work, sonnet-medium for reviewers,
76
+ verifiers and implementers. Where effort is not settable, carry the intent through
77
+ the agent definition, the Workflow opts, or the tmux launch flags.
78
+
73
79
  An agent definition can also carry a default:
74
80
 
75
81
  ```yaml
@@ -87,8 +93,9 @@ agent(prompt, { model: "sonnet", effort: "low" }) // mechanical sub-
87
93
  ```
88
94
 
89
95
  **THE DIRECTIVE, imperative and not a comment: REVIEWERS RUN SONNET AT MEDIUM
90
- EFFORT.** Pass `effort: "medium"` literally — under Workflow AND under the Agent
91
- tool. Never dispatch a reviewer "from memory".
96
+ EFFORT.** Pass `effort: "medium"` literally under Workflow, in the agent
97
+ definition, or on `thrum tmux create`/`launch`. Never dispatch a reviewer "from
98
+ memory".
92
99
 
93
100
  **AND VERIFY, DO NOT ASSERT:** any claim about what a tool does or does not expose
94
101
  must be checked against the schema in front of you.
@@ -112,9 +119,10 @@ plus brainstormer → opus-medium and brainstormer's own subagents → sonnet-lo
112
119
 
113
120
  ## Pin every spawn explicitly — the floor depends on the ROLE, not the depth
114
121
 
115
- Every orchestrator MUST pass an explicit `model:` and `effort:` on EVERY subagent
116
- it spawns. An unspecified subagent SILENTLY INHERITS THE PARENT'S MODEL — so an
117
- Opus orchestrator that forgets the pin just spent Opus tokens on a grep.
122
+ Every orchestrator MUST pass an explicit `model:` on EVERY subagent it spawns, and
123
+ `effort` wherever the mechanism exposes it. An unspecified subagent SILENTLY
124
+ INHERITS THE PARENT'S MODEL — so an Opus orchestrator that forgets the pin just
125
+ spent Opus tokens on a grep.
118
126
 
119
127
  **The floor is set by what the agent DOES, not by how deep it sits** — see the
120
128
  Agent tiers table above. This applies recursively: an implementer spawning its
@@ -0,0 +1,186 @@
1
+ ---
2
+ name: conversation-only-context
3
+ description: "Use at the START of any long-lived agent session (researcher, coordinator, orchestrator, brainstormer), and whenever the operator says we are going to have a long conversation, get a lot done, or keep many threads open - also when context is climbing, when several investigations are in flight at once, when you are about to read code or a multi-file doc set to investigate, or when you catch yourself opening a log to just check quickly."
4
+ ---
5
+
6
+ # Conversation-only context — spend your own context on the human, dispatch everything else
7
+
8
+ Your context is the session's scarcest resource. It buys conversation with the
9
+ operator, messaging, queue and state — nothing else. Every investigation,
10
+ verification, root-cause, code read, doc summary and VM job goes to a named
11
+ persistent subagent that writes its result to a file.
12
+
13
+ `<scratch>` below means `/private/tmp` on macOS (where `/tmp` is a symlink) and
14
+ `/tmp` on Linux. Check with `[ -L /tmp ]`; never assume from the platform name.
15
+
16
+ ## Wake procedure
17
+
18
+ 0. Caller binding decays to anonymous across a restart. Before the first mutating
19
+ thrum command, re-warm it: `thrum prime >/private/tmp/rw.txt 2>&1` — output to
20
+ a file, **not** back into context.
21
+ 1. Read the **full** prime briefing with the Read tool, every page, in order. Name
22
+ one load-bearing fact from the **middle** of it before taking any action.
23
+ 2. Execute the resume plan from the newest restart snapshot.
24
+ 3. Reconcile `thrum queue list` against `thrum state show personal_state:current`.
25
+ 4. Read `thrum inbox --unread` **and** run `thrum message search` — stale unread
26
+ sorts last and is missed by the inbox page. Mark each processed message read
27
+ (`thrum message read <id>`).
28
+ 5. Where the snapshot lists live subagents (§ restart handoff), respawn each one
29
+ with its result file as seed before doing its work yourself.
30
+
31
+ ## The dispatch rule
32
+
33
+ **The parent never reads code, logs, or multi-file doc sets to investigate.**
34
+ Ask a subagent a smaller question instead. "I will just check quickly" is the
35
+ failure this skill exists to prevent.
36
+
37
+ | Parent keeps inline | Parent dispatches |
38
+ |---|---|
39
+ | Reading and replying to messages (`thrum reply --stdin <<'EOF'`) | Any code read, grep sweep, or call-site audit |
40
+ | Queue and state reconcile | Root-cause investigation |
41
+ | Talking to the human at the pane | Verification of an implementer's work |
42
+ | Owner-only decisions, surfaced with a recommendation | Summarizing a doc set |
43
+ | Tiny mechanical git **in its own worktree** (commit/push docs) | Any VM job |
44
+ | Reading a subagent's ≤20-line summary | Anything whose tool output runs to pages |
45
+ | The ONE file a question names — a subagent's report, or a deliverable you were asked to review | Everything the named file cites |
46
+
47
+ That row is purpose, not size: a file handed to you to act on is conversation,
48
+ not investigation.
49
+
50
+ Never spawn a subagent to operate inside another agent's worktree.
51
+
52
+ ## Dispatch shape
53
+
54
+ Every dispatch carries: an explicit `name:`, an explicit `model:`, an output file
55
+ path, a ≤15-20 line reply cap, and the pasted CONSTRAINTS block. Launch
56
+ independent subagents **in one message** so they run in parallel.
57
+
58
+ Pin `model:` on every spawn. Pass `effort` only where the mechanism exposes it —
59
+ Workflow `agent()` opts, agent-definition frontmatter, `thrum tmux launch
60
+ --effort`. **The Agent tool does not take `effort`.**
61
+
62
+ ```python
63
+ Agent(subagent_type="general-purpose",
64
+ name="<prefix>_<slug>",
65
+ model="sonnet",
66
+ description="<3-5 words>",
67
+ prompt="""<task>
68
+
69
+ WRITE your result to <scratch>/<prefix>-<slug>.md. Reply to me with a
70
+ summary of at most 20 lines - no logs, no transcripts, no command dumps.
71
+
72
+ === CONSTRAINTS ... === # pasted verbatim, see below
73
+ """)
74
+ ```
75
+
76
+ Write is fine for a NEW result file; to update an existing one, Read it first or
77
+ append with a heredoc (`cat >> <path> <<'EOF'`) — Write refuses to overwrite a
78
+ file it has not read this session.
79
+
80
+ Continue it — never respawn:
81
+
82
+ ```python
83
+ SendMessage(to="<prefix>_<slug>",
84
+ message="Answer L3 and L5 first and report a partial now. Same file, append.")
85
+ ```
86
+
87
+ A name keeps working after the subagent's turn ends; a send resumes it with its
88
+ context intact. "A subagent whose turn ends is not waiting — it is gone" applies
89
+ to fire-and-forget monitors, not to a named agent you re-message.
90
+
91
+ ### Naming and tiers
92
+
93
+ | Subagent role | Name prefix | Model / effort (effort only where exposed) | Result file |
94
+ |---|---|---|---|
95
+ | Investigation, grep, doc summary, mechanical | `<topic>` (e.g. `vmdocs`) | `sonnet` / low | `<scratch>/<name>.md` |
96
+ | Root-cause | `<bead>_rc` | `sonnet` / low | `<scratch>/<bead>-rc.md` |
97
+ | Verifier, reviewer, design-verify | `<bead>_verify` | `sonnet` / medium | `<scratch>/<bead>-verify.md` |
98
+ | Implementer | `impl_<slug>` | `sonnet` / medium | `<scratch>/<slug>-impl.md` |
99
+ | VM driver | `vmdriver_<slug>` | `sonnet` / medium | `<scratch>/<slug>-vm.md` |
100
+ | Prose or deep review | any | `opus` — **only when the operator asks** | as above |
101
+
102
+ Haiku is banned. Opus is never yours to grant onward.
103
+
104
+ ### CONSTRAINTS block — paste verbatim into every dispatch
105
+
106
+ ```text
107
+ === CONSTRAINTS — apply to you and anything YOU dispatch (including this line) ===
108
+ - Work SYNCHRONOUSLY. Tests in the FOREGROUND with a bounded `-timeout`.
109
+ - NO POLL LOOPS. Never `until <check>; do sleep N; done`, never
110
+ `while kill -0 $(cat pid)`, never `$( )` in a loop condition — even when a
111
+ tool's own guidance suggests it. It trips a permission modal, and A FROZEN
112
+ PANE EMITS NOTHING, so nobody can tell you are blocked. If you must background
113
+ work, wait for the completion notification.
114
+ - Every sub-agent YOU spawn gets an explicit `model:` — sonnet (low mechanical,
115
+ medium judgment). HAIKU IS BANNED. Opus is never yours.
116
+ - READ-ONLY git outside your own worktree. NEVER `checkout`/`reset`/`restore`/
117
+ `stash`/`clean` in ANY directory — `stash` is one shared stack across all
118
+ worktrees and the shared tree holds live agents' uncommitted state.
119
+ - Pair every zero/empty result with a control that MUST return non-zero.
120
+ ```
121
+
122
+ ## Relaying findings
123
+
124
+ Consolidate one pass's findings into **one** numbered message, severity-tagged
125
+ BLOCKING / IMPORTANT / MINOR — never partial batches, which get half-fixed. But a
126
+ **design-gating** answer goes out the moment it lands, even while the rest of the
127
+ pass is still running: when a downstream implementer is already building, send the
128
+ cheapest zero-cost interim guidance immediately rather than waiting for the
129
+ verifier to finish.
130
+
131
+ A result file on this box is unreadable from a remote-box coordinator. **Put the
132
+ substance in the message; never send only a path.**
133
+
134
+ Attribute what you did not verify yourself: forwarding a subagent's finding as
135
+ your own claim launders an unchecked assertion into your authority. Either read
136
+ the cited line, or write "from my verifier, cites `file:line`".
137
+
138
+ ## Pane discipline
139
+
140
+ Write to the pane only when the human asked, or when the decision is one only the
141
+ human can make. Everything else goes through `thrum send` / `thrum reply`.
142
+ Surface an owner-only decision crisply, with a recommendation attached.
143
+
144
+ ## VM work
145
+
146
+ Use a named persistent driver (`vmdriver_<slug>`) per
147
+ `dev-docs/daemon-wedge-sync-audit/execution-pattern-driver-subagents-in-vm.md`:
148
+ reach the VM only through the repo's tmux-ssh script, state the three-clause GO
149
+ (execute now · drive to completion yourself · then stop and report), require the
150
+ isolation proof and the 3-section report. VM specifics — profiles, first-boot
151
+ re-key, isolation legs — are in `dev-docs/vm-dev-patterns/`. Never do VM work
152
+ yourself.
153
+
154
+ ## Restart handoff
155
+
156
+ Subagents do not survive a restart. Before restarting:
157
+
158
+ 1. Tell every live subagent to finish and write its file.
159
+ 2. Record in the restart snapshot, per subagent: **name · what it owned · result
160
+ file path · model/effort**.
161
+ 3. State which subagents have already exited.
162
+
163
+ The successor respawns each one with its result file as seed.
164
+
165
+ Restart on a number read from the pane footer, never on a felt sense.
166
+
167
+ ## See Also
168
+
169
+ - `efficient-multi-agent-research` — the fan-out sibling: partition, investigate to
170
+ disk, consolidate, for N>6 items. This skill sets the session's posture; that one
171
+ runs a single pass.
172
+ - `choosing-subagent-models` — the tier policy and source of the CONSTRAINTS block.
173
+
174
+ ## Failure modes
175
+
176
+ - Respawning a fresh single-shot subagent for a follow-up when a named one already holds the context — `SendMessage` it; a respawn loses the context and pays the re-read.
177
+ - A fire-and-forget "monitor until done" agent that arms a monitor and ends its turn — measured at 70 min, 122k tokens, zero output. Put the drive-to-completion clause in every dispatch.
178
+ - The parent reading code or docs "to just check quickly" — message the subagent that already read them and ask for the one field.
179
+ - One subagent handed ten tasks — partition it and dispatch the parts in parallel.
180
+ - A spawn with no `name:` — it cannot be re-messaged. Name every spawn.
181
+ - A spawn with no `model:` — it silently inherits Opus. Pin `model:` on every spawn, and `effort` wherever the mechanism takes it.
182
+ - Holding a design-gating answer behind the rest of the pass while an implementer builds on the old premise — that one goes immediately; the rest ships as one consolidated message.
183
+ - Acting on the resume plan alone and skipping the middle of the prime — the falsified premise sits in the part you skipped.
184
+ - Trusting an older "not started" message over a newer "already running" one — verify, do not recall; that cuts both directions.
185
+ - Restarting without listing every live subagent's name and result file in the snapshot — the successor cannot respawn what is not written down.
186
+ - Forwarding a subagent's BLOCKING finding to a third party as your own claim — check the cited line, or attribute it; never state as yours what you did not verify.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: coordinator-dispatching-work
3
- description: "Use when starting an epic, dispatching to an implementer, creating a worktree for an agent, or spawning a sub-agent. Loads coordinator-specific discipline for kicking off implementation work."
3
+ description: "Use when starting an epic, dispatching to an implementer or orchestrator, choosing or re-routing which orchestrator gets a task, creating a worktree for an agent, or spawning a sub-agent. Loads coordinator-specific discipline for kicking off implementation work - routing by requirement and checking the target's load first."
4
4
  ---
5
5
 
6
6
  # Coordinator: Dispatching Work
@@ -72,8 +72,10 @@ optimization.
72
72
 
73
73
  ## Check the target's queue before you dispatch (R3)
74
74
 
75
- Before dispatching, confirm the agent isn't already loaded: `thrum queue list --agent
76
- <target>` (message it to confirm if its state is unclear). An empty result does NOT
75
+ Before dispatching, read the target's load two ways: its queue AND its pane. `thrum
76
+ queue list --agent <target>` (a well-run orchestrator keeps its queue current) plus a
77
+ quick `thrum tmux capture <target>` to see what it is mid-task on (message it to confirm
78
+ if its state is unclear). An empty result does NOT
77
79
  mean free — it also shows for an agent that keeps no queue or that you mis-named;
78
80
  confirm by message before assigning. `assign` appends to `assigned_to` with no
79
81
  duplicate check, so this pre-dispatch read is the only guard against double-loading a
@@ -236,11 +238,21 @@ grep -E 'THRUM-REVIEW: stage=prompt verdict=Ready:Yes([[:space:]]|-->|$)' "$PROM
236
238
  ```
237
239
 
238
240
  - **Stamp present** → the pre-dispatch review is already satisfied; SKIP
239
- re-running it and dispatch.
240
- - **Stamp absent** → fall through to the normal pre-dispatch dual review. Do NOT
241
- block dispatch on the stamp — it is a shortcut that lets you skip a redundant
242
- review, not a new hard requirement (pre-feature prompts and prompts from other
243
- flows won't carry it).
241
+ re-running it and dispatch. (The binding check itself — does the stamp still
242
+ bind the exact prompt object and locked plan — is `project-setup`'s job, not
243
+ this gate's; see its canonical contract, `project-setup` SKILL.md § "Step 5:
244
+ Stamp the prompt for the review loop" + Phase 0. Do not restate that recipe
245
+ here.)
246
+ - **Stamp absent on a project-setup-produced prompt is an ANOMALY, not a
247
+ sanctioned shortcut.** After the prompt-stamp fix (`project-setup` Step 5),
248
+ project-setup cannot report completion or dispatch-readiness without
249
+ appending this stamp — so a project-setup prompt reaching dispatch without
250
+ one means something skipped a step. Do not treat its absence as normal; run
251
+ the full pre-dispatch dual review AND surface the missing stamp as a finding.
252
+ The one legitimate, narrow carve-out: **pre-feature prompts and prompts from
253
+ non-project-setup flows** genuinely never carry this stamp — for those, fall
254
+ through to the normal pre-dispatch dual review same as before, with no
255
+ anomaly flag.
244
256
 
245
257
  This is the prose side of the boundary; the post-DONE dual-review (which reviews
246
258
  the implementer's CODE, a different artifact) always runs separately. The prompt
@@ -251,6 +263,14 @@ post-DONE side.)
251
263
 
252
264
  When the fleet has standing orchestrators and the work is a full plan execution:
253
265
 
266
+ 🔴 **Route by what the work REQUIRES, not by which orchestrator is easiest to reach.**
267
+ An orchestrator co-located with special resources - a build/signing/release box, the
268
+ shared data store, a particular OS - is RESERVED for work that needs those resources;
269
+ all other work goes to a general-purpose orchestrator, addressed BY NAME. The
270
+ locally-visible orchestrator is the one you reach for by reflex, and that reflex
271
+ overloads it. Enumerate the FULL set first (local and remote alike), then pick by
272
+ requirement and by load - never by visibility.
273
+
254
274
  ### Step 1: Select an orchestrator
255
275
 
256
276
  ```bash
@@ -420,6 +420,74 @@ circuit-breaker — **BLOCKING**. Peer call with timeout but no circuit-breaker
420
420
  **Triage:**
421
421
  `git diff main...HEAD --name-only | xargs grep -n '{peer_call_patterns}'`
422
422
 
423
+ ## Lens 11 — Census classification coverage (mandatory test, not a pattern check)
424
+
425
+ **Why:** `go test ./...` never runs in Pass-3 — targeted-test selection is
426
+ scoped to files the diff touches, and `internal/daemon/rpc/testcensus` is an ad
427
+ hoc test target nothing routes to. A merge that adds a `state.NewState(...)`
428
+ call site in an `internal/daemon/rpc/*_test.go` file (test-fixture setup, not a
429
+ handler) can leave it unclassified and desync the frozen census snapshot with
430
+ no gate catching it — trunk went RED post-merge on 573mo v87 for exactly this
431
+ reason (thrum-ykmly).
432
+
433
+ **Relationship to Lenses 1–10:** every other lens in this file is an
434
+ LLM-judgment pattern check — grep a pattern, read the surrounding code, apply
435
+ judgment. This lens is MECHANICAL: run the command, read PASS/FAIL. It shares
436
+ its evidentiary discipline with the philosophy gate's coincidence-detector-tests
437
+ lens and preamble rule 3 (RUN any test you make a claim about) rather than with
438
+ Lenses 1–10's pattern-matching shape.
439
+
440
+ **Own narrower trigger — this lens does NOT fire on every gate run.**
441
+ `internal/daemon/` is already a gate-wide `trigger_directories` entry, so this
442
+ gate already RUNS on any `internal/daemon/rpc/` diff. This lens carries its OWN
443
+ trigger on top of that (see `config.lenses.census_classification_coverage` and
444
+ `lens_scope_semantics`): it fires only when the diff (against merge-base,
445
+ preamble rule 4) either (a) touches `internal/daemon/rpc/testcensus/**`
446
+ directly, or (b) touches an `internal/daemon/rpc/*_test.go` file AND an
447
+ added/changed line matches `trigger_content_pattern` (bare `NewState(`,
448
+ alias-agnostic on purpose — a renamed import of the state package would defeat
449
+ a `state.`-prefixed match, and over-firing here costs one fast test run while
450
+ under-firing reproduces the exact hole this lens exists to close). A diff
451
+ elsewhere under `internal/daemon/` (state/, safedb/, scheduler/, or an
452
+ `rpc/*_test.go` edit that never touches a `NewState(` call) fires the GATE but
453
+ not THIS lens — record it walked-and-N/A. A non-test `.go` change in
454
+ `internal/daemon/rpc/` alone (e.g. a handler edit with no test change) does not
455
+ fire this lens either: `census.go`'s `Run()` only scans `*_test.go` source
456
+ non-recursively, so a handler-only edit cannot desync the census by
457
+ construction.
458
+
459
+ **How to apply:** Read `config.lenses.census_classification_coverage`. Check
460
+ the trigger per the paragraph above. If it fires, run
461
+ `config.lenses.census_classification_coverage.mandatory_test_command` in the
462
+ gate's throwaway worktree (preamble rule 2) and record the literal output.
463
+
464
+ **Gates `TestCensusClassificationCoverage` ONLY — deliberately NOT the whole
465
+ package.** `mandatory_test_command` is `-run`-scoped to
466
+ `^TestCensusClassificationCoverage$`. The sibling test in the same package,
467
+ `TestCensusRegeneration`, is an orthogonal count-snapshot drift detector — it
468
+ reds on ANY new top-level test function added anywhere in
469
+ `internal/daemon/rpc`, unrelated to whether any `NewState` site is classified.
470
+ Gating the whole package would false-block the next unrelated census-surface
471
+ diff on `TestCensusRegeneration`'s brittleness with zero added
472
+ defect-prevention (`ClassificationCoverage` alone already catches the defect
473
+ this lens exists for). `TestCensusRegeneration`'s own staleness is tracked
474
+ separately (nd2j1/P1 owns the classification data; the brittle count-snapshot
475
+ assertion itself is tracked as **thrum-33zzh** — "make advisory or drop the
476
+ raw-count assertion"), not by this lens.
477
+
478
+ **Severity:** Trigger fires and `mandatory_test_command` was not run before
479
+ merge, OR was run and is RED — **BLOCKING**. A RED result means an unclassified
480
+ `NewState` site: confirm `testdata/classifications.json` was regenerated per
481
+ `internal/daemon/rpc/testcensus/README.md` and re-committed on the same branch
482
+ before merge.
483
+
484
+ **Triage:**
485
+ ```bash
486
+ git diff <merge-base>...<tip> --name-only | grep -E 'internal/daemon/rpc/testcensus/|internal/daemon/rpc/.*_test\.go$'
487
+ # for any *_test.go hit outside testcensus/, confirm content match:
488
+ git diff <merge-base>...<tip> -- <file> | grep -E '^\+.*NewState\('
489
+ ```
490
+
423
491
  ## Cross-gate consolidation with the philosophy gate
424
492
 
425
493
  This gate runs **in parallel** with `coordinator-philosophy-merge-gate` (both
@@ -176,6 +176,30 @@ executed half is **scoped to the change**:
176
176
  - Any `*_tripwire_test.go` the change touches must stay green or the diff must
177
177
  state why its premise changed; a tripwire edited to accommodate the change is a
178
178
  finding, not a fix.
179
+ - 🔴 **The CLASS-firing governance gates, on the merged tree — they are BLIND to
180
+ changed-package scoping, exactly like the `//go:embed` asset in §5.**
181
+ `TestFixtureDriftGuard_NoUnlistedSchemaMirrors` (`internal/schema`) and
182
+ `TestEveryMirrorWriterIsSanctioned` (`tests/gate`) fire on a CLASS — a hand-rolled
183
+ `CREATE TABLE` for a real production table name, or a new durablelane / mirror
184
+ writer — so a diff adds the violation **without touching `internal/schema` or
185
+ `tests/gate`**, and the changed-package `-race` never runs them. Cheap trigger, run
186
+ it every merge: `git diff <base> <tip> | grep -iE 'CREATE TABLE|ALTER TABLE|durablelane|MirrorWriter|sanctionedMirrorWriters'`
187
+ — ANY hit ⇒ also run `go test ./internal/schema/ -run TestFixtureDriftGuard` and
188
+ `go test ./tests/gate/ -run TestEveryMirrorWriterIsSanctioned` on the MERGED tree.
189
+ Measured 2026-08-28: kgi7t (`TestEveryMirrorWriterIsSanctioned`) and xo58f
190
+ (`TestFixtureDriftGuard`) BOTH escaped per-merge Pass-3 this way and surfaced only
191
+ in the daily gate as trunk-reds. The changed-package rule below is necessary, not
192
+ sufficient — a class gate is a property of the TREE, not the neighbourhood.
193
+ 🔴 **Also always-run, same class-gate reasoning:**
194
+ `TestNoMainStateDBWriteOwnerBypasses` (`internal/testgate`; corpus 5.1
195
+ owner-bypass-check) — a scanner ratchet over every raw main-State-DB write
196
+ site, blind to changed-package scoping the same way. Measured 2026-09-04:
197
+ thrum-mjmu3 Part a introduced a raw `r.db.ExecContext` outside
198
+ `safedb.WriteCoordinator` in a durable-lane cold-boot restore step; the
199
+ ratchet went 213→214 and gate_thrum's Pass-3 on that merge was CLEAN because
200
+ this test was not yet in the always-run set (thrum-wdt97). Run it on the
201
+ MERGED tree every merge: `go test ./internal/testgate/... -run
202
+ TestNoMainStateDBWriteOwnerBypasses`.
179
203
 
180
204
  **Establish branch-attributable vs pre-existing from the changed-package `-race`
181
205
  result** — a regression adds failures the same packages did not have before. Do
@@ -519,8 +519,11 @@ contract-drift and quality issues the internal reviewer misses.
519
519
  stamp. Any FEASIBILITY finding (see Loop semantics) routes to the
520
520
  coordinator instead of being folded inline.
521
521
  6. Researcher repeats only if cycle-1 introduces new design surface (rare for
522
- bounded mechanical plans); otherwise v2 LOCKED, stamped
523
- `<!-- THRUM-REVIEW: stage=plan verdict=Ready:Yes cycle=<N> date=<YYYY-MM-DD> verify=Ready:Yes -->`.
522
+ bounded mechanical plans); otherwise v2 LOCKED. Before stamping, compute
523
+ `plan_obj`/`src_lock` per `_stamp-protocol.md` § "Review-object binding"
524
+ (strip-then-hash the plan against itself for `plan_obj`; plain-hash the
525
+ LOCKED source for `src_lock`), then stamp
526
+ `<!-- THRUM-REVIEW: stage=plan verdict=Ready:Yes cycle=<N> date=<YYYY-MM-DD> verify=Ready:Yes plan_obj=<blob> src_lock=<blob> -->`.
524
527
  7. Researcher signals plan LOCKED back to coord, citing both review passes
525
528
  and the Deviations block.
526
529
 
@@ -561,11 +564,12 @@ review catches translation errors (plan → prompt) plus prompt-specific quality
561
564
  `requesting-code-review` SUPPLEMENTARY — clarity, scope language,
562
565
  dispatch-readiness, sub-agent model guidance, DONE-shape spec.
563
566
  3. Researcher consolidates → folds inline → re-issues prompt; `project-setup`
564
- appends the prompt-stage stamps (it OWNS them — see that skill's Step 5):
565
- first `<!-- THRUM-GATE: stage=prompt next=dispatch -->` immediately after the
566
- prompt is generated, then
567
- `<!-- THRUM-REVIEW: stage=prompt verdict=Ready:Yes cycle=<N> date=<YYYY-MM-DD> -->`
568
- after this review terminates.
567
+ OWNS and applies the prompt-stage stamps — the gate footer, the
568
+ binding-bound verdict stamp, and the hard completion requirement that it
569
+ not exist without one — per that skill's canonical contract (`project-setup`
570
+ SKILL.md § "Step 5: Stamp the prompt for the review loop" + Phase 0). This
571
+ step does not restate that mechanics; it exists only to confirm the stamp
572
+ is applied within the same project-setup session, before signaling back.
569
573
  4. Researcher signals "project-setup complete + post-setup dual-review applied"
570
574
  back to coord, citing both review passes + final artifact paths.
571
575
 
@@ -125,10 +125,10 @@ hard to consolidate. Specify table columns in every prompt.
125
125
  **Not specifying output file paths** - Agents may write to the working directory
126
126
  or not at all. Always include the exact output path in every prompt.
127
127
 
128
- **Have sub-agents write their findings file with a Bash heredoc, not the Write
129
- tool.** The Write tool refuses subagent report files ("Subagents should return
130
- findings as text, not write report files") regardless of how the brief is worded.
131
- The filesystem is writable; the tool is what refuses.
128
+ **Write is fine for a NEW findings file; a heredoc is the safe form for updating
129
+ one.** Write refuses to overwrite a file it has not Read this session, so a
130
+ round-2 agent updating an existing findings file fails unless it Reads it first.
131
+ Prefer `cat >> <path> <<'EOF'` to append on later rounds.
132
132
 
133
133
  **Put this in every prompt:**
134
134
 
@@ -136,8 +136,9 @@ The filesystem is writable; the tool is what refuses.
136
136
  READ-ONLY EVERYWHERE EXCEPT YOUR OUTPUT FILE.
137
137
  Do not modify source, tests, config, git state, or the issue tracker.
138
138
  You MUST write your findings to exactly: dev-docs/<topic>/findings_N.md
139
- Write it with a Bash heredoc (cat > <path> <<'EOF' ... EOF), NOT the Write tool —
140
- Write refuses report files. That single write is expected and authorized.
139
+ Write is fine for creating it. To UPDATE it on a later round, Read it first or
140
+ append with a heredoc (cat >> <path> <<'EOF' ... EOF) — Write refuses to
141
+ overwrite a file it has not Read. That single write is expected and authorized.
141
142
  ```
142
143
 
143
144
  If an agent still returns text instead of a file, persist it yourself verbatim