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.
- package/assets/commands/thrum-_compact-runtime-commands.md +61 -0
- package/assets/commands/thrum-_snapshot-protocol.md +17 -6
- package/assets/commands/thrum-_stamp-protocol.md +9 -2
- package/assets/commands/thrum-compact-extended.md +352 -0
- package/assets/commands/thrum-compact.md +300 -0
- package/assets/commands/thrum-inbox.md +1 -1
- package/assets/commands/thrum-restart-extended.md +84 -11
- package/assets/commands/thrum-restart.md +105 -13
- package/assets/commands/thrum-send.md +4 -1
- package/assets/commands/thrum-sleep-extended.md +67 -46
- package/assets/commands/thrum-sleep.md +67 -58
- package/assets/skills/adversarial-critique/SKILL.md +5 -8
- package/assets/skills/brainstorm-steward/SKILL.md +65 -11
- package/assets/skills/brainstormer-staying-current/SKILL.md +1 -4
- package/assets/skills/choosing-subagent-models/SKILL.md +14 -0
- package/assets/skills/conversation-only-context/SKILL.md +11 -11
- package/assets/skills/conversation-only-context/resources/driver-subagent-pattern.md +1 -1
- package/assets/skills/conversation-only-context/resources/remote-environment-scenarios.md +1 -1
- package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +98 -6
- package/assets/skills/coordinator-context-monitoring/SKILL.md +29 -19
- package/assets/skills/coordinator-deploying-a-node/SKILL.md +124 -0
- package/assets/skills/coordinator-dispatching-work/SKILL.md +24 -11
- package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +62 -20
- package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +6 -6
- package/assets/skills/coordinator-merging-code/SKILL.md +25 -11
- package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +28 -7
- package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +3 -3
- package/assets/skills/coordinator-rolling-out-to-nodes/SKILL.md +92 -0
- package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +3 -3
- package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +21 -20
- package/assets/skills/coordinator-running-review-cycles/SKILL.md +15 -4
- package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +0 -2
- package/assets/skills/efficient-multi-agent-research/SKILL.md +3 -3
- package/assets/skills/implementer-orchestrating-execution/SKILL.md +11 -14
- package/assets/skills/orchestrate/SKILL.md +47 -35
- package/assets/skills/persistent-watcher-archetype/SKILL.md +172 -25
- package/assets/skills/persistent-watcher-archetype/resources/capture_archive.py +618 -0
- package/assets/skills/persistent-watcher-archetype/resources/jev_watcher_filter.py +837 -0
- package/assets/skills/persistent-watcher-archetype/resources/roster-watch.sh +1140 -0
- package/assets/skills/persistent-watcher-archetype/resources/roster-watch_test.sh +291 -0
- package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +2 -2
- package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +20 -8
- package/assets/skills/prime-agent/SKILL.md +10 -0
- package/assets/skills/project-setup/SKILL.md +22 -16
- package/assets/skills/project-setup/resources/implementation-agent.md +56 -35
- package/assets/skills/researcher-investigating/SKILL.md +2 -5
- package/assets/skills/thrum/SKILL.md +14 -7
- package/assets/skills/thrum/resources/ANTI_PATTERNS.md +16 -18
- package/assets/skills/thrum/resources/CLI_REFERENCE.md +17 -11
- package/assets/skills/thrum/resources/LISTENER_PATTERN.md +9 -3
- package/assets/skills/thrum/resources/MESSAGING.md +18 -11
- package/assets/skills/thrum/resources/WORKTREES.md +1 -1
- package/assets/skills/thrum-memory/SKILL.md +1 -1
- package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +1 -1
- package/assets/skills/update-queue/SKILL.md +6 -8
- package/assets/skills/using-the-dashboard/SKILL.md +30 -0
- package/assets/skills/using-the-queue/SKILL.md +76 -5
- package/assets/skills/using-thrum-state/SKILL.md +83 -0
- package/assets/skills/verify-against-plan/SKILL.md +120 -5
- package/assets/skills/watcher-archetype/SKILL.md +18 -0
- package/assets/skills/writing-durable-markdown/SKILL.md +5 -0
- package/dist/installer.js +4 -4
- package/dist/permission-fixture.integration.test.js +4 -4
- package/package.json +1 -1
- package/assets/skills/coordinator-deploying-the-fleet/SKILL.md +0 -396
- package/dist/allowlist-sync.test.d.ts +0 -1
- 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
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
266
|
-
//
|
|
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
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
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",
|