opencode-thrum-pro 0.1.7 → 0.1.9

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 (37) hide show
  1. package/assets/commands/thrum-restart.md +1 -1
  2. package/assets/skills/brainstorm-queue-aware/SKILL.md +1 -1
  3. package/assets/skills/brainstorm-steward/SKILL.md +3 -3
  4. package/assets/skills/choosing-subagent-models/SKILL.md +14 -21
  5. package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +11 -10
  6. package/assets/skills/coordinator-context-monitoring/SKILL.md +11 -12
  7. package/assets/skills/coordinator-deploying-a-node/SKILL.md +3 -1
  8. package/assets/skills/coordinator-dispatching-work/SKILL.md +3 -1
  9. package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +6 -6
  10. package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +9 -1
  11. package/assets/skills/coordinator-merging-code/SKILL.md +5 -5
  12. package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +3 -3
  13. package/assets/skills/coordinator-rolling-out-to-nodes/SKILL.md +1 -0
  14. package/assets/skills/efficient-multi-agent-research/SKILL.md +2 -2
  15. package/assets/skills/memory-audit-recover/SKILL.md +7 -5
  16. package/assets/skills/memory-curate/SKILL.md +2 -2
  17. package/assets/skills/memory-maintenance/SKILL.md +2 -0
  18. package/assets/skills/orchestrate/SKILL.md +1 -1
  19. package/assets/skills/persistent-watcher-archetype/SKILL.md +2 -2
  20. package/assets/skills/persistent-watcher-archetype/resources/jev_watcher_filter.py +5 -5
  21. package/assets/skills/persistent-watcher-archetype/resources/roster-watch.sh +5 -5
  22. package/assets/skills/persistent-watcher-archetype/resources/roster-watch_test.sh +2 -2
  23. package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +1 -1
  24. package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +3 -3
  25. package/assets/skills/project-philosophy/SKILL.md +1 -1
  26. package/assets/skills/project-setup/SKILL.md +2 -2
  27. package/assets/skills/researcher-maintaining-memory/SKILL.md +2 -0
  28. package/assets/skills/thrum/SKILL.md +9 -4
  29. package/assets/skills/thrum/resources/CLI_REFERENCE.md +32 -27
  30. package/assets/skills/thrum-memory/SKILL.md +1 -1
  31. package/assets/skills/using-monitors/SKILL.md +198 -0
  32. package/assets/skills/using-the-dashboard/SKILL.md +3 -13
  33. package/assets/skills/using-the-queue/SKILL.md +2 -3
  34. package/assets/skills/using-thrum-state/SKILL.md +1 -1
  35. package/dist/hooks/block-sync-cd.js +1 -1
  36. package/dist/hooks/block-sync-cd.test.js +1 -1
  37. package/package.json +1 -1
@@ -361,7 +361,7 @@ Every archive already self-verifies: `sessionarchive.Archive` computes a
361
361
  SHA-256 checksum of the snapshot content it read *before* the move, then
362
362
  re-reads the file it just wrote at the destination and confirms the
363
363
  checksums match (`sessionarchive.VerifyArchivedIdentity` /
364
- `ChecksumContent` — see `internal/daemon/sessionarchive/verify.go`) before
364
+ `ChecksumContent` in the daemon's session-archive package) before
365
365
  reporting success. A destination that exists but doesn't hash-match its
366
366
  own pre-move content is treated as a hard failure of the archive call, not
367
367
  a warning. This is what actually proves "the file at
@@ -32,7 +32,7 @@ answer," do this instead:
32
32
  clear question, plus 2-3 concrete options (A/B/C) so the Steward has
33
33
  something to decide between, not an open-ended essay prompt.
34
34
  2. Send it as a `NEED` to your steward, per the NEED/ANSWER convention
