opencode-thrum-pro 0.1.5 → 0.1.6

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 (67) hide show
  1. package/assets/commands/thrum-_compact-runtime-commands.md +61 -0
  2. package/assets/commands/thrum-_snapshot-protocol.md +17 -6
  3. package/assets/commands/thrum-_stamp-protocol.md +9 -2
  4. package/assets/commands/thrum-compact-extended.md +352 -0
  5. package/assets/commands/thrum-compact.md +300 -0
  6. package/assets/commands/thrum-inbox.md +1 -1
  7. package/assets/commands/thrum-restart-extended.md +84 -11
  8. package/assets/commands/thrum-restart.md +105 -13
  9. package/assets/commands/thrum-send.md +4 -1
  10. package/assets/commands/thrum-sleep-extended.md +67 -46
  11. package/assets/commands/thrum-sleep.md +67 -58
  12. package/assets/skills/adversarial-critique/SKILL.md +5 -8
  13. package/assets/skills/brainstorm-steward/SKILL.md +65 -11
  14. package/assets/skills/brainstormer-staying-current/SKILL.md +1 -4
  15. package/assets/skills/choosing-subagent-models/SKILL.md +14 -0
  16. package/assets/skills/conversation-only-context/SKILL.md +11 -11
  17. package/assets/skills/conversation-only-context/resources/driver-subagent-pattern.md +1 -1
  18. package/assets/skills/conversation-only-context/resources/remote-environment-scenarios.md +1 -1
  19. package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +98 -6
  20. package/assets/skills/coordinator-context-monitoring/SKILL.md +29 -19
  21. package/assets/skills/coordinator-deploying-a-node/SKILL.md +124 -0
  22. package/assets/skills/coordinator-dispatching-work/SKILL.md +24 -11
  23. package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +62 -20
  24. package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +6 -6
  25. package/assets/skills/coordinator-merging-code/SKILL.md +25 -11
  26. package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +28 -7
  27. package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +3 -3
  28. package/assets/skills/coordinator-rolling-out-to-nodes/SKILL.md +92 -0
  29. package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +3 -3
  30. package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +21 -20
  31. package/assets/skills/coordinator-running-review-cycles/SKILL.md +15 -4
  32. package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +0 -2
  33. package/assets/skills/efficient-multi-agent-research/SKILL.md +3 -3
  34. package/assets/skills/implementer-orchestrating-execution/SKILL.md +11 -14
  35. package/assets/skills/orchestrate/SKILL.md +47 -35
  36. package/assets/skills/persistent-watcher-archetype/SKILL.md +172 -25
  37. package/assets/skills/persistent-watcher-archetype/resources/capture_archive.py +618 -0
  38. package/assets/skills/persistent-watcher-archetype/resources/jev_watcher_filter.py +837 -0
  39. package/assets/skills/persistent-watcher-archetype/resources/roster-watch.sh +1140 -0
  40. package/assets/skills/persistent-watcher-archetype/resources/roster-watch_test.sh +291 -0
  41. package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +2 -2
  42. package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +20 -8
  43. package/assets/skills/prime-agent/SKILL.md +10 -0
  44. package/assets/skills/project-setup/SKILL.md +22 -16
  45. package/assets/skills/project-setup/resources/implementation-agent.md +56 -35
  46. package/assets/skills/researcher-investigating/SKILL.md +2 -5
  47. package/assets/skills/thrum/SKILL.md +14 -7
  48. package/assets/skills/thrum/resources/ANTI_PATTERNS.md +16 -18
  49. package/assets/skills/thrum/resources/CLI_REFERENCE.md +17 -11
  50. package/assets/skills/thrum/resources/LISTENER_PATTERN.md +9 -3
  51. package/assets/skills/thrum/resources/MESSAGING.md +18 -11
  52. package/assets/skills/thrum/resources/WORKTREES.md +1 -1
  53. package/assets/skills/thrum-memory/SKILL.md +1 -1
  54. package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +1 -1
  55. package/assets/skills/update-queue/SKILL.md +6 -8
  56. package/assets/skills/using-the-dashboard/SKILL.md +30 -0
  57. package/assets/skills/using-the-queue/SKILL.md +76 -5
  58. package/assets/skills/using-thrum-state/SKILL.md +83 -0
  59. package/assets/skills/verify-against-plan/SKILL.md +120 -5
  60. package/assets/skills/watcher-archetype/SKILL.md +18 -0
  61. package/assets/skills/writing-durable-markdown/SKILL.md +5 -0
  62. package/dist/installer.js +4 -4
  63. package/dist/permission-fixture.integration.test.js +4 -4
  64. package/package.json +1 -1
  65. package/assets/skills/coordinator-deploying-the-fleet/SKILL.md +0 -396
  66. package/dist/allowlist-sync.test.d.ts +0 -1
  67. package/dist/allowlist-sync.test.js +0 -44