35
- (`dev-docs/specs/2026-08-24-brainstorm-need-answer-convention.md`):
35
+ (the NEED/ANSWER convention spec in the project's design docs):
36
36
 
37
37
  ```bash
38
38
  thrum send --to @<steward> --stdin <<'EOF'
@@ -72,7 +72,7 @@ thread:
72
72
  - **Push — inbox scan.** Each cycle, read your inbox (`thrum inbox`) for
73
73
  messages from a roster brainstormer whose first line matches
74
74
  `NEED [<topic>]:` (the convention from
75
- `dev-docs/specs/2026-08-24-brainstorm-need-answer-convention.md`). Read
75
+ the NEED/ANSWER convention spec in the project's design docs). Read
76
76
  the topic, question, and options straight from the prose — there is no
77
77
  structured payload to parse; you are a judgment agent reading a message
78
78
  the way you'd read any message. The sender identity tells you which
@@ -242,9 +242,9 @@ state as soon as the brainstormer advances past it.
242
242
 
243
243
  ## Reuse note
244
244
 
245
- `dev-docs/templates/brainstorm-steward-watch_params.json` is the canonical
245
+ The brainstorm-steward watch-params template in the project's design docs is the canonical
246
246
  starting config for a new Brainstorm Steward — it uses the schema documented
247
- in `dev-docs/specs/2026-07-22-watch-params-schema.md` unchanged, with an
247
+ in the watch-params schema spec unchanged, with an
248
248
  empty `roster` to be populated as brainstormers are launched under it. Its
249
249
  `parent` field ships pre-filled with this fleet's own escalation target —
250
250
  when copying the template to a different fleet or box, replace that value
@@ -36,14 +36,8 @@ Every subagent you spawn MUST pass an explicit, resolved `model:` (plus effort
36
36
  where the runtime supports it). Omitting it runs the subagent on YOUR model —
37
37
  the single biggest avoidable cost leak in agent work.
38
38
 
39
- **The mechanical tier never resolves to a below-floor model, for any runtime.**
40
- On Claude specifically that means **Haiku is banned entirely** — there is no
41
- mechanical-task carve-out that reaches for it; lint runs, grep-and-collect,
42
- file maps, config edits, and all other "simple" work dispatch at the
43
- mechanical tier's Claude resolution (sonnet-low), not Haiku. Never select
44
- Haiku on own judgment on Claude — use the mechanical tier's resolved value
45
- instead. Other runtimes floor at their own mechanical-tier value
46
- (`RuntimePreset.TierModels`), not at a Claude name.
39
+ Haiku 5.5 is eligible for summarization and mechanical work.
40
+ Read the effective model and effort at use from `runtime.role_models`.
47
41
 
48
42
  ### Read the tiers at the moment of use
49
43
 
@@ -75,9 +69,10 @@ same tier from your own runtime's preset (`RuntimePreset.TierModels`) or
75
69
  | **Sub-agent** (investigation, grep, mechanical) | `mechanical` | **`sonnet` / low** |
76
70
 
77
71
  - **`mechanical` tier (sub-agents ONLY)** — investigation, grep-and-collect,
78
- file maps, lint runs, mechanical tasks. Resolves to `sonnet` @ low effort on
79
- Claude. This is the floor for a *sub-agent*, NOT for an implementer or a
80
- reviewer, and NOT the tier a non-Claude runtime should read as its default.
72
+ file maps, lint runs, mechanical tasks. Read the effective model and effort
73
+ at use from `runtime.role_models`. For other runtimes, use their own
74
+ mechanical-tier value (`RuntimePreset.TierModels`), not a Claude name.
75
+ The mechanical tier is for sub-agents only, never for an implementer or a reviewer.
81
76
  - **`judgment` tier (implementers, verifiers, reviewers)** — resolves to
82
77
  `sonnet` @ MEDIUM effort on Claude. Not low. A reviewer on low effort is a
83
78
  rubber stamp with extra steps, and rubber-stamped reviews are how a merge
@@ -159,9 +154,8 @@ reviewer "from memory", and never dispatch one at the mechanical tier.
159
154
  **AND VERIFY, DO NOT ASSERT:** any claim about what a tool does or does not expose
160
155
  must be checked against the schema in front of you.
161
156
 
162
- Never select the `orchestration` tier or a below-floor model on your own
163
- judgment — use the `judgment` tier's resolved value for your runtime, at the
164
- floor of that tier's lowest effort.
157
+ Never select the `orchestration` tier on your own judgment — use the
158
+ `judgment` tier's configured value from `runtime.role_models`.
165
159
 
166
160
  ## Where the tier-model mapping lives
167
161
 
@@ -238,9 +232,8 @@ that stops at depth 1 is absent where the work happens.
238
232
  work, wait for the completion notification.
239
233
  - Every sub-agent YOU spawn gets an explicit `model:`, resolved for your
240
234
  runtime's tier — `mechanical` (rote/find-replace) or `judgment` (real
241
- analysis). On Claude that's sonnet-low / sonnet-medium and HAIKU IS BANNED;
242
- on another runtime, use that runtime's own resolved row. The
243
- `orchestration` tier is never yours to spawn.
235
+ analysis). Read the effective model and effort from `runtime.role_models` at
236
+ use. The `orchestration` tier is never yours to spawn.
244
237
  - READ-ONLY git outside your own worktree. NEVER `checkout`/`reset`/`restore`/
245
238
  `stash`/`clean` in ANY directory — `stash` is one shared stack across all
246
239
  worktrees and the shared tree holds live agents' uncommitted state.
@@ -252,10 +245,10 @@ signal this condition produces.
252
245
 
253
246
  ### Cheap subagents → fan out, don't pile up
254
247
 
255
- Because `mechanical`-tier subagents are cheap (sonnet-low on Claude; the
256
- mechanical-tier row for your runtime otherwise), prefer MANY small parallel subagents
257
- over one subagent handed a pile of tasks. When a research or investigation task
258
- has independent parts, partition it and dispatch the parts in parallel (use the
248
+ Because Haiku 5.5 supports summarization and mechanical sub-agent work, prefer
249
+ MANY small parallel subagents over one agent handed a pile of tasks. Read the
250
+ effective model and effort from `runtime.role_models` at dispatch. When research or
251
+ investigation has independent parts, partition them in parallel (use the
259
252
  `efficient-multi-agent-research` skill) — smaller scopes are cheaper, run
260
253
  concurrently (faster), and keep each subagent's context tight. One subagent
261
254
  given ten tasks is the anti-pattern.
@@ -176,11 +176,10 @@ salvaged files distinct names and land the salvage somewhere durable (the main r
176
176
 
177
177
  ## 🔴 The reap is worktree-removal-only — and TEST every step on ONE item first
178
178
 
179
- **`thrum agent delete --force` is REFUSED by the CAS guard on a stale agent with an empty
180
- `agent_pid_start_time`** (*"expected-state premise is required … refusing rather than treating
181
- an empty/never-read premise as a match"*). `--force` does NOT bypass it; the guard is
182
- correct. So the reap that actually frees load is worktree removal — but salvage BEFORE you remove,
183
- then remove PLAIN (no `--force`):
179
+ **`thrum agent delete` refuses an agent whose linked worktree still exists** (and any agent
180
+ with a live pid/tmux process, and coordinators); `--force` does NOT bypass these guards. So
181
+ the reap that frees load is worktree removal first — salvage BEFORE you remove, then remove
182
+ PLAIN (no `--force`), then `thrum agent delete`:
184
183
 
185
184
  1. Enumerate untracked+ignored `.thrum` content — BOTH flags, one flag alone misses a class:
186
185
  `git status --untracked-files=all --ignored -- .thrum`
@@ -200,9 +199,10 @@ then remove PLAIN (no `--force`):
200
199
  mistake it for protecting the ignored-file class. (Not a pipe — a pipe returns the last
201
200
  command's rc, not `git`'s.)
202
201
 
203
- The delete (`thrum agent delete`) best-effort-fails and leaves a **benign orphan DB row**,
204
- which you LEAVE (filtered at query per the fleet ruling; never `agent cleanup --force` to
205
- tidy them).
202
+ Then REAP the agent row with `thrum agent delete <agent>` once the worktree is gone and no
203
+ process is live: orphan rows must be reaped, not left. Never use `thrum agent cleanup
204
+ --force` (it can flag live fleet agents). A refused reap on a dead, worktree-less agent is a
205
+ bug: report it, do not route around it.
206
206
 
207
207
  ⚠️ **After a large reap you may see a `DeadAgentSweeper` skip alert + elevated write-RPC
208
208
  latency — do NOT assume the reap caused it.** This box carries a standing single-writer
@@ -302,8 +302,9 @@ RSS-MB.
302
302
  list AND disk), not just the exit code.
303
303
  - **Rank candidates by last-message CONTENT, not elapsed time** — your own
304
304
  broadcasts reset every recipient's idle clock.
305
- - **`--force` on agent-delete does NOT bypass the CAS guard** on a stale agent — the
306
- reap that frees load is worktree removal, not the delete.
305
+ - **`--force` on agent-delete does NOT bypass its guards** — the worktree must go first
306
+ (teardown with salvage or plain `git worktree remove`), then `thrum agent delete` reaps
307
+ the row.
307
308
  - **Reap order is `thrum tmux kill <session>` FIRST, THEN `git worktree remove`** —
308
309
  removing the worktree alone leaves the agent's tmux session alive with a
309
310
  now-deleted cwd, which the fleet flags as recurring "cwd drift" and whose runtime
@@ -30,13 +30,13 @@ What's forbidden is a script that bypasses this skill and fires
30
30
 
31
31
  This skill's tier ladder (Steps 3–5) governs how the coordinator restarts
32
32
  **other** agents. When the **coordinator restarts ITSELF**, a different rule
33
- applies and it is absolute — but D5 (owner-ruled)
33
+ applies and it is absolute — but the current ruling
34
34
  REVERSES which command that is:
35
35
 
36
36
  > **The coordinator ALWAYS uses the EXTENDED snapshot grade for itself — never
37
37
  > the standard/compact form. Which command carries that grade is
38
38
  > runtime-directed: `/thrum:compact-extended` on any runtime with a verified
39
- > compact-equivalent skill (D6, the default), `/thrum:restart-extended` ONLY
39
+ > compact-equivalent skill (the default), `/thrum:restart-extended` ONLY
40
40
  > as the fallback on a runtime without one (today: muse, copilot).**
41
41
 
42
42
  This is a REVERSAL of the prior absolute rule ("ALWAYS restart-extended, NEVER
@@ -62,7 +62,7 @@ Practical trigger points for coordinator self-restart/compact:
62
62
  In every one of these: run `/thrum:compact-extended` if the coordinator's
63
63
  runtime has a compact equivalent (the default); `/thrum:restart-extended` only
64
64
  if it does not. (The role-rule `coord-always-restart-extended` — `Coordinator
65
- ALWAYS uses /thrum:restart-extended` — is RETIRED as of D5; see
65
+ ALWAYS uses /thrum:restart-extended` — is RETIRED; see
66
66
  `thrum memory` for its retirement record. This skill is the always-loaded home
67
67
  so the CURRENT rule survives even when memory isn't consulted.)
68
68
 
@@ -163,7 +163,7 @@ is a single monitor for all lenses in v1, not one per lens.
163
163
  | `idle_mid_task` | ON | L2 — 30-min idle-with-open-task detection (bead cross-ref). RECOMMEND `/thrum:sleep-extended`; a no-task variant REPORTs `idle-no-task` at lower priority. |
164
164
  | `snapshot_awaiting_restart` | ON | L3 — pane text + fresh-snapshot two-signal heuristic. RECOMMEND `thrum tmux restart <agent>` (never autonomous). |
165
165
  | `blocked_on_human_modal` | ON | L4 — detects spend-limit/permission/consent modal prompts. DETECTION ONLY, never auto-answers; routes into the L5 ledger. |
166
- | `pending_human_ledger` | ON | L5 — flat JSONL ledger of items awaiting a human. EXEMPT from D5 backoff — surfaces every tick. `hb_ledger_resolve` (the resolution path) is defined but has zero call sites — nothing marks an entry resolved, so the ledger is append-only-forever, not "until resolved." |
166
+ | `pending_human_ledger` | ON | L5 — flat JSONL ledger of items awaiting a human. EXEMPT from progressive backoff — surfaces every tick. `hb_ledger_resolve` (the resolution path) is defined but has zero call sites — nothing marks an entry resolved, so the ledger is append-only-forever, not "until resolved." |
167
167
  | `waiting_on_coord` | ON | L9 — 21-rule pattern match (folded in from the standalone waiting-on-coord sweep) + warm-hold exemption. RECOMMEND coordinator answer. |
168
168
 
169
169
  ### Default-OFF lenses (E8, flag-gated — NOT YET IMPLEMENTED as of E7)
@@ -183,8 +183,7 @@ produce no output until enabled.
183
183
  ## Configuration
184
184
 
185
185
  The heartbeat system is configured under the `heartbeat` key in
186
- `.thrum/config.json` (Go type: `HeartbeatConfig` in
187
- `internal/config/heartbeat.go`). Example:
186
+ `.thrum/config.json` (Go type: `HeartbeatConfig`). Example:
188
187
 
189
188
  ```json
190
189
  {
@@ -222,10 +221,10 @@ The heartbeat system is configured under the `heartbeat` key in
222
221
  | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
223
222
  | `cadence_minutes_day` | Daytime cadence target (default 30 min). The underlying monitor runs `@every 30m` and self-gates so only busy-fleet daytime ticks fire. |
224
223
  | `cadence_minutes_overnight` | Overnight cadence target (default 60 min). The monitor fires every other `@every 30m` tick overnight (effective 60-min rate). |
225
- | `busy_only_daytime` | When `true`, daytime ticks are skipped unless any agent has `agent_status=working` (D3 busy-signal gate). |
224
+ | `busy_only_daytime` | When `true`, daytime ticks are skipped unless any agent has `agent_status=working` (busy-signal gate). |
226
225
  | `busy_signal` | Algorithm for "is the fleet busy?" Default `working_or_activity_30m`: any agent working OR activity in the last 30 min. |
227
226
  | `overnight_window.tz` | **MUST be an explicit IANA timezone** (e.g. `"America/New_York"`). If unset, the sweep warns and falls back to always-daytime-rate rather than silently guessing. |
228
- | `backoff.start_after` | Number of consecutive unchanged detections of the same problem before D5 progressive backoff begins. |
227
+ | `backoff.start_after` | Number of consecutive unchanged detections of the same problem before progressive backoff begins. |
229
228
  | `backoff.multiplier` | Each backoff step multiplies the surface interval by this factor (default 2×). |
230
229
  | `backoff.floor_every` | The surface interval is capped at this many ticks — the problem is never fully silenced (always surfaces at least once per `floor_every` ticks). |
231
230
  | `lenses.<name>.enabled` | Enable/disable a lens individually. |
@@ -238,8 +237,8 @@ The heartbeat system is configured under the `heartbeat` key in
238
237
  `.thrum/config.json`** to configure the heartbeat. Two gotchas make this
239
238
  non-obvious; both bite silently.
240
239
 
241
- **1. All-or-nothing merge (the sharp footgun).** The loader
242
- (`internal/config/daemon.go`, `LoadThrumConfig`) applies
240
+ **1. All-or-nothing merge (the sharp footgun).** The config
241
+ loader (`LoadThrumConfig`) applies
243
242
  `DefaultHeartbeatConfig()` **only when the stanza is effectively absent**
244
243
  (`schema_version == 0` AND zero lenses):
245
244
 
@@ -254,7 +253,7 @@ There is **no field-level deep merge.** The moment you write a stanza with
254
253
  `cadence_minutes_day: 0`, `enabled: false`, and **all 12 lenses disabled**. So
255
254
  "I'll just set the timezone" with a three-line stanza silently turns the whole
256
255
  sweep off. **Always write the COMPLETE stanza** — copy
257
- `DefaultHeartbeatConfig()` (`internal/config/heartbeat.go`) in full, then change
256
+ `DefaultHeartbeatConfig()` in full, then change
258
257
  only what you need.
259
258
 
260
259
  **2. `overnight_window.tz` MUST be an explicit IANA zone.** Default is `""` (the
@@ -332,7 +331,7 @@ repeals fix-while-warm.
332
331
  restart is **under one turn** once context is meaningfully above the re-prime
333
332
  floor.
334
333
  - **Orchestrators re-prime far cheaper than coordinators, and it is structural:**
335
- `internal/cli/prime_filter.go` gives ONLY `role=="coordinator"` the full
334
+ the prime output filter gives ONLY `role=="coordinator"` the full
336
335
  project-state passthrough. Measured orchestrator re-prime ≈ 30k tokens.
337
336
  - Coordinators keep the ladder below for a stated reason — larger re-entry cost,
338
337
  plus decision-dense context a snapshot cannot faithfully reconstruct.
@@ -48,7 +48,9 @@ If the project has no WAL/SHM, this section is N/A — skip and note.
48
48
 
49
49
  ## 4. Never wrap service start/restart in a blind timeout
50
50
 
51
- Run the project's service restart command (`fleet.deployment.thrum.service.restart_command` or product equivalent) without a blind timeout wrapper. A timeout that kills a migrating restart mid-migration manufactures a half-migrated store.
51
+ 🔴 **On macOS, the restart command is NOT `service.restart_command` — it is `service.mac_restart_command`** (Thrum: `scripts/mac-daemon-restart-via-cron.sh`, run from inside the repo). A daemon restarted from a terminal or agent pane gets its Local Network (TCC) permission attributed to that terminal and is silently denied LAN peer dials (`no route to host`): peers go dark while the node itself looks healthy, and `PPID 1` does NOT prove it was re-parented correctly. Check the node's OS before choosing the command; this has been forgotten on primary repeatedly. After a mac restart, verify the peers on the LAN reconnect, not just the version.
52
+
53
+ Run the project's service restart command (`fleet.deployment.thrum.service.restart_command`, or `service.mac_restart_command` on macOS, or product equivalent) without a blind timeout wrapper. A timeout that kills a migrating restart mid-migration manufactures a half-migrated store.
52
54
 
53
55
  - Adapter flag `service.never_timeout` is true.
54
56
  - Signal-window rules are adapter data: `migration.signal_rules` (`SIGQUIT` fatal-always, `SIGUSR1` fatal-before-socket). Respect them — do not send fatal signals in the forbidden window.
@@ -26,7 +26,9 @@ send. See `coordinator-managing-state-and-lifecycle` § "Destroy an agent before
26
26
  tearing down its worktree" for the canonical sequence (`thrum tmux kill` BEFORE
27
27
  `thrum worktree teardown`). The dispatching skill mentions it here so you find
28
28
  the cross-reference when planning a wave recycle, not just when reading the
29
- lifecycle skill end-to-end.
29
+ lifecycle skill end-to-end. Teardown does not delete the agent row: it stays
30
+ retired until a separate `thrum agent delete <agent>` (after the worktree is
31
+ gone) or the sweeper reaps it.
30
32
 
31
33
  ## Never spawn sub-agents into worktrees where Thrum agents are running
32
34
 
@@ -90,7 +90,7 @@ the one resource it cannot replace and leaves no durable verdict artifact.
90
90
  explicitly in the merge record so the gap is visible rather than invisible.
91
91
 
92
92
  **Verdicts are artifacts:** the runner saves them to
93
- `dev-docs/gate-reports/<ISO-week>/<date>/<gate-slug>/` and commits them BEFORE
93
+ `<gate-reports-dir>/<ISO-week>/<date>/<gate-slug>/` (the project's gate-reports directory) and commits them BEFORE
94
94
  removing any worktree. A verdict that exists only in a message cannot be audited
95
95
  later.
96
96
 
@@ -99,7 +99,7 @@ later.
99
99
  Mandatory checklist handed to the GATE RUNNER (and to any sub-agent it spawns in
100
100
  turn) at dispatch time. Each
101
101
  rule below currently lives only as prose warnings scattered through
102
- `dev-docs/hotpath-gate-efficacy.md` and gets re-learned per session — hand this
102
+ the hotpath-gate efficacy log and gets re-learned per session — hand this
103
103
  list to the sub-agent verbatim at dispatch time instead of relying on it
104
104
  rediscovering these the hard way:
105
105
 
@@ -570,7 +570,7 @@ diff — don't bounce reflexively.
570
570
 
571
571
  ## Efficacy tracking (v1 acceptance requirement)
572
572
 
573
- After every gate run, record the outcome in `dev-docs/hotpath-gate-efficacy.md`
573
+ After every gate run, record the outcome in the hotpath-gate efficacy log
574
574
  (created lazily on first run). This is a durable, committed, grep-queryable log
575
575
  that measures v1's real-world efficacy so we can iterate to v2 with data.
576
576
 
@@ -609,7 +609,7 @@ rows.
609
609
  | `CLEAN-WITH-GATE-FIXES` | Gate found only mechanical bookkeeping gaps, fixed them itself as a GATE-AUTHORED commit, re-ran only the affected checks, merge proceeded |
610
610
 
611
611
  **Retrospective reclassification:** When triaging a new wedge or incident, grep
612
- `dev-docs/hotpath-gate-efficacy.md` for the merged branch/date. If the gate
612
+ the hotpath-gate efficacy log for the merged branch/date. If the gate
613
613
  previously logged `CLEAN` for that branch, append a follow-up line referencing
614
614
  the original entry:
615
615
  `- YYYY-MM-DD | branch=<branch> | RECLASSIFIED: CLEAN->SLIPPED | notes=<what the gate missed + which lens should have caught it>`.
@@ -625,13 +625,13 @@ gaps and lens improvements for the next version.
625
625
  patterns, reference functions, incident prose)
626
626
  - `project-hotpath-gate` — the companion builder skill that generates/reconciles
627
627
  the config (analog of `project-philosophy`)
628
- - `dev-docs/brainstorms/hotpath-merge-gate/` — the locked brainstorm (evidence,
628
+ - the hotpath-merge-gate brainstorm record — the locked brainstorm (evidence,
629
629
  gap analysis, lens details, decisions)
630
630
  - `coordinator-philosophy-merge-gate` — the companion general-structural gate
631
631
  (runs in parallel)
632
632
  - `.thrum/philosophy.md` — canonical source for general anti-patterns (cross-
633
633
  referenced by Lenses 1–2, 6–7 where they overlap)
634
- - `dev-docs/hotpath-gate-efficacy.md` — running efficacy log (created lazily on
634
+ - the hotpath-gate efficacy log — running efficacy log (created lazily on
635
635
  first gate run, appended after each run)
636
636
 
637
637
  <!-- THRUM-GATE: stage=skill next=review -->
@@ -130,9 +130,17 @@ run the two-command destroy sequence **in this order**:
130
130
 
131
131
  ```bash
132
132
  thrum tmux kill <session> # kills tmux + runtime first
133
- thrum worktree teardown <name> # then removes worktree + identity
133
+ thrum worktree teardown <name> # then retires the agent + removes the worktree (salvage first)
134
+ thrum agent delete <agent> # SEPARATE reap, after the worktree is gone
134
135
  ```
135
136
 
137
+ `worktree teardown` does NOT delete the agent: it is preparation for deleting
138
+ one. The row stays `retired` (hidden from default `agent list` / `team` /
139
+ overview / UI; visible with `--include-inactive` / `--phase retired`) until
140
+ `thrum agent delete` or the sweeper reaps it. `agent delete` refuses an agent
141
+ whose linked worktree still exists or whose process (pid/tmux) is live, and
142
+ refuses coordinators; correct order is teardown (with salvage) THEN delete.
143
+
136
144
  Sanity-check: after teardown, `tmux list-sessions` should NOT show the agent's
137
145
  session. If it does, the runtime was never killed and step 1 was skipped.
138
146
 
@@ -169,7 +169,7 @@ The §3 lens gates (hotpath + philosophy) are the judgement half. The per-merge
169
169
  executed half is **scoped to the change**:
170
170
 
171
171
  - Build the merged tree (`go build ./...`).
172
- - 🔴 **RULING 2026-09-29:** the full sharded race suite —
172
+ - 🔴 **RULING:** the full sharded race suite —
173
173
  `scripts/test-run.sh race all` (~15-20 min) — on the **merged tree**, every
174
174
  Pass-3 verdict. This replaces the prior "changed packages only (+ rpc shard
175
175
  sweep)" race scoping below the ruling; a targeted `-run` is still never
@@ -227,7 +227,7 @@ the UI lane, the full tripwire/boxstate reconciliation — is the **periodic
227
227
  full-gate cadence**, an agent run on the dedicated isolation box, and it is its own
228
228
  skill: **`coordinator-full-gate`**. A merge never triggers it, and its merge-base
229
229
  replay is not a per-merge step. Running it per merge is ~2h that on a common-mode-
230
- red trunk fails on the baseline every time. The RULING 2026-09-29 above
230
+ red trunk fails on the baseline every time. The RULING above
231
231
  widens only the **race lane** to full-sharded per merge — it does not pull the
232
232
  other lanes (integration, UI, mutation-check) into the per-merge path.
233
233
 
@@ -288,12 +288,12 @@ agreeing (`rev-parse origin/<branch>` and `ls-remote`). **Assert the branch TIP*
288
288
  ## 7. AFTER THE MERGE — four things, none optional
289
289
 
290
290
  1. **The verdict artifact.** The runner saves verdicts to
291
- `dev-docs/gate-reports/<ISO-week>/<date>/<gate-slug>/` and **commits them
291
+ `<gate-reports-dir>/<ISO-week>/<date>/<gate-slug>/` (the project's gate-reports directory) and **commits them
292
292
  BEFORE removing any worktree.** Save → commit → then destroy. A verdict that
293
293
  exists only in a message cannot be audited later. The artifact must be able to
294
294
  carry a `CLEAN-WITH-GATE-FIXES` outcome, and when one applies it cites the
295
295
  `GATE-AUTHORED` commit SHA.
296
- 2. **The efficacy row**, in `dev-docs/hotpath-gate-efficacy.md` — required for
296
+ 2. **The efficacy row**, in the hotpath-gate efficacy log — required for
297
297
  **every** trigger-dir merge, including `SKIPPED_WITH_EVIDENCE` rows and
298
298
  orchestrator-run merges. The log measures gate **coverage**, not just outcomes.
299
299
  Record `FALSE_POSITIVE=0` explicitly on a clean run so the zero is a
@@ -364,7 +364,7 @@ hides staleness.
364
364
  - `coordinator-hotpath-merge-gate` · `coordinator-philosophy-merge-gate` — the lenses
365
365
  - `coordinator-running-review-cycles` — the dual-review cycle feeding §1
366
366
  - `coordinator-branch-split-on-block` — when a gate blocks one concern of a bundled branch
367
- - `dev-docs/gate-reports/README.md` — the artifact contract
367
+ - the gate-reports README — the artifact contract
368
368
  - `.thrum/hotpath-gate.json` · `.thrum/philosophy.md` — the configs; read these, not prose
369
369
 
370
370
  ## Project-specific rules (already loaded)
@@ -63,7 +63,7 @@ the one resource it cannot replace and leaves no durable verdict artifact.
63
63
  explicitly in the merge record so the gap is visible rather than invisible.
64
64
 
65
65
  **Verdicts are artifacts:** the runner saves them to
66
- `dev-docs/gate-reports/<ISO-week>/<date>/<gate-slug>/` and commits them BEFORE
66
+ `<gate-reports-dir>/<ISO-week>/<date>/<gate-slug>/` (the project's gate-reports directory) and commits them BEFORE
67
67
  removing any worktree. A verdict that exists only in a message cannot be audited
68
68
  later.
69
69
 
@@ -204,7 +204,7 @@ detection is the 10a/10b/10d probe set, not a single grep.
204
204
  ## Lens 10 — Conflated multi-responsibility service (Anti-Pattern #10)
205
205
 
206
206
  This lens uses three sub-procedures from the single-responsibility standing rule
207
- (`dev-docs/decisions/2026-07-09-single-responsibility-standing-rule.md`).
207
+ (the project's single-responsibility decision record).
208
208
 
209
209
  ### 10a — Structural backstop (grep before you trust prose review)
210
210
 
@@ -453,7 +453,7 @@ diff — don't bounce reflexively.
453
453
 
454
454
  - `.thrum/philosophy.md` — canonical source for all anti-patterns and red flags
455
455
  checked by this gate
456
- - `dev-docs/decisions/2026-07-09-single-responsibility-standing-rule.md` — full
456
+ - the single-responsibility decision record — full
457
457
  standing rule with the three-class scope model, enforcement architecture
458
458
  (3a/3b/3c/3d), and worked examples of previously-traced conflated services
459
459
 
@@ -90,3 +90,4 @@ The node's local runbook is `coordinator-deploying-a-node` — reference that sk
90
90
  - If `fleet.deployment.thrum.products` exists (vestigial), STOP — products live only at `fleet.deployment.products`.
91
91
  - If a node reports success by exit code alone without observed-effect verification, treat as not-verified.
92
92
  - Never wrap a service restart in a blind timeout on the node (see node skill `service.never_timeout` and `migration.signal_rules`).
93
+ - 🔴 A **macOS** node (including the build host / primary) restarted with the plain `service.restart_command` instead of `service.mac_restart_command` (Thrum: `scripts/mac-daemon-restart-via-cron.sh`). The daemon loses its Local Network permission and its LAN peers go dark while it looks healthy. Put the mac command in every macOS node's dispatch, and use it yourself when you roll the build host last.
@@ -59,7 +59,7 @@ read-only scoping) are in "Core Pattern" below.
59
59
  **Every prompt MUST scope the read-only restriction around its own output
60
60
  file, in the same breath:** `READ-ONLY EVERYWHERE EXCEPT YOUR OUTPUT FILE —
61
61
  do not modify source, tests, config, git state, or the issue tracker; you
62
- MUST write exactly dev-docs/<topic>/findings_N.md, and that write is
62
+ MUST write exactly <topic-dir>/findings_N.md, and that write is
63
63
  expected and authorized.` Omitting the exception is the most common cause of
64
64
  a fan-out that returns nothing — see Common Mistakes.
65
65
 
@@ -134,7 +134,7 @@ Prefer `cat >> <path> <<'EOF'` to append on later rounds.
134
134
  ```text
135
135
  READ-ONLY EVERYWHERE EXCEPT YOUR OUTPUT FILE.
136
136
  Do not modify source, tests, config, git state, or the issue tracker.
137
- You MUST write your findings to exactly: dev-docs/<topic>/findings_N.md
137
+ You MUST write your findings to exactly: <topic-dir>/findings_N.md
138
138
  Write is fine for creating it. To UPDATE it on a later round, Read it first or
139
139
  append with a heredoc (cat >> <path> <<'EOF' ... EOF) — Write refuses to
140
140
  overwrite a file it has not Read. That single write is expected and authorized.
@@ -44,7 +44,7 @@ monotonic-ish and globally comparable; the larger ULID wins.
44
44
  | Concurrent edits to the SAME field | Larger-ULID event wins. Loser preserved in JSONL. |
45
45
  | Concurrent tombstones | Idempotent. One delete-event wins by ts; status=purged. |
46
46
  | Edit + delete, delete-ULID > edit-ULID | Final `status=purged`. |
47
- | Edit + delete, edit-ULID > delete-ULID AND edit explicitly sets `status=active` | Edit wins → `status=active` (undelete). |
47
+ | Edit + delete, edit-ULID > delete-ULID AND edit explicitly sets `status=active` | Edit wins → `status=active` (undelete; needs a license). |
48
48
  | Edit + delete, edit-ULID > delete-ULID AND edit does NOT touch `status` | Patch applies to body/tags but `status=purged` persists. |
49
49
 
50
50
  LWW is acceptable here because memories aren't a high-write-conflict surface
@@ -94,6 +94,8 @@ Undelete by writing `status=active`:
94
94
  thrum memory edit <id> --status active
95
95
  ```
96
96
 
97
+ Note: `memory edit` (including `--status active` undelete) requires a license (memory.substrate); soft delete (`memory delete`) and reads stay open.
98
+
97
99
  Undelete works regardless of who won the concurrent edit/delete race (see LWW
98
100
  table above). The audit history is intact, so the recovered memory carries its
99
101
  full provenance.
@@ -116,10 +118,10 @@ Type DELETE <id> to confirm: DELETE <id>
116
118
  The exact token (`DELETE <id>`) must be typed back — matches beads'
117
119
  `DESTROY-<prefix>` pattern. There is no `--yes` bypass.
118
120
 
119
- | Mode | Mechanism | Recovery |
120
- | --------------- | -------------------------------------------------------------- | ---------------------------------------- |
121
- | Soft (default) | `memory.delete` → projection `status=purged` | `thrum memory edit <id> --status active` |
122
- | Hard (`--hard`) | `memory.purge_executed` → physical JSONL removal at compaction | Not recoverable post-compaction |
121
+ | Mode | Mechanism | Recovery |
122
+ | --------------- | -------------------------------------------------------------- | ----------------------------------------------------------- |
123
+ | Soft (default) | `memory.delete` → projection `status=purged` | `thrum memory edit <id> --status active` (license required) |
124
+ | Hard (`--hard`) | `memory.purge_executed` → physical JSONL removal at compaction | Not recoverable post-compaction |
123
125
 
124
126
  ## When NOT to hard-delete
125
127
 
@@ -25,7 +25,7 @@ Edge kind is an open string (same hybrid pattern as memory kind). Future edges
25
25
  like `blocks`, `duplicates`, or `parent-child` can be added without schema
26
26
  change.
27
27
 
28
- Add and remove edges:
28
+ Add and remove edges. `memory link` and `memory edit` require a license (memory.substrate); `memory unlink` does not.
29
29
 
30
30
  ```bash
31
31
  thrum memory link <a> supersedes <b>
@@ -59,7 +59,7 @@ thrum memory unlink <new-id> supersedes <old-id>
59
59
  thrum memory edit <old-id> --status active
60
60
  ```
61
61
 
62
- Both steps are required. If you only need to keep the historical link but
62
+ The `--status active` step needs a license; without one the superseded memory stays superseded. Both steps are required. If you only need to keep the historical link but
63
63
  re-surface the old memory, just run the `--status active` edit and leave the
64
64
  edge in place.
65
65
 
@@ -21,6 +21,8 @@ thrum memory edit <id> --title "<updated title>"
21
21
  thrum memory edit <id> --add-tag <new-tag> # appends (repeatable); --rm-tag removes
22
22
  ```
23
23
 
24
+ `memory edit` requires a license (memory.substrate); `delete` and `unlink` do not.
25
+
24
26
  Edits create a **history record** — the daemon retains the prior version. View
25
27
  edit history:
26
28
 
@@ -653,7 +653,7 @@ thrum worktree teardown <name>
653
653
  ```
654
654
 
655
655
  > After set-phase=retired, `thrum team` and the active UI views no longer show
656
- > this agent (sidebar, pickers, badge, kbd-nav). It remains in the Retired tab.
656
+ > this agent (sidebar, pickers, badge, kbd-nav). It remains in the Retired tab (teardown retires, it does not delete the row).
657
657
  > Its messages + runtime transcript persist for audit. `thrum agent delete` is
658
658
  > operator-only (gated to the coordinator) and is NOT an orchestrator tool —
659
659
  > `set-phase retired` is the orchestrator's retirement command for both normal
@@ -131,7 +131,7 @@ The procedure, every time, no shortcuts:
131
131
  another peer's panes read-only regardless of capability.
132
132
 
133
133
  **Worked example** — the standard two-variant Claude Code tool-confirmation
134
- modal (`internal/daemon/permission/patterns.go`, pattern `tool_confirmation`,
134
+ modal (the daemon's permission patterns, pattern `tool_confirmation`,
135
135
  dialect `claude-tui`), captured verbatim:
136
136
 
137
137
  ```
@@ -149,7 +149,7 @@ dialect `claude-tui`), captured verbatim:
149
149
  allow-rule beyond this one command; the modal judgment rubric above is a
150
150
  per-command judgment, not a license to mint blanket permissions. This is
151
151
  pinned by `ApproveKey: "1", // Yes (once) — NEVER "2"` in
152
- `internal/daemon/permission/patterns.go` and tested by
152
+ the daemon's permission patterns and tested by
153
153
  `TestApproveKeyNeverForeverAllow`.
154
154
 
155
155
  ## Your own restart (auto-restart-at-ctx-threshold)
@@ -45,8 +45,8 @@ NOTIFY_CLASSES = VALID_CLASSES - {"ordinary"}
45
45
  REDACTION_TOKEN = "[REDACTED_SECRET]"
46
46
  LOCAL_PATH_REDACTION_TOKEN = "[REDACTED_LOCAL_PATH]"
47
47
  SECRET_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
48
- ("private_key_block", re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----.*?-----END [A-Z0-9 ]*PRIVATE KEY-----", re.S)),
49
- ("env_secret_assignment", re.compile(r"(?im)^\s*(?:export\s+)?[A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PRIVATE[_-]?KEY|AUTH[_-]?KEY)[A-Z0-9_]*\s*=\s*([^\s#]+)")),
48
+ ("private_key_block", re.compile(r"-----BEGIN [0-9A-Z ]*PRIVATE KEY-----.*?-----END [0-9A-Z ]*PRIVATE KEY-----", re.S)),
49
+ ("env_secret_assignment", re.compile(r"(?im)^\s*(?:export\s+)?[0-9A-Z_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PRIVATE[_-]?KEY|AUTH[_-]?KEY)[0-9A-Z_]*\s*=\s*([^\s#]+)")),
50
50
  ("bearer_token", re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+/=-]{16,}")),
51
51
  ("openai_key", re.compile(r"\bsk-[A-Za-z0-9_-]{20,}\b")),
52
52
  ("thrum_key", re.compile(r"\btskey-[A-Za-z0-9_-]{16,}\b")),
@@ -318,7 +318,7 @@ def prompt_payload(state: dict[str, Any], provider: str = TYPESAFE_PROVIDER) ->
318
318
  },