@@ -63,22 +63,46 @@ Three distinct fields, do not conflate them:
63
63
  | `thrum queue add --title "..."` | Create a bundle in your queue |
64
64
  | `thrum queue add --from-message <msg_id>` | Special case: lift ONE specific message you received (content->title, author->assigned_by, refs->refs). Use the exact msg-id you're acting on - never head-1 of the inbox |
65
65
  | `thrum queue add --ref bead:<id>` | Promote a backlog item into active work |
66
- | `thrum queue list [--status <s>] [--agent <id>]` | List your queue (or another agent's) |
66
+ | `thrum queue list [--status <s>] [--stage <s>] [--agent <id>]` | List your queue (or another agent's); `--status`/`--stage` AND-combine when both given |
67
67
  | `thrum queue show <bundle-id> [--agent <id>]` | Full detail for one bundle |
68
68
  | `thrum queue start <bundle-id>` | -> in_progress (clears block_reason) |
69
- | `thrum queue block <bundle-id> [--reason "..."]` | -> blocked (records the reason) |
69
+ | `thrum queue block <bundle-id> [--reason "..."]` | -> blocked (records the reason; `--reason` required) |
70
70
  | `thrum queue done <bundle-id>` | -> done (TEMPORARY waypoint, not deleted; `start` reopens it; clears block_reason) |
71
+ | `thrum queue wait <bundle-id> [--reason "..."]` | -> waiting (alias for `queue update --status=waiting`; `--reason` optional) |
72
+ | `thrum queue ready <bundle-id>` | -> ready (alias for `queue update --status=ready`) |
73
+ | `thrum queue update <bundle-id> [flags]` | General partial update: `--status`, `--stage`, `--reason` (status_reason), `--title`, `--ref` (repeatable, replaces existing refs), `--priority`, `--assigned-by`, `--assigned-to-remove` - at least one required; `--status=blocked` requires `--reason` |
71
74
  | `thrum queue drop <bundle-id>` | DELETE bundle + all its items, any status (the one destructive verb - drop promptly once a done bundle's record stops being useful) |
72
75
  | `thrum queue assign <bundle-id> <agent-id>...` | Append agent(s) to assigned_to (routing metadata only) |
76
+ | `thrum queue note <bundle-id> --stdin` (or `--body-file <f>`) | Append a dated progress note (append-only; shown by `show`, last note previewed by `list`) |
73
77
  | `thrum queue item add <bundle-id> --title "..."` | Add one embedded item |
74
78
  | `thrum queue item batch-add <bundle-id> --title "..." --title "..."` | Add several items in one call (shared --ref/--priority) |
75
79
  | `thrum queue item start/block/done <bundle-id> <item-id>` | Item-level status (start/done clear the item's block_reason) |
80
+ | `thrum queue item update <bundle-id> <item-id> [flags]` | Partial item update: `--title`, `--assigned-by`, `--ref` (repeatable, replaces existing refs), `--priority` - at least one required |
76
81
 
77
82
  Optional: `--ref <type>:<value>` (repeatable), `--priority N` (higher sorts
78
83
  first), and `--from-message` (special case, below). `add` / `item add` require at
79
84
  least one of `--title` or `--from-message`; explicit `--title` wins over
80
85
  `--from-message`. **`--title` is the normal way to add work.**
81
86
 
87
+ ### Status and stage: two independent axes
88
+
89
+ A bundle carries two orthogonal fields, both settable via `queue update`:
90
+
91
+ - **`status`** - `pending`, `in_progress`, `waiting`, `ready`, `blocked`,
92
+ `done`. Says whether the owning agent can or must act now. `start` / `block`
93
+ / `done` / `wait` / `ready` are all just `queue update --status=<x>` under a
94
+ short name; `wait` and `ready` are exact aliases with no independent
95
+ behavior of their own.
96
+ - **`stage`** - `intake`, `planning`, `dispatched`, `implementation`,
97
+ `integration`, `review`, `rework`, `gate`, `merge`, `deploy`,
98
+ `verification`, `cleanup`. Says where the bundle sits in its workflow,
99
+ independent of status. Optional; never set on items, bundle-only. Set/read
100
+ via `queue update --stage <s>` / `queue list --stage <s>` / `queue show`.
101
+
102
+ `status_reason` (set via `--reason` on `update`/`block`/`wait`) is the
103
+ general reason field; `--status=blocked` requires it, `wait` accepts it
104
+ optionally, other transitions clear it.
105
+
82
106
  ## Common Flows
83
107
 
84
108
  **Add work you're taking on (the normal case):**
@@ -139,6 +163,50 @@ close only the bundles a check proves done, and skip the rest. Two checks only:
139
163
  Do NOT read each bundle's full history - the sweep is those two checks against
140
164
  merged/closed state, nothing more.
141
165
 
166
+ ## Identity-field mutation: queue vs. state (two intentional shapes)
167
+
168
+ `queue update` / `queue item update` and `state set` handle identity-field
169
+ mutation differently. This is an intentional two-shape convention chosen per
170
+ domain, not an inconsistency - do not "fix" one to match the other.
171
+
172
+ **`queue.update` / `queue.item.update`**: wrapped by `RejectIdentityKeysHandler`,
173
+ which returns a typed `*queue.ErrImmutableField` error (`queue: cannot modify
174
+ immutable field %q`) if a raw identity key (`id`, `agent_id`, `created_at` for
175
+ bundles; `id`, `created_at` for items) appears in the wire payload at all -
176
+ checked BEFORE decode, fail-closed. Note: `QueueUpdateRequest`/
177
+ `QueueItemUpdateRequest` have no JSON fields for these identity keys anyway,
178
+ so this wrapper is defense-in-depth, not load-bearing on its own.
179
+
180
+ **`state.set`**: no reject path at all, and that is by design, per field:
181
+ - **Kind/Scope** are the addressing key - a different `(Kind,Scope)` pair
182
+ addresses a DIFFERENT record, so setting them isn't a mutation attempt and
183
+ there's nothing to reject.
184
+ - **Class** is derived server-side from the kind registry; no wire field
185
+ exists for a caller to even attempt to set it.
186
+ - **CreatedAt** is server-stamped once at creation (on the fleet tier it's
187
+ actually derived from `UpdatedAt` at read time rather than stored
188
+ independently); not client-spoofable, and `StateSetRequest` has no field to
189
+ smuggle it through.
190
+ - **EstablishedBy** is influenceable only via `CallerAgentID` on a
191
+ non-peercred transport (browser/WS, unit tests) - but that is a general,
192
+ already-documented property of the RPC layer (identity trust depends on
193
+ transport), not something unique to `state.set`, and it is explicitly out
194
+ of scope per `docs/security_model/master.md` §1.5: "Thrum does not attempt
195
+ to prevent agent identity forgery" is an owner ruling, stated as
196
+ [INTENDED] and permanent, not tracked as debt.
197
+
198
+ **The convention, stated plainly:** queue rejects raw identity keys at the
199
+ wire as defense-in-depth (even though its request types don't have those
200
+ fields anyway); state has no reject path because its identity/system-derived
201
+ fields are either addressing semantics (not a mutation) or server-derived/
202
+ unspoofable (nothing to reject). Two different but each internally-consistent
203
+ shapes, chosen by domain, not an oversight.
204
+
205
+ Non-goal: fail-closed reject-symmetry for `state.set` (mirroring queue's
206
+ `RejectIdentityKeysHandler`) is a possible future non-security hardening
207
+ preference, not built here - out of scope per `docs/security_model/master.md`
208
+ §1.5.
209
+
142
210
  ## Common Mistakes
143
211
 
144
212
  - **Treating `add`/`done` as the work itself.** The queue *tracks* your work; it
@@ -162,9 +230,12 @@ merged/closed state, nothing more.
162
230
  bundle stays, and only `drop` deletes it. Drop each done bundle promptly once
163
231
  its record stops being useful; a queue full of undropped done bundles is the
164
232
  primary rot mode.
165
- - **Reaching for a `remove`/`rename`/item-`drop` verb.** They do not exist by
166
- design: removal happens only at the bundle level via `drop`; titles are set at
167
- creation and not edited.
233
+ - **Reaching for a `remove`/item-`drop` verb.** Removal (of a bundle and every
234
+ item inside it) happens only at the bundle level via `drop` - there is no
235
+ finer-grained delete. Renaming/re-titling is NOT in this category: `queue
236
+ update --title` and `queue item update --title` are the supported post-create
237
+ edit path for a bundle's or item's title (and other fields - stage, refs,
238
+ priority, assigned-by).
168
239
  - **Trying to edit another agent's queue.** Impossible by design - you only
169
240
  write your own. Cross-agent work is messaging plus the recipient's own `add`.
170
241
  - **Treating the queue as durable/shared history.** It is local, private, and
@@ -71,6 +71,48 @@ when you're stating it without a live check; a write relayed from a message
71
71
  is forced to `--method relayed` (low-trust) — never choose `relayed`
72
72
  yourself.
73
73
 
74
+ ## Field Mutability Matrix
75
+
76
+ `Entry` (`internal/state/types.go`) has five mutability classes, not one
77
+ uniform "immutable field" guard:
78
+
79
+ - **immutable-identity** — part of the `(Kind, Scope)` addressing key, or
80
+ fixed at creation with no update path. "Changing" one of these isn't an
81
+ edit, it addresses a different record.
82
+ - **mutable-whole-object** — caller-settable via `state set`, which is
83
+ always a full upsert (overwrite), never a field-level PATCH. Need a
84
+ partial update? GET, edit client-side, SET the full value back.
85
+ - **mutable** — caller-settable via specific `state set` flags,
86
+ independent of Value.
87
+ - **system-derived** — recomputed server-side from non-caller input
88
+ (registry declaration, resolved caller identity); any caller-supplied
89
+ value for these is ignored, not merely defaulted.
90
+ - **derived** — set only as an automatic side effect of a `state set`
91
+ call, never independently caller-directed.
92
+
93
+ | Field | Class | How it's set/changed | Notes |
94
+ |---|---|---|---|
95
+ | `Kind` | immutable-identity | Parsed from the CLI/RPC target at call time | Part of the addressing key; a new `Kind` is a new record, not an edit. |
96
+ | `Scope` | immutable-identity | Parsed from the CLI/RPC target at call time | Same addressing-key reasoning as `Kind`. No error on "change" — it creates an independent entry (`TestStateHandler_HandleSet_DifferentScope_CreatesIndependentEntry`). |
97
+ | `Value` | mutable-whole-object | `state set --value` | Every `state set` is a full upsert; no field-level PATCH RPC/CLI surface exists. |
98
+ | `AsOf` | mutable (fleet only) | `state set --as-of` | Server only checks that it *parses* as a timestamp; nothing compares it against the previous stored `AsOf` or rejects a bump with no real change — a documented, deliberately unenforced soft-gap. |
99
+ | `Method` | mutable (fleet only) | `state set --method` | Forced to `relayed` server-side whenever the request carries `FromMessage`, overriding any caller-claimed value. Outside that one rule, a caller-supplied `Method` is accepted as-is with no check that it reflects how the value was actually established. |
100
+ | `VerifyCmd` | mutable (fleet only) | `state set --verify-cmd` | Plain caller-settable field. |
101
+ | `FreshnessOwner` | mutable (fleet only) | `state set --freshness-owner` | Plain caller-settable field. |
102
+ | `OriginDaemon` | system-derived | Stamped by sync/durable-lane plumbing | Exact assignment site outside `internal/state` is unconfirmed; not caller-settable via `state set`. |
103
+ | `Class` | system-derived, immutable-per-caller | Declared per-`Kind` in the code registry (`registry.go` `KindDef.Class`), stamped server-side on every write | `StateSetRequest` has no `class` wire field at all — a raw request that smuggles an unrecognized `class` key is silently dropped, not rejected (`TestStateHandler_HandleSet_ClassFieldOnWireIsSilentlyDropped`). Changes only if the registry declaration itself changes. |
104
+ | `EstablishedBy` | system-derived, immutable-per-caller | Re-resolved from the caller's identity on every write (`resolveCallerAgent` / `guard.DaemonResolve`), fleet tier only | No dedicated wire field to set directly — only `CallerAgentID`, fed through identity resolution, can influence it. On peercred transports the claim is verified against kernel evidence; on non-peercred transports (browser/WS, unit tests) an unverified claim is accepted verbatim. Local tier never populates this field (always `""`). |
105
+ | `CreatedAt` | immutable-identity (local tier only) | Stamped once at first insert | Local tier: preserved across updates by the store's rewrite path (`local_store.go` `Set`), not validated/rejected — it's simply never reassigned. Fleet tier: **not** independently preserved — there's no separate `created_at` column, so `fleet_store.go`'s `toEntry()` derives it from `UpdatedAt` on every read; do not assume local/fleet parity here. |
106
+ | `UpdatedAt` | derived | Automatic side effect of every `state set` | Never independently caller-directed. |
107
+
108
+ None of this is a uniform "reject the write" guard (that mechanism —
109
+ `RejectIdentityKeysHandler` / `ErrImmutableField` — belongs to `thrum queue`,
110
+ a different subsystem, not state). State's actual enforcement is a mix:
111
+ addressing semantics for the identity fields (no error, just a different
112
+ record), silent wire-level drop for `Class`/`EstablishedBy` (no field to
113
+ send them on), and preserve-on-rewrite for local-tier `CreatedAt` (no
114
+ validation, just never overwritten).
115
+
74
116
  ## Common Flows
75
117
 
76
118
  **Deploy runbook, after verifying a box's serving SHA:**
@@ -91,6 +133,47 @@ thrum state list
91
133
  effect, then `thrum state set` with the fresh value and `--method
92
134
  by_effect`. Never "correct" a row you have not personally re-measured.
93
135
 
136
+ ## Consolidating your agent-harness memories into state (durable overflow)
137
+
138
+ Your **agent-harness memories** — the memory index your runtime auto-loads at
139
+ session start (each runtime has its own: a `MEMORY.md`-style index on some,
140
+ an equivalent auto-injected file on Codex/Cursor/Copilot/others) — has a **load
141
+ size cap**. Past that cap the runtime **silently drops the tail entries** at
142
+ wake: they are invisible exactly when you most need them. Thrum state is an
143
+ uncapped, queryable durable store, so it is the right overflow home.
144
+
145
+ **Technique (scope-preserving — the only safe way to shrink the index):**
146
+
147
+ 1. **Classify, don't cut.** For each auto-loaded entry ask: *if this were
148
+ absent at wake, would I take a WRONG action, or merely a SLOWER one?*
149
+ WRONG-if-absent (destructive-op guards, owner rulings, disbelieve-the-
150
+ instrument traps, habits that must interrupt a confident wrong answer) STAY
151
+ in the auto-loaded index. SLOWER-if-absent (things you would just look up
152
+ once you know the topic exists) are demotable.
153
+ 2. **Demote by MOVING, never by shortening.** Relocate each demotable entry
154
+ **verbatim** into a thrum state entry (e.g. `thrum state set --kind
155
+ reference --scope <agent>_discipline_index`) and/or your runtime's secondary
156
+ lookup file. Leave a short **bidirectional pointer**: the auto-loaded index
157
+ keeps a one-line pointer to the state entry; the state entry names the topic
158
+ file the full detail lives in. **Never merge two entries, summarise to save
159
+ bytes, or delete** — summarising discards the SCOPE that makes an entry true,
160
+ which is the whole failure this guards against. The per-topic detail files
161
+ are never touched.
162
+ 3. **Reduction comes only from relocation.** Prove conservation:
163
+ `entries_removed_from_index == entries_added_to_state` (set equality, not
164
+ just count). Target comfortably under the load cap; do not chase a smaller
165
+ number by demoting genuine must-fire entries.
166
+ 4. **Use a high-reasoning subagent to index both stores** and PROPOSE the moves
167
+ (it produces the new index, the state value, and a verification checklist);
168
+ you review the judgment calls (which must-fire entries to keep) and apply.
169
+ Watch for **line-packing collateral**: if two entries share one physical
170
+ line, demoting one must not drag its line-mate out with it.
171
+ 5. **Refuse the size-nag hook.** A `PostToolUse` hook may fire after an index
172
+ edit demanding "compact / merge or drop stale entries." Merge and drop are
173
+ forbidden by this contract; the sanctioned action is verbatim relocation,
174
+ which you have already done. Being under the load cap is the goal, not the
175
+ hook's smaller aspirational number.
176
+
94
177
  ## Common Mistakes
95
178
 
96
179
  - **Normalising the deploy table to one SHA "because that's probably
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  name: verify-against-plan
3
- description: "Use after implementation is complete to verify the code covers every requirement from the plan / design spec - runs alongside code-review as the second pass in the Code Review Protocol. Outputs structured findings - missing scope, unmet acceptance criteria, silent deviations from the spec, newly-introduced surprises."
3
+ description: "Use after implementation is complete to verify the code covers every requirement from the plan / design spec - runs alongside code-review as the second pass in the Code Review Protocol. Takes an optional design= input so requirements that only the design doc states are checked too. Outputs structured findings - missing scope, unmet acceptance criteria, silent deviations from the spec, newly-introduced surprises."
4
4
  ---
5
5
 
6
6
  # Verify Against Plan
7
7
 
8
8
  ## Inputs
9
9
 
10
- Two inputs are required. Missing either is a pre-flight bail.
10
+ Two inputs are required and one is optional. Missing a required input is a
11
+ pre-flight bail.
11
12
 
12
13
  ### 1. Plan or spec path (required)
13
14
 
@@ -29,6 +30,17 @@ file-structure and acceptance-criteria anchors the skill compares against.
29
30
  - **Worktree path** — infer the branch and diff from the worktree's current
30
31
  state. E.g. `<workspace>/thrum/plugin-skills-slate`.
31
32
 
33
+ ### 3. Design doc path (optional): `design=<path>`
34
+
35
+ A markdown design doc or spec the implementation must also honour. The plan
36
+ stays the authoritative anchor; the design is a second source of requirements.
37
+ Omit it and the skill behaves exactly as before (plan only). When supplied, the
38
+ skill also runs the **Design-doc pass** below.
39
+
40
+ Pass it when the plan implements all or part of a design: `design=<absolute path
41
+ to the design spec>`. A path that is missing, empty or unreadable is a
42
+ pre-flight bail (check 1b), never a silent skip.
43
+
32
44
  ### Context-inferred defaults
33
45
 
34
46
  When the caller supplies no explicit scope, infer:
@@ -45,6 +57,12 @@ When the caller supplies no explicit scope, infer:
45
57
  /verify-against-plan plan=dev-docs/plans/YYYY-MM-DD-topic-plan.md branch=feat/plugin-skills-slate
46
58
  ```
47
59
 
60
+ **With a design doc (requirements only the design states are checked too):**
61
+
62
+ ```text
63
+ /verify-against-plan plan=<plan file> design=<design file> branch=feat/plugin-skills-slate
64
+ ```
65
+
48
66
  **Context-inferred (from current worktree):**
49
67
 
50
68
  ```text
@@ -62,6 +80,11 @@ clear error — do not proceed with partial inputs.
62
80
  file is non-empty markdown. Error message on failure names the path and the
63
81
  issue (missing / empty / unreadable).
64
82
 
83
+ 1b. **Design doc exists and is readable (only when `design=` is supplied).**
84
+ Same shape as check 1: the path resolves and the file is non-empty markdown,
85
+ and the error names the path and the issue. Unlike the plan, a design doc is
86
+ not required to have a File Structure table or an Acceptance section.
87
+
65
88
  2. **Implementation scope resolves to a non-empty diff.** For branch or
66
89
  commit-range inputs, `git diff` must produce at least one changed file. An
67
90
  empty diff means either the branch is at merge-base or the range is
@@ -160,10 +183,98 @@ clear error — do not proceed with partial inputs.
160
183
  that the callee package was actually searched and the cost formula uses
161
184
  realistic production scale, not a test-fixture number.
162
185
 
163
- Only when checks 1–3 pass should the comparison pass begin. Checks 4, 5, and 6
186
+ Only when checks 1–3 (and 1b, when `design=` is supplied) pass should the
187
+ comparison pass begin. Checks 4, 5, and 6
164
188
  run alongside and contribute findings; a missing stamp never blocks, and check 6
165
189
  only fires when the diff actually touches a hot root.
166
190
 
191
+ ## Design-doc pass (only when `design=` is supplied)
192
+
193
+ Runs after the plan comparison and reports on the same scale. It exists because
194
+ a design doc can carry a requirement that no plan task mentions, and a plan-only
195
+ review can never see that gap.
196
+
197
+ **1. Extract requirements, mechanically.** Enumerate in DOCUMENT ORDER and number
198
+ them R1..Rn, recording `<design file>:<line>` and the verbatim quote for each.
199
+ Nothing is extracted by judgment; exactly these rules apply:
200
+
201
+ - **Section items.** Under any heading whose title contains (case-insensitive)
202
+ `requirement`, `acceptance`, `success criteria`, `invariant`, `constraint`,
203
+ `non-goal` or `out of scope`, EVERY bullet, numbered item or table row is one
204
+ requirement. The section runs until the next heading of the same or a higher
205
+ level. A nested sub-bullet is its own requirement only if it carries its own
206
+ MUST/SHALL; otherwise it belongs to its parent item.
207
+ - **Standalone MUST sentences.** Outside those sections, every sentence that
208
+ contains `MUST`, `MUST NOT`, `SHALL` or `SHALL NOT` in capitals is one
209
+ requirement.
210
+ - **Non-goals.** Items under `non-goal` / `out of scope` headings are NEGATIVE
211
+ requirements: the diff must not implement them.
212
+ - **Skipped, exactly:** everything else — background, motivation, alternatives
213
+ considered, open questions, examples, and lowercase "must"/"shall"/"should"
214
+ prose.
215
+
216
+ If the design has no extracted requirement, say so in one informational line
217
+ ("design supplied but no extractable requirements") and continue plan-only;
218
+ never report that as design coverage. Let N be the number extracted.
219
+
220
+ **2. Scope them, mechanically.** A requirement Rk is CHECKED if and only if
221
+ EITHER of these holds; otherwise it is OUTSIDE:
222
+
223
+ - **(a) Name match.** Build the set of names from (i) every path segment and
224
+ backticked token in the plan's File Structure table and (ii) every path
225
+ segment of the diff's changed paths and every identifier on its added or
226
+ removed lines. Drop generic names (`internal`, `src`, `cmd`, `pkg`, `lib`,
227
+ `docs`, `test`, `tests`, `main`, `index`, `util`, `utils`, `common`) and names
228
+ shorter than 4 characters. Rk is CHECKED when its text contains any remaining
229
+ name as a whole word, case-insensitively (`export` matches `export/handler.go`;
230
+ it does not match `exports`).
231
+ - **(b) Cited heading.** Rk sits under a design heading whose exact title also
232
+ appears verbatim in the plan.
233
+
234
+ Requirements that are OUTSIDE are never flagged; they are counted so silence
235
+ cannot pass for coverage, and C + O must equal N:
236
+
237
+ ```text
238
+ **Design scope:** <N> extracted: <C> checked, <O> outside this plan's scope, not checked
239
+ ```
240
+
241
+ A checked Rk that the plan explicitly defers or excludes is not a finding; cite
242
+ the plan's exclusion instead.
243
+
244
+ **3. Compare.** Each checked Rk is MET, ABSENT or DEVIATED against the diff and
245
+ the branch-HEAD code (a requirement already satisfied by unchanged code is MET;
246
+ cite where). An Rk that a plan item also covers is reported once, under the plan
247
+ finding.
248
+
249
+ **4. Report.** Same four fields and the same BLOCKING / IMPORTANT / MINOR
250
+ buckets; the first field is labelled `Design reference` instead of
251
+ `Plan reference`, so consolidation is unchanged:
252
+
253
+ ```markdown
254
+ ### BLOCKING #2 — <short descriptor>
255
+
256
+ - **Design reference:** <design file>:<line> — "<verbatim requirement>"
257
+ - **Implementation state:** <code path>:<line> or "absent"
258
+ - **Why it matters:** <the design requirement or invariant this ties back to>
259
+ - **Suggested resolution:** <add the missing code, or flag the design/plan gap for the coordinator>
260
+ ```
261
+
262
+ Severity: an unmet MUST, acceptance criterion or stated invariant is BLOCKING; a
263
+ silent deviation (different name, shape or behavior) or a violated non-goal is
264
+ IMPORTANT; a documentation or reference gap is MINOR.
265
+
266
+ **Example.** Design line 9 reads "The export endpoint MUST reject request bodies
267
+ over 1 MiB with HTTP 413." The plan lists only the audit-log task and its File
268
+ Structure names `export/handler.go` and `audit/audit.go`; the diff adds only
269
+ audit logging. Plan-only, nothing is flagged. With `design=`, that requirement is
270
+ extracted (it sits under a Requirements heading), CHECKED by name match (`export`
271
+ is a name from the File Structure and the diff), ABSENT from the code, and
272
+ reported as a BLOCKING finding quoting design line 9 with implementation state
273
+ `absent`. A second requirement in the same design, "The billing service MUST
274
+ retry failed charges three times", contains no name from the plan or the diff, so
275
+ it is OUTSIDE: counted in the `Design scope` line, never flagged, even though it
276
+ is a MUST that the diff does not implement.
277
+
167
278
  ## Output format
168
279
 
169
280
  Findings go to stdout in this exact shape so the coordinator can consolidate the
@@ -173,6 +284,8 @@ dual-review batch without reformatting.
173
284
  ## Verify-Against-Plan Findings
174
285
 
175
286
  **Plan:** <path> **Implementation:** <branch> (<N> commits, <M> files changed)
287
+ **Design:** <path> (only when design= was supplied)
288
+ **Design scope:** <N> extracted: <C> checked, <O> outside this plan's scope, not checked (only when design= was supplied)
176
289
  **Summary:** <N> BLOCKING, <N> IMPORTANT, <N> MINOR
177
290
 
178
291
  ---
@@ -303,7 +416,8 @@ not insubordination.
303
416
 
304
417
  ## Scope discipline
305
418
 
306
- This skill has ONE job: does the code match what the plan said it would do?
419
+ This skill has ONE job: does the code match what the plan (and the design doc,
420
+ when `design=` is supplied) said it would do?
307
421
 
308
422
  It does NOT:
309
423
 
@@ -316,7 +430,8 @@ It does NOT:
316
430
  branch.
317
431
  - **Review the plan itself** — pre-implementation plan review is handled by
318
432
  `superpowers:writing-plans` (built-in reviewer). If the plan is wrong, that's
319
- a separate workflow.
433
+ a separate workflow. The same holds for the design doc: it is a source to
434
+ check the code against, not something this skill critiques.
320
435
 
321
436
  If a finding would belong in any of the above categories, omit it from
322
437
  verify-against-plan output. Coordinator will pick up quality / security /
@@ -82,3 +82,21 @@ lives in the domain's scanner, never in agent prose.
82
82
  The watcher's config lives at `.thrum/agents/<id>/` at the repo root
83
83
  (resolve via `.thrum/redirect` from a worktree) — see the role preamble for
84
84
  the per-wake read. It runs as a `scheduled_agent` job.
85
+
86
+ ## Escalation
87
+
88
+ ### 🔴 HUMAN-NEEDED escalation goes to the owner by message, never pane-only
89
+
90
+ When you need a human decision, resolve the owner from CONFIG — never hardcode
91
+ a name — and deliver it as a message, not pane text:
92
+
93
+ OWNER=$(jq -r '.permission_supervisors[] | select(startswith("@user:"))' .thrum/config.json | sed 's/^@//' | head -1)
94
+ thrum send --to "$OWNER" --stdin <<'EOF'
95
+ [ESCALATION] <what needs a decision>: <why>. Action needed: <what>.
96
+ EOF
97
+
98
+ Writing the escalation only into your own pane is not delivery — nobody reads
99
+ watcher panes. If you get no response within 15 minutes, resend and verify
100
+ delivery (`thrum sent --unread`) before assuming it was seen. SAFE recognized
101
+ modals stay approver-of-record and are approved directly, unchanged — this
102
+ rule applies only to escalations that need a human decision.
@@ -58,6 +58,11 @@ shorten data.
58
58
  No issue IDs, no dates, no agent names, no measurements in customer-facing files.
59
59
  The tracker references the change; the change never references the tracker.
60
60
 
61
+ ## A directive has no author and no history
62
+
63
+ State the rule; never name who ruled it. Never show a before-and-after — the
64
+ previous wording is deleted, not exhibited.
65
+
61
66
  ## A skill is generic — never specific to the repo you edit from
62
67
 
63
68
  Write the rule so it holds for any repo running the skill. No repo names, config
package/dist/installer.js CHANGED
@@ -188,7 +188,7 @@ export function readFilePatternFor(filePath) {
188
188
  }
189
189
  // loadAllowlist reads the canonical allowlist JSON asset bundled with this
190
190
  // plugin (opencode-plugin/assets/thrum_allowlist.json — a byte-for-byte
191
- // copy of internal/permissions/thrum_allowlist.json, kept in sync by
191
+ // copy of the Go-owned canonical allowlist source, kept in sync by
192
192
  // scripts/sync-skills.sh's sync_opencode step; see that script's comment
193
193
  // for why a copy is needed instead of a live cross-language import).
194
194
  export function loadAllowlist(assetPath = ALLOWLIST_ASSET_PATH) {
@@ -261,9 +261,9 @@ export function mergePermissions(config, allowlist, extraReadPatterns = [], home
261
261
  // coordinator/watcher operational artifacts. This is a DELIBERATE
262
262
  // exception, not a template for widening elsewhere: it grants READ/ACCESS
263
263
  // ONLY — never execute, never shell-interpolation, never a sibling root
264
- // like /private or /private/tmpfoo, never write/delete. See
265
- // internal/permissions/thrum_allowlist.json's own "OWNER-AUTHORIZED
266
- // EXCEPTION" comment for the full ruling this quotes from. Rendered
264
+ // like /private or /private/tmpfoo, never write/delete. See the canonical
265
+ // allowlist source's own "OWNER-AUTHORIZED EXCEPTION" comment for the full
266
+ // ruling this quotes from. Rendered
267
267
  // through the same readPatternFor() glob as canonical read_paths (e.g.
268
268
  // "/private/tmp" -> "/private/tmp/**") so it goes through the identical
269
269
  // preserveBareScalarAsDefault-aware merge machinery above — never a
@@ -145,10 +145,10 @@ async function buildDenyByDefaultFixtureConfig() {
145
145
  return fixture;
146
146
  }
147
147
  // ─── Representative acceptance-criteria commands ───────────────────────────
148
- // Mirrors internal/permissions/thrum_allowlist.json's command_patterns
149
- // (read-only reference — see that file's own comment for the canonical
150
- // list). These are the finding-6 acceptance commands, a representative
151
- // subset of the full canonical list.
148
+ // Mirrors the canonical allowlist source's command_patterns (read-only
149
+ // reference — see that file's own comment for the canonical list). These
150
+ // are the finding-6 acceptance commands, a representative subset of the
151
+ // full canonical list.
152
152
  const POSITIVE_COMMANDS = [
153
153
  "thrum inbox",
154
154
  "thrum send",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-thrum-pro",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Thrum multi-agent coordination plugin for Open Code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",