319
319
  }
320
320
  if provider == OPENROUTER_PROVIDER:
321
- payload["user"] = "thrum-roster-watch-filter"
321
+ payload["user"] = "roster-watch-filter"
322
322
  return payload
323
323
 
324
324
 
@@ -462,8 +462,8 @@ def classify_remote(state: dict[str, Any], provider: str, api_key: str, timeout:
462
462
  }
463
463
  if provider == OPENROUTER_PROVIDER:
464
464
  headers.update({
465
- "HTTP-Referer": "https://github.com/falconleon/thrum",
466
- "X-Title": "thrum-roster-watch-filter",
465
+ "HTTP-Referer": "https://github.com/leonletto/thrum-pro.git",
466
+ "X-Title": "roster-watch-filter",
467
467
  })
468
468
  last_error = None
469
469
  last_category = None
@@ -32,7 +32,7 @@
32
32
  # GHOST-TIP DETECTION: Claude Code's TUI renders an unaccepted inline
33
33
  # autocomplete suggestion in the input box using the literal SGR
34
34
  # "dim/faint" escape code (ESC[2m ... ESC[0m) — confirmed by direct byte
35
- # inspection (2026-08-17, a reconciler agent's "Stop the sweep loop
35
+ # inspection (a reconciler agent's "Stop the sweep loop
36
36
  # for now" tip). Real typed/submitted text never carries that code. `thrum
37
37
  # tmux capture` returns plain text with all styling stripped, so a script
38
38
  # parsing ONLY that output cannot tell a live ghost-tip apart from real
@@ -50,7 +50,7 @@
50
50
  #
51
51
  # REDIRECT FIX (the actual bug this reconciliation targets):
52
52
  # `watch_params.json` lives under the SHARED, redirect-resolved `agents/`
53
- # tree (`internal/paths/paths.go` AgentDir: "the agents/ tree is shared,
53
+ # tree (the AgentDir path helper: "the agents/ tree is shared,
54
54
  # not per-worktree"), not under this script's own worktree. Both source
55
55
  # scripts read it as if it were always reachable via a hardcoded/relative
56
56
  # main-repo path (`cd /Users/you/dev/thrum` +
@@ -60,7 +60,7 @@
60
60
  # feature worktree. `resolve_watch_params()` below replaces that implicit
61
61
  # assumption with the same explicit `.thrum/redirect`-following idiom
62
62
  # already used by `scripts/heartbeat-lib.sh:225-232` and
63
- # `scripts/thrum-check-inbox.sh:25-41`.
63
+ # the check-inbox hook script.
64
64
  #
65
65
  # PORTABILITY FIX: both source scripts also hardcoded a single operator's
66
66
  # absolute paths (`cd /Users/you/dev/thrum`,
@@ -105,7 +105,7 @@ set -uo pipefail
105
105
  # ---------------------------------------------------------------------------
106
106
  # Args: worktree_root. Prints the redirect-resolved absolute .thrum/ dir.
107
107
  # Matches the idiom already used by scripts/heartbeat-lib.sh's
108
- # hb_sessions_dir() and scripts/thrum-check-inbox.sh's SPOOL_THRUM
108
+ # hb_sessions_dir() and the check-inbox hook script's SPOOL_THRUM
109
109
  # resolution: no redirect file present (main repo) -> use the local .thrum/
110
110
  # as-is; redirect file present (feature worktree) -> follow it to the main
111
111
  # repo's .thrum/.
@@ -648,7 +648,7 @@ except: print(0)
648
648
  # entry resolves to a real agent_id via `thrum team --offline --json`, and
649
649
  # log ROSTER-CHECK OK/FAIL. Cheap and valuable regardless of execution
650
650
  # model (catches the exact "bare `<box>`-as-hostname" class of stale-
651
- # roster-entry bug watcher-primary hit 2026-07-24..2026-09-06). Runs AFTER
651
+ # roster-entry bug watcher-primary hit). Runs AFTER
652
652
  # the dynamic roster is loaded from watch_params.json, not against a
653
653
  # hardcoded array.
654
654
  # ---------------------------------------------------------------------------
@@ -69,8 +69,8 @@ AGENT="watcher_primary"
69
69
  # Scenario 1 (MANDATORY, cmain-required regression guard): a worktree that
70
70
  # redirects to a main-repo tree. The "roster-editing agent's Edit-tool write"
71
71
  # lands in the REDIRECT TARGET (main-repo) copy -- that's the physically
72
- # correct, shared, redirect-resolved location per internal/paths/paths.go
73
- # AgentDir ("the agents/ tree is shared, not per-worktree"), and it's what
72
+ # correct, shared, redirect-resolved location per the AgentDir path helper
73
+ # ("the agents/ tree is shared, not per-worktree"), and it's what
74
74
  # resolve_watch_params must read. A worktree-LOCAL copy at the naive,
75
75
  # unresolved path is a decoy: it's what a broken script (SCRIPT_DIR-relative,
76
76
  # no redirect-follow) would read instead, and it must be ignored.
@@ -94,7 +94,7 @@ run_ssh() {
94
94
  }
95
95
 
96
96
  # ssh-add retry: if the SSH hop itself fails at the connection layer (not a
97
- # thrum-level error), the caller's identities may need re-adding to the
97
+ # CLI-level error), the caller's identities may need re-adding to the
98
98
  # keychain-backed ssh-agent. No default keys ship here -- this is a
99
99
  # generic plugin script, and baking in one operator's specific key
100
100
  # filenames would be wrong for every other fleet/operator that copies it.
@@ -74,7 +74,7 @@ AGENT_NAME="$(basename -- "${SCRIPT_DIR}")"
74
74
  REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../../.." >/dev/null 2>&1 && pwd)"
75
75
  # Resolve watch_params.json through .thrum/redirect. The
76
76
  # agents/ tree is the SHARED, redirect-resolved (main-repo) tree, not
77
- # per-worktree (see internal/paths/paths.go AgentDir) -- reading it
77
+ # per-worktree (see the AgentDir path helper) -- reading it
78
78
  # straight off SCRIPT_DIR (this copy's own worktree-local location) reads
79
79
  # the wrong, stale file whenever this script is deployed into a feature
80
80
  # worktree that redirects: a roster-editing agent's Edit-tool write lands
@@ -185,7 +185,7 @@ log "roster: ${ROSTER[*]}"
185
185
  #
186
186
  # NOTE ON THE TWO-STEP TOPOLOGY LOOKUP: `thrum state list --kind topology
187
187
  # --json`'s CLI output does NOT include each entry's `.value` (verified
188
- # against cmd/thrum/state.go's stateListEntry struct, which has no Value
188
+ # against the CLI's stateListEntry struct, which has no Value
189
189
  # field, and confirmed live -- `state list --json` returns only
190
190
  # kind/scope/method/as_of/established_by). Only `state get`/`state show`
191
191
  # emit the full entry including `.value`. So topology resolution is
@@ -338,7 +338,7 @@ for agent in "${ROSTER[@]}"; do
338
338
  STATUS="clean"
339
339
  MATCH_NAME=""
340
340
 
341
- # thrum detect exit-code contract (cmd/thrum/detect.go):
341
+ # thrum detect exit-code contract:
342
342
  # exit 0 + matched entry name on stdout -> match
343
343
  # exit 1, silent -> no match
344
344
  # `if VAR=$(...); then` is exempt from `set -e` semantics for this
@@ -231,7 +231,7 @@ undetected or unprovided. Fill in:
231
231
 
232
232
  ### Step 6a: MANDATORY — wire the project's security/threat model into the Decision Framework
233
233
 
234
- If the project has a written threat model (search `docs/security_model/`,
234
+ If the project has a written threat model (search the project's security-model directory,
235
235
  `docs/security*.md`, `SECURITY.md`), the rendered philosophy MUST carry a
236
236
  Decision Framework row pointing at it — and that row MUST NOT be keyed on the
237
237
  word "attacker".
@@ -260,8 +260,8 @@ components, data flow, and interfaces.
260
260
 
261
261
  ### 🔴 Phase 1a — MANDATORY security-frame check (do this BEFORE decomposing)
262
262
 
263
- **Read the project's threat model** (`docs/security_model/master.md` §1 and §1.2
264
- on thrum; otherwise `docs/security*.md` / `SECURITY.md`) **whenever the plan
263
+ **Read the project's threat model** (its security-model document, or
264
+ `docs/security*.md` / `SECURITY.md`) **whenever the plan
265
265
  contains ANY of the following — and note that NONE of them need name an
266
266
  attacker:**
267
267
 
@@ -94,6 +94,8 @@ EOF
94
94
  thrum memory edit <id> --full "@/tmp/memory-verify-body.md"
95
95
  ```
96
96
 
97
+ Re-verifying with `memory edit` needs a license.
98
+
97
99
  ## Staleness check via git diff
98
100
 
99
101
  For research notes that cite specific files, structural staleness review filters
@@ -86,14 +86,19 @@ thrum tmux launch <session> [--runtime <r>] Start runtime in session (REQUIRED
86
86
  ### Monitor Jobs
87
87
 
88
88
  ```bash
89
- thrum monitor add <name> --cmd "..." --on-match "msg" Add a monitored process
90
- thrum monitor list List all monitor jobs
91
- thrum monitor show <name> Show job details and recent matches
89
+ thrum monitor start --name <n> --match <re> --to @agent [--schedule "<cron>"] [--notify-on-success] -- <cmd> [args...]
90
+ thrum monitor list [--all] List monitor jobs (running; --all adds stopped)
91
+ thrum monitor show <name> Show job details
92
+ thrum monitor logs <name> Recent matched lines
93
+ thrum monitor update <name> Change match, schedule, target or debounce in place
92
94
  thrum monitor stop <name> Stop a monitor job
93
- thrum monitor logs <name> Tail job output
95
+ thrum monitor delete <name> Remove a job and free its name
94
96
  thrum monitor restart <name> Restart a stopped job
95
97
  ```
96
98
 
99
+ Reminders, periodic script checks and delivery verification: see the
100
+ `using-monitors` skill.
101
+
97
102
  ### Daemon & Sync
98
103
 
99
104
  ```bash
@@ -255,7 +255,7 @@ Flags:
255
255
  --runtime string Runtime preset: claude, codex, cursor, gemini, opencode, auggie, cli-only
256
256
  --preamble-file string Custom preamble file to compose with default preamble
257
257
  --no-init Skip runtime config generation, just register agent
258
- --force Overwrite existing runtime config files
258
+ --force Force runtime config reinitialization; project instruction files are preserved
259
259
  --dry-run Preview changes without writing files or registering
260
260
  --no-agent-pid Persist agent_pid=0 instead of detecting the runtime ancestor; defers PID claim to first /thrum:prime (used for inline tmux quickstart)
261
261
  ```
@@ -379,6 +379,10 @@ thrum agent delete <name>
379
379
  thrum agent delete coordinator_1B9K --force
380
380
  ```
381
381
 
382
+ Refuses an agent whose linked worktree still exists (run `worktree teardown`
383
+ first), a live pid/tmux process, and coordinators. Order: teardown (with
384
+ salvage), then delete.
385
+
382
386
  Flags:
383
387
 
384
388
  ```text
@@ -858,7 +862,7 @@ thrum tmux cancel <command-id> # Cancel a queued or active comma
858
862
  --intent string Initial work intent description
859
863
  --runtime string Runtime preset: claude, codex, cursor, gemini, opencode, auggie
860
864
  --no-agent Skip agent registration (create session only)
861
- --force Overwrite existing runtime config files
865
+ --force Re-register even if agent exists; kill and recreate an existing session
862
866
  ```
863
867
 
864
868
  Without `--no-agent`, the command errors if `--name`, `--role`, and `--module`
@@ -900,7 +904,7 @@ thrum worktree create <name> --detach # Detached HEAD worktree
900
904
  thrum worktree create <name> \
901
905
  --name <agent> --role <role> --module <mod> # Create worktree + register agent
902
906
  thrum worktree setup <name> # Alias for worktree create
903
- thrum worktree teardown <name> # Remove worktree, keep branch
907
+ thrum worktree teardown <name> # Retire agent + remove worktree, keep branch (does NOT delete the agent; reap with `thrum agent delete` after)
904
908
  thrum worktree teardown <name> --delete-branch # Also delete the worktree's branch
905
909
  thrum worktree list # List worktrees with agent info
906
910
  ```
@@ -939,51 +943,52 @@ later.
939
943
 
940
944
  ## Monitor Jobs
941
945
 
942
- Run a long-lived command, filter output through a regex, and deliver matching
943
- lines as Thrum messages. Jobs persist across daemon restarts. Max 100
944
- concurrent. The command must follow `--`.
946
+ Run a command, filter its output through a regex, and deliver matching lines as
947
+ Thrum messages. Without `--schedule` the command runs continuously and restarts
948
+ with backoff; with `--schedule` it runs once per cron tick. Jobs persist across
949
+ daemon restarts. Max 100 concurrent. The command must follow `--`.
945
950
 
946
951
  ```bash
947
952
  thrum monitor start --name <n> --match <re> --to @agent -- <cmd> [args...]
948
953
  thrum monitor start --name <n> --match <re> --to @agent \
949
- --debounce 120s --env KEY=VALUE -- <cmd>
954
+ --schedule "*/5 * * * *" --notify-on-success --debounce 120s --env KEY=VALUE -- <cmd>
950
955
  thrum monitor list # Running jobs only
951
956
  thrum monitor list --all # Include stopped/dead (<1 week)
952
957
  thrum monitor show <id|name> # Full detail (env values redacted)
953
- thrum monitor stop <id|name> # SIGTERM → 5s → SIGKILL
954
- thrum monitor logs <id|name> # Last 20 matched lines
955
- thrum monitor logs <id|name> -n 50 # Last 50 matched lines
958
+ thrum monitor logs <id|name> -n 50 # Last 50 matched lines (default 20)
959
+ thrum monitor update <id|name> --match <re> # Change match/schedule/target/debounce in place
960
+ thrum monitor stop <id|name> # SIGTERM, 5s, SIGKILL
956
961
  thrum monitor restart <id|name> # Restart dead/stopped monitor
962
+ thrum monitor delete <id|name> # Remove the record and free the name
963
+ thrum monitor adopt <id|name> # Claim a monitor for this daemon
957
964
  ```
958
965
 
959
- Note: `monitor add` is retained as an alias for `monitor start`.
966
+ `monitor add` is an alias for `monitor start`.
960
967
 
961
968
  `monitor start` flags:
962
969
 
963
970
  ```text
964
971
  --name string Unique monitor name (required)
965
- --match string Regex pattern — matching lines trigger a message (required)
972
+ --match string Regex pattern - matching lines trigger a message (required)
966
973
  --to string Target agent or group, e.g. @coordinator (required)
967
- --debounce duration Leading-edge debounce window, minimum 30s (default 60s)
968
- --env strings Environment variable in KEY=VALUE form (repeatable)
974
+ --schedule string 5-field cron expression; runs the command once per tick
975
+ --notify-on-success Deliver a generic [OK] message for a successful run in which no line matched
976
+ --debounce duration Leading-edge debounce window, minimum 30s (default 1m)
977
+ --env stringArray Environment variable in KEY=VALUE form (repeatable)
969
978
  --cwd string Working directory for the command (default: current dir)
970
979
  ```
971
980
 
972
- `monitor list` flags:
973
-
974
- ```text
975
- --all Include stopped/dead monitors younger than one week
976
- ```
981
+ `monitor update` flags: `--match`, `--to`, `--schedule`, `--debounce`.
982
+ `--schedule` changes the cron of a scheduled monitor at any time; setting or
983
+ removing a schedule switches the mode, which is refused while the monitor is
984
+ running and allowed while it is stopped.
977
985
 
978
- `monitor logs` flags:
979
-
980
- ```text
981
- -n, --limit int Max number of matches to return (default 20)
982
- ```
986
+ `monitor logs` flag: `-n, --limit int` (default 20).
983
987
 
984
- Key constraints: local Unix socket only, max line length 2KB (lines truncated),
985
- leading-edge debounce (min 30s), auto-persists across daemon restart, sends a
986
- notify-only message on child exit.
988
+ Key constraints: local Unix socket only, lines over 2KB are truncated with a
989
+ marker, one matching line is delivered per debounce window, and a message is
990
+ sent when the child exits non-zero. See the `using-monitors` skill for
991
+ reminders, script checks and delivery rules.
987
992
 
988
993
  ---
989
994
 
@@ -70,7 +70,7 @@ thrum memory edit <id> --short @newshort.md
70
70
  thrum memory edit <id> --add-tag v0.11 --rm-tag draft
71
71
  ```
72
72
 
73
- Link an edge:
73
+ Link an edge. `memory create`, `edit` and `link` need a license (memory.substrate); reads, `delete`, `purge` and `unlink` do not.
74
74
 
75
75
  ```bash
76
76
  thrum memory link <new-id> supersedes <old-id>
@@ -0,0 +1,198 @@
1
+ ---
2
+ name: using-monitors
3
+ description: "Use when scheduling a reminder, watching a log, or running a periodic check that must reach an agent - thrum monitor start, cron --schedule, --match, --notify-on-success, --debounce, recurring nudge, script check with exit codes, why a monitor message did not arrive, how to verify a monitor delivered."
4
+ ---
5
+
6
+ # Thrum Monitors - Reminders And Script Checks
7
+
8
+ ## Overview
9
+
10
+ A monitor runs a command, keeps the output lines that match a regex, and
11
+ delivers them as Thrum messages to a target: a continuous monitor sends each
12
+ kept line, a scheduled monitor sends all the lines of a run in one message. With
13
+ `--schedule` it runs the command once per cron tick; without it the command runs continuously
14
+ and restarts with backoff when it exits. Monitors persist across daemon
15
+ restarts and run only on the daemon that owns them.
16
+
17
+ Every flag below appears in `thrum monitor start --help`. Re-read the help
18
+ before relying on a flag not listed here.
19
+
20
+ ## Command Surface
21
+
22
+ ```bash
23
+ thrum monitor start --name <n> --match <re> --to @agent [--schedule "<cron>"] \
24
+ [--notify-on-success] [--debounce 60s] [--env K=V] [--cwd <dir>] -- <cmd> [args...]
25
+ thrum monitor list [--all]
26
+ thrum monitor show <name|id>
27
+ thrum monitor logs <name|id> [-n 50]
28
+ thrum monitor update <name|id> [--match <re>] [--to <t>] [--debounce <d>] [--schedule "<cron>"]
29
+ thrum monitor stop <name|id>
30
+ thrum monitor restart <name|id>
31
+ thrum monitor delete <name|id>
32
+ ```
33
+
34
+ - `--name`, `--match` and `--to` are required; the command follows `--`.
35
+ - `--schedule` takes a 5-field cron expression. `update --schedule` changes the
36
+ cron of a scheduled monitor at any time. Setting a first schedule or removing
37
+ it (`--schedule ""`) switches the mode, which is refused while the monitor is
38
+ running and allowed while it is stopped.
39
+ - `--debounce` is leading-edge with a 30s minimum (default 1m).
40
+ - `stop` leaves a stopped record that still holds the name; `delete` frees the
41
+ name. Use a unique name per monitor.
42
+
43
+ ## What Is Delivered
44
+
45
+ Each output line is tested against `--match` on its own. A line is truncated at
46
+ 2048 bytes with a marker appended.
47
+
48
+ A scheduled (`--schedule`) job is a one-shot run, so its exit code is
49
+ authoritative. It delivers ONE message at exit that carries every matched line,
50
+ in order. A continuous job has no exit code per line, so it classifies and
51
+ delivers and classifies each matched line on its own, by its words.
52
+
53
+ | Scheduled job | Delivered as |
54
+ | --------------------------------------------------------------- | --------------------------------------------- |
55
+ | Non-zero exit, or the command failed to start | `[FAIL] <name>: exit code N` with the output tail, then the matched lines |
56
+ | Exit 0, a matched line starts `ERROR:`, `FATAL:` or `FAIL:` | `[FAIL] <name>: matched error pattern` + all matched lines |
57
+ | Exit 0, highest prefix is `WARN:`, `WARNING:` or `ALERT:` | `[WARN] <name>: matched WARNING` + all matched lines |
58
+ | Exit 0, lines matched, no such prefix | All matched lines, in order, verbatim |
59
+ | Exit 0, nothing matched, `--notify-on-success` | `[OK] <name>: succeeded` |
60
+ | Exit 0, nothing matched | Nothing (logged only) |
61
+
62
+ Cadence: the first non-zero scheduled run sends its `[FAIL]` notice; repeats of
63
+ that failure are not sent again until the outcome changes or the re-escalation
64
+ interval (30 minutes) passes. An exit-0 run that matched lines sends its message
65
+ on every run.
66
+
67
+ On a scheduled job only these prefixes, at the very start of a line, label the
68
+ output, with the colon directly after the word: `ERROR:`, `FATAL:` and `FAIL:`
69
+ fail it; `WARN:`, `WARNING:` and `ALERT:` warn. Any other prefix, including
70
+ `ALERT-<NAME>:`, `ERROR :`, `INFO:`, `OK:`, `NOT_OK:` and lowercase forms, is
71
+ neutral. They are read before any wording in the body, and
72
+ the highest one decides the label. Words such as `done`, `ok`, `complete`, `failures` or `error` neither
73
+ drop nor relabel a line, and `WARN: batch not complete` stays a warning.
74
+ `--notify-on-success` adds the generic `[OK]` only for a run in which nothing
75
+ matched; an empty matched line counts as a match and is announced as
76
+ `[matched N empty line(s)]`.
77
+
78
+ The whole message, including its label and framing, never exceeds the daemon's
79
+ configured message-body limit. Matched lines are sent whole while they fit both
80
+ budgets: the in-memory retention budget (each line costs its bytes plus a small
81
+ fixed overhead, so a line close to the limit, or many short lines, can be cut
82
+ or omitted earlier than their plain total would suggest) and the final rendered
83
+ message including its label. Lines that do not fit are replaced by `[N more
84
+ matched lines omitted]`, and a first line that does not fit is cut and
85
+ marked. The limit is never below 1024 bytes (the configuration floor), enough for
86
+ the framing and an omission marker with ordinary job names; when variable
87
+ framing such as a very long job name does not fit, the monitor sends a bounded
88
+ summary that starts with `+N omitted` instead of the lines. A run keeps only a bounded prefix of its lines in memory (at most
89
+ the delivery limit, or 1 MiB when the limit is disabled, counting a small fixed
90
+ overhead per line) but
91
+ counts every match, so the omitted count is exact and an `ERROR:`/`WARN:` line
92
+ past the retained prefix still sets the label.
93
+
94
+ | Continuous job (no `--schedule`) | Delivered as |
95
+ | --------------------------------------------------------------- | --------------------------------------------- |
96
+ | Contains `error`, `fatal`, `fail`, `permission denied` | `[FAIL] <name>: matched error pattern` + line |
97
+ | Contains `warn` or `warning` | `[WARN] <name>: matched WARNING` + line |
98
+ | Contains a whole word `success`, `succeeded`, `complete`, `completed`, `done`, `ok` | Not delivered (logged only) |
99
+ | Any other matched line | The line, verbatim |
100
+
101
+ A continuous job checks a line for a negated completion word (`not complete`)
102
+ first, then for a level prefix (`ERROR:`, `WARN:`, `INFO:`, optionally after a
103
+ timestamp), then for the words above. Tokens such as `fail=0` are not failures;
104
+ the phrase `0 errors` still contains `error`.
105
+
106
+ Delivery limits:
107
+
108
+ - A continuous job delivers the first matching line in each debounce window at
109
+ once and holds later ones for a trailing summary. A scheduled job has no
110
+ debounce; its lines are aggregated as above.
111
+ - The `[FAIL]` body for a non-zero exit carries only the last 500 bytes of
112
+ output, so the message that matters goes last.
113
+
114
+ ## Prompt-Only Reminder
115
+
116
+ A reminder is a monitor whose command only prints a fixed line. Give the line a
117
+ stable prefix and match exactly that prefix.
118
+
119
+ ```bash
120
+ thrum monitor start --name standup-nudge --to @me \
121
+ --match '^REMINDER-STANDUP:' --schedule "0 9 * * 1-5" -- \
122
+ echo "REMINDER-STANDUP: post the status update, then reply to the open review"
123
+ ```
124
+
125
+ - Schedule the reminder; words in its text, including "failures", "error" and
126
+ "done", then neither relabel nor drop it, and the exit code decides success.
127
+ - Make the body an instruction with the next concrete step; a bare "reminder"
128
+ carries no action.
129
+ - Add `--notify-on-success` only when a delivered line must be guaranteed for a
130
+ run that matched nothing.
131
+ - A delivered reminder shows the schedule fired. It does not show that the work
132
+ moved. Check the work itself (commits, queue, state) before treating the
133
+ reminder as progress.
134
+
135
+ ## Script Check
136
+
137
+ A script check runs a bounded, synchronous probe and reports through its exit
138
+ code and one structured line.
139
+
140
+ ```bash
141
+ thrum monitor start --name disk-check --to @me \
142
+ --match '^CHECK-DISK:' --schedule "*/15 * * * *" -- \
143
+ bash -c 'u=$(df --output=pcent / | tail -1 | tr -dc 0-9)
144
+ [ "$u" -lt 90 ] && exit 0
145
+ echo "CHECK-DISK: root volume at ${u} percent used, free space then rerun df -h /"
146
+ exit 3'
147
+ ```
148
+
149
+ Rules:
150
+
151
+ 1. Bound every probe with `timeout <secs>`; a hung probe holds the tick.
152
+ 2. Exit 0 for healthy and print nothing matching, which keeps the run quiet.
153
+ 3. Exit non-zero for a failure; the daemon sends the `[FAIL]` notice with the
154
+ exit code and output tail.
155
+ 4. Print a matching line that names the subject, the observed value and the
156
+ next action. A generic `OK` or `done` line carries no information.
157
+ 5. Use a distinct stable prefix per outcome (`CHECK-DISK:` for a finding,
158
+ `REMINDER-DISK:` for a nudge) so `--match` selects what must reach the
159
+ target.
160
+
161
+ | Outcome | Script does | Target receives |
162
+ | -------- | ---------------------------------------- | ---------------------------------- |
163
+ | Quiet | Exit 0, no matching line | Nothing |
164
+ | Failure | Non-zero exit, finding line last | One `[FAIL] <name>: exit code N` message with the tail and the finding line |
165
+ | Finding | Exit 0, matching line(s) | The line(s), verbatim (labeled only by an `ERROR:`/`FATAL:`/`FAIL:` or `WARN:`/`WARNING:`/`ALERT:` prefix) |
166
+ | Reminder | Exit 0, one stable-prefix instruction | The line, verbatim |
167
+
168
+ A `--match` that never matches the script's real output delivers nothing and
169
+ raises no error. Test the regex against the exact output before starting.
170
+
171
+ ## Verify By Delivery
172
+
173
+ Start the monitor with a unique name, `--to` the verifying agent, and a
174
+ one-minute schedule, then read the delivered message back in full.
175
+
176
+ ```bash
177
+ thrum monitor start --name verify-<unique> --to @me --match '^VERIFY-<unique>:' \
178
+ --schedule "* * * * *" -- echo "VERIFY-<unique>: delivery body check"
179
+ thrum inbox --unread
180
+ thrum message get <message-id> # the complete delivered body
181
+ thrum monitor logs verify-<unique> # the matches the daemon recorded
182
+ thrum monitor delete verify-<unique>
183
+ ```
184
+
185
+ Confirm the body carries the prefix, the wording and no unexpected `[FAIL]`
186
+ or `[WARN]` label. Delete the monitor when done; never alter a monitor another
187
+ agent owns.
188
+
189
+ ## Troubleshooting
190
+
191
+ - Nothing arrived: `thrum monitor show` for state and last exit, `thrum
192
+ monitor logs` for recorded matches. No recorded match means the regex did not
193
+ match, or (continuous job) the line was completion-classified and suppressed;
194
+ run the command by hand and compare its output with the regex.
195
+ - Message labeled `[FAIL]` unexpectedly: the job exited non-zero, the line starts
196
+ with `ERROR:`/`FATAL:`/`FAIL:`, or (continuous job) the line contains a fail or
197
+ error substring.
198
+ - Name already in use: a stopped record holds it. `thrum monitor delete` it.
@@ -26,18 +26,8 @@ than no dashboard.
26
26
  |---|---|
27
27
  | `dashboard.set/get/show/clear/delete` RPCs (daemon-side) | **LANDED** on trunk (merge `6c7539236f83`); usable only against daemons BUILT after that merge — verify your daemon's build before relying on the RPCs |
28
28
  | `thrum dashboard` CLI (set/show/get/update/delete/clear/results) | **LANDED** on trunk (merge `108511a0`); usable only against a `thrum` binary BUILT after that merge — verify your CLI's build before relying on these commands |
29
- | Web UI Dashboard tab (where the human SEES a board) | **BUILT, NOT YET DEPLOYED** (E3 landed on trunk, merge `49712f87` — visible after the next deploy) |
30
- | Answer round-trip (user clicks → message + nudge to you) | **BUILT, NOT YET DEPLOYED** (E4 landed on trunk, merge `ea95318a` — visible after the next deploy) |
31
-
32
- **Consequence until the next deploy carries E3 and E4:** publishing a board
33
- is STILL a no-op for the human on any box running a pre-E3/pre-E4 binary —
34
- it renders NOWHERE there and, even where it renders, an answer cannot
35
- return to you, even though the UI and answer-round-trip code now both exist
36
- on trunk. Do NOT substitute a dashboard for a nudge or a direct message
37
- unless you know the box you're talking to has deployed a build containing
38
- BOTH E3 and E4. If the human cannot see the board or their answer cannot
39
- reach you, the question DOES disappear — the exact failure the dashboard
40
- exists to kill.
29
+ | Web UI Dashboard tab (where the human SEES a board) | **SHIPPED** in v0.11.0-rc.1 (E3, merge `49712f87`) |
30
+ | Answer round-trip (user clicks → message + nudge to you) | **SHIPPED** in v0.11.0-rc.1 (E4, merge `ea95318a`) |
41
31
 
42
32
  **Consequence, now that E2 has landed:** the `thrum dashboard ...` command
43
33
  shapes below are runnable — but, per the E2 row above, only against a CLI
@@ -89,7 +79,7 @@ cat > /tmp/board.md <<'EOF'
89
79
  {"title": "My board", "widgets": [ ... ]}
90
80
  EOF
91
81
  thrum dashboard set --doc-file /tmp/board.md
92
- thrum dashboard set --stdin < /tmp/board.md
82
+ thrum dashboard set < /tmp/board.md # stdin is the default when --doc-file is omitted
93
83
 
94
84
  # HTML regions come from FILES ONLY — never inline (backticks in shell strings EXECUTE)
95
85
  thrum dashboard set --html-file /tmp/panel.html --html-id my-panel
@@ -191,7 +191,7 @@ so this wrapper is defense-in-depth, not load-bearing on its own.
191
191
  non-peercred transport (browser/WS, unit tests) - but that is a general,
192
192
  already-documented property of the RPC layer (identity trust depends on
193
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
194
+ of scope per the project's security model: "Thrum does not attempt
195
195
  to prevent agent identity forgery" is an owner ruling, stated as
196
196
  [INTENDED] and permanent, not tracked as debt.
197
197
 
@@ -204,8 +204,7 @@ shapes, chosen by domain, not an oversight.
204
204
 
205
205
  Non-goal: fail-closed reject-symmetry for `state.set` (mirroring queue's
206
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.
207
+ preference, not built here - out of scope per the project's security model.
209
208
 
210
209
  ## Common Mistakes
211
210
 
@@ -73,7 +73,7 @@ yourself.
73
73
 
74
74
  ## Field Mutability Matrix
75
75
 
76
- `Entry` (`internal/state/types.go`) has five mutability classes, not one
76
+ The state `Entry` type has five mutability classes, not one
77
77
  uniform "immutable field" guard:
78
78
 
79
79
  - **immutable-identity** — part of the `(Kind, Scope)` addressing key, or
@@ -29,7 +29,7 @@ const SYNC = String.raw `\.git/thrum-sync/a-sync`;
29
29
  // Command-position anchor: line start or immediately after a shell separator
30
30
  // (;, &&, ||), then optional whitespace. This matches `cd`/`git` only when they
31
31
  // are the actual command — NOT when those words appear inside a quoted argument
32
- // preceded by a bare space (the D5 false-positive: `bd remember "... cd
32
+ // preceded by a bare space (the quoted-argument false-positive: `bd remember "... cd
33
33
  // .git/thrum-sync/a-sync ..."` must NOT be denied). Match op-shape, not the mere
34
34
  // presence of the path string.
35
35
  const CMDPOS = String.raw `(^|;|&&|\|\|)\s*`;
@@ -31,7 +31,7 @@ const ALLOW = [
31
31
  ["git -C a-sync push", `git -C ${ASYNC} push`],
32
32
  ["git -C a-sync log (not branch-changing)", `git -C ${ASYNC} log --oneline`],
33
33
  ["ls the path", `ls ${ASYNC}/`],
34
- // D5 false-positive guard: cd inside a quoted argument (bare space before it),
34
+ // quoted-argument false-positive guard: cd inside a quoted argument (bare space before it),
35
35
  // not at command position -> must be ALLOWED.
36
36
  ["quoted cd inside bd remember", `bd remember "note: do not cd ${ASYNC} directly"`],
37
37
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-thrum-pro",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Thrum multi-agent coordination plugin for Open Code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",