opencode-thrum-pro 0.1.5 → 0.1.7

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 (90) hide show
  1. package/assets/commands/thrum-compact-extended.md +367 -0
  2. package/assets/commands/thrum-compact.md +301 -0
  3. package/assets/commands/thrum-inbox.md +1 -1
  4. package/assets/commands/thrum-restart-extended.md +85 -16
  5. package/assets/commands/thrum-restart.md +106 -14
  6. package/assets/commands/thrum-send.md +4 -1
  7. package/assets/commands/thrum-sleep-extended.md +68 -51
  8. package/assets/commands/thrum-sleep.md +68 -63
  9. package/assets/commands/thrum-update-project.md +1 -1
  10. package/assets/skills/adversarial-critique/SKILL.md +5 -8
  11. package/assets/skills/brainstorm-steward/SKILL.md +65 -11
  12. package/assets/skills/brainstormer-staying-current/SKILL.md +1 -4
  13. package/assets/skills/choosing-subagent-models/SKILL.md +200 -70
  14. package/assets/skills/closing-findings-while-warm/SKILL.md +1 -2
  15. package/assets/skills/configure-roles/SKILL.md +2 -2
  16. package/assets/skills/conversation-only-context/SKILL.md +11 -11
  17. package/assets/skills/conversation-only-context/resources/driver-subagent-pattern.md +1 -1
  18. package/assets/skills/conversation-only-context/resources/remote-environment-scenarios.md +1 -1
  19. package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +98 -6
  20. package/assets/skills/coordinator-branch-split-on-block/SKILL.md +1 -2
  21. package/assets/skills/coordinator-context-monitoring/SKILL.md +29 -19
  22. package/assets/skills/coordinator-deploying-a-node/SKILL.md +124 -0
  23. package/assets/skills/coordinator-dispatching-work/SKILL.md +26 -14
  24. package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +65 -21
  25. package/assets/skills/coordinator-interacting-with-the-owner/SKILL.md +1 -2
  26. package/assets/skills/coordinator-maintaining-memory/SKILL.md +1 -1
  27. package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +7 -8
  28. package/assets/skills/coordinator-merging-code/SKILL.md +43 -22
  29. package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +29 -9
  30. package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +4 -5
  31. package/assets/skills/coordinator-rolling-out-to-nodes/SKILL.md +92 -0
  32. package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +3 -3
  33. package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +25 -25
  34. package/assets/skills/coordinator-running-review-cycles/SKILL.md +16 -6
  35. package/assets/{commands/thrum-_deviations-protocol.md → skills/deviations-protocol/SKILL.md} +6 -12
  36. package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +0 -2
  37. package/assets/skills/efficient-multi-agent-research/SKILL.md +3 -3
  38. package/assets/skills/generator-idempotency-protocol/SKILL.md +9 -0
  39. package/assets/skills/implementer-maintaining-memory/SKILL.md +3 -4
  40. package/assets/skills/implementer-orchestrating-execution/SKILL.md +12 -16
  41. package/assets/skills/implementer-receiving-dispatch/SKILL.md +15 -17
  42. package/assets/skills/implementer-receiving-review-feedback/SKILL.md +3 -4
  43. package/assets/skills/implementer-status-and-handoff/SKILL.md +1 -14
  44. package/assets/skills/implementer-tdd-and-quality/SKILL.md +2 -2
  45. package/assets/skills/memory-curate/SKILL.md +1 -1
  46. package/assets/skills/memory-write-discipline/SKILL.md +1 -1
  47. package/assets/skills/orchestrate/SKILL.md +77 -36
  48. package/assets/skills/persistent-watcher-archetype/SKILL.md +172 -25
  49. package/assets/skills/persistent-watcher-archetype/resources/capture_archive.py +618 -0
  50. package/assets/skills/persistent-watcher-archetype/resources/jev_watcher_filter.py +837 -0
  51. package/assets/skills/persistent-watcher-archetype/resources/roster-watch.sh +1140 -0
  52. package/assets/skills/persistent-watcher-archetype/resources/roster-watch_test.sh +291 -0
  53. package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +2 -2
  54. package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +20 -8
  55. package/assets/skills/prime-agent/SKILL.md +10 -0
  56. package/assets/skills/project-hotpath-gate/SKILL.md +1 -2
  57. package/assets/skills/project-philosophy/SKILL.md +1 -2
  58. package/assets/skills/project-rules-protocol/SKILL.md +12 -0
  59. package/assets/skills/project-setup/SKILL.md +27 -22
  60. package/assets/skills/project-setup/resources/implementation-agent.md +55 -35
  61. package/assets/skills/researcher-answering-queries/SKILL.md +1 -2
  62. package/assets/skills/researcher-investigating/SKILL.md +8 -13
  63. package/assets/skills/researcher-maintaining-memory/SKILL.md +1 -1
  64. package/assets/{commands/thrum-_snapshot-protocol.md → skills/snapshot-protocol/SKILL.md} +27 -21
  65. package/assets/{commands/thrum-_stamp-protocol.md → skills/stamp-protocol/SKILL.md} +14 -8
  66. package/assets/skills/thrum/SKILL.md +14 -7
  67. package/assets/skills/thrum/resources/ANTI_PATTERNS.md +16 -18
  68. package/assets/skills/thrum/resources/CLI_REFERENCE.md +17 -11
  69. package/assets/skills/thrum/resources/LISTENER_PATTERN.md +9 -3
  70. package/assets/skills/thrum/resources/MESSAGING.md +18 -11
  71. package/assets/skills/thrum/resources/WORKTREES.md +1 -1
  72. package/assets/skills/thrum-memory/SKILL.md +1 -1
  73. package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +1 -1
  74. package/assets/skills/update-queue/SKILL.md +6 -8
  75. package/assets/skills/using-the-dashboard/SKILL.md +30 -0
  76. package/assets/skills/using-the-queue/SKILL.md +76 -5
  77. package/assets/skills/using-thrum-state/SKILL.md +85 -2
  78. package/assets/skills/verify-against-plan/SKILL.md +122 -7
  79. package/assets/skills/watcher-archetype/SKILL.md +18 -0
  80. package/assets/skills/writing-durable-markdown/SKILL.md +5 -0
  81. package/dist/installer-assets.test.js +41 -0
  82. package/dist/installer.js +4 -4
  83. package/dist/permission-fixture.integration.test.js +4 -4
  84. package/package.json +1 -1
  85. package/assets/commands/thrum-_generator-idempotency-protocol.md +0 -15
  86. package/assets/commands/thrum-_project-rules-protocol.md +0 -18
  87. package/assets/commands/thrum-_verify-before-write-protocol.md +0 -27
  88. package/assets/skills/coordinator-deploying-the-fleet/SKILL.md +0 -396
  89. package/dist/allowlist-sync.test.js +0 -44
  90. /package/dist/{allowlist-sync.test.d.ts → installer-assets.test.d.ts} +0 -0
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env bash
2
+ # roster-watch_test.sh — unit tests for roster-watch.sh's
3
+ # resolve_watch_params() (the ".thrum/redirect" fix), plus a
4
+ # coverage bonus for thrum-watch-pane-capture.sh's PARAMS_FILE resolution
5
+ # logic (inline harness — that script isn't structured as sourceable
6
+ # functions the way roster-watch.sh is; see Scenario 3 below).
7
+ #
8
+ # Run: bash claude-plugin/skills/persistent-watcher-archetype/resources/roster-watch_test.sh
9
+ # (or: SCRIPT_UNDER_TEST=/path/to/alt-roster-watch.sh bash roster-watch_test.sh
10
+ # to run Scenario 1 against a different implementation, e.g. for a manual
11
+ # RED-before/GREEN-after check against a deliberately-reverted copy.)
12
+ #
13
+ # All tests are hermetic: every fixture is a throwaway tree under mktemp -d,
14
+ # cleaned up via a trap; nothing here touches the real repo's .thrum, a live
15
+ # daemon, or the network.
16
+ set -uo pipefail
17
+
18
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
+ SCRIPT_UNDER_TEST="${SCRIPT_UNDER_TEST:-$SCRIPT_DIR/roster-watch.sh}"
20
+
21
+ # shellcheck source=/dev/null
22
+ source "$SCRIPT_UNDER_TEST" || {
23
+ echo "roster-watch.sh not found/sourceable at $SCRIPT_UNDER_TEST -- resolve_watch_params is missing" >&2
24
+ exit 1
25
+ }
26
+
27
+ pass=0
28
+ fail=0
29
+ assert_eq() {
30
+ local desc="$1" expected="$2" actual="$3"
31
+ if [ "$expected" = "$actual" ]; then echo "PASS: $desc"; pass=$((pass + 1));
32
+ else echo "FAIL: $desc -- expected '$expected' got '$actual'"; fail=$((fail + 1)); fi
33
+ }
34
+ assert_ne() {
35
+ local desc="$1" unexpected="$2" actual="$3"
36
+ if [ "$unexpected" != "$actual" ]; then echo "PASS: $desc"; pass=$((pass + 1));
37
+ else echo "FAIL: $desc -- '$actual' unexpectedly equals '$unexpected'"; fail=$((fail + 1)); fi
38
+ }
39
+
40
+ # roster_count <file> -- number of entries in the "roster" array of a
41
+ # watch_params.json fixture. Uses python3 (already a hard dependency of
42
+ # roster-watch.sh itself, e.g. load_roster()).
43
+ roster_count() {
44
+ python3 -c '
45
+ import json, sys
46
+ try:
47
+ d = json.load(open(sys.argv[1]))
48
+ print(len(d.get("roster", [])))
49
+ except Exception:
50
+ print(-1)
51
+ ' "$1" 2>/dev/null
52
+ }
53
+
54
+ # mk_watch_params <file> <n> -- write a watch_params.json fixture with a
55
+ # roster array of n plain-string entries.
56
+ mk_watch_params() {
57
+ local file="$1" n="$2"
58
+ mkdir -p "$(dirname "$file")"
59
+ python3 -c '
60
+ import json, sys
61
+ n = int(sys.argv[1])
62
+ json.dump({"roster": [f"agent{i}" for i in range(n)]}, open(sys.argv[2], "w"))
63
+ ' "$n" "$file"
64
+ }
65
+
66
+ AGENT="watcher_primary"
67
+
68
+ # =============================================================================
69
+ # Scenario 1 (MANDATORY, cmain-required regression guard): a worktree that
70
+ # redirects to a main-repo tree. The "roster-editing agent's Edit-tool write"
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
74
+ # resolve_watch_params must read. A worktree-LOCAL copy at the naive,
75
+ # unresolved path is a decoy: it's what a broken script (SCRIPT_DIR-relative,
76
+ # no redirect-follow) would read instead, and it must be ignored.
77
+ #
78
+ # Bead framing: main-repo (correct) copy has 25 entries ("not 25" in the
79
+ # bug's own words -- i.e. the FIXED behavior must see 25); the worktree-local
80
+ # decoy has 24 (the stale/wrong count an unfixed script would see instead).
81
+ # =============================================================================
82
+ echo "--- Scenario 1: regression guard (redirect-resolved read wins over worktree-local decoy) ---"
83
+
84
+ worktree_root="$(mktemp -d)"
85
+ mainrepo_root="$(mktemp -d)"
86
+ trap 'rm -r -- "$worktree_root" "$mainrepo_root" 2>/dev/null || true' EXIT
87
+
88
+ mkdir -p "$worktree_root/.thrum"
89
+ printf '%s/.thrum' "$mainrepo_root" > "$worktree_root/.thrum/redirect"
90
+
91
+ mainrepo_params="$mainrepo_root/.thrum/agents/$AGENT/watch_params.json"
92
+ worktree_decoy_params="$worktree_root/.thrum/agents/$AGENT/watch_params.json"
93
+
94
+ mk_watch_params "$mainrepo_params" 25 # the real edit, correctly-wired fixture
95
+ mk_watch_params "$worktree_decoy_params" 24 # the stale worktree-local decoy
96
+
97
+ resolved="$(resolve_watch_params "$worktree_root" "$AGENT")"
98
+ assert_eq "resolves to the redirect-target (main-repo) path" "$mainrepo_params" "$resolved"
99
+ assert_ne "does NOT resolve to the worktree-local decoy path" "$worktree_decoy_params" "$resolved"
100
+ assert_eq "resolved file has the EDITED count (25, not 24)" "25" "$(roster_count "$resolved")"
101
+
102
+ # --- Negative control: prove the resolver is really reading the redirect
103
+ # target's CONTENT, not something incidental (e.g. a hardcoded/dead-end
104
+ # path, or a test bug where both fixtures happen to satisfy the assertion
105
+ # regardless of which tree holds what). Swap which tree holds the "right"
106
+ # (25) vs "wrong" (24) count, WITHOUT changing which path resolve_watch_params
107
+ # points at. If the resolver is doing real redirect-following, the resolved
108
+ # PATH stays the main-repo path (structural, content-independent), but the
109
+ # roster COUNT read from it must now be 24 (the swapped-in wrong count) --
110
+ # i.e. the same "expect 25" assertion used above must now correctly FAIL,
111
+ # proving the earlier PASS wasn't vacuous.
112
+ echo "--- Scenario 1 (mis-wired control): swapped fixture must make the same assertion fail ---"
113
+ mk_watch_params "$mainrepo_params" 24 # now the main-repo copy is "stale"
114
+ mk_watch_params "$worktree_decoy_params" 25 # decoy now happens to hold the "right" count
115
+
116
+ resolved_mis="$(resolve_watch_params "$worktree_root" "$AGENT")"
117
+ assert_eq "mis-wired: resolved path is unchanged (still the main-repo path)" "$mainrepo_params" "$resolved_mis"
118
+ got_count="$(roster_count "$resolved_mis")"
119
+ if [ "$got_count" = "25" ]; then
120
+ echo "FAIL: mis-wired control -- expected count MISMATCH (resolver should read the swapped-in 24, not the decoy's 25) but got 25; this would mean the test passes regardless of fixture wiring"
121
+ fail=$((fail + 1))
122
+ else
123
+ echo "PASS: mis-wired control -- resolver reads 24 (the main-repo tree's current content), confirming it is not incidentally reading the decoy"
124
+ pass=$((pass + 1))
125
+ fi
126
+
127
+ # restore correct wiring for anything downstream that might reuse these dirs
128
+ mk_watch_params "$mainrepo_params" 25
129
+ mk_watch_params "$worktree_decoy_params" 24
130
+
131
+ rm -r -- "$worktree_root" "$mainrepo_root"
132
+ trap - EXIT
133
+
134
+ # =============================================================================
135
+ # Scenario 2: existing (non-worktree) behavior unchanged -- no .thrum/redirect
136
+ # file at all means this IS the main repo; resolve_watch_params must fall
137
+ # through to the LOCAL .thrum/agents/<agent>/watch_params.json.
138
+ # =============================================================================
139
+ echo "--- Scenario 2: no redirect file -> resolves to local .thrum path (no regression) ---"
140
+
141
+ mainrepo_only="$(mktemp -d)"
142
+ trap 'rm -r -- "$mainrepo_only" 2>/dev/null || true' EXIT
143
+
144
+ local_params="$mainrepo_only/.thrum/agents/$AGENT/watch_params.json"
145
+ mk_watch_params "$local_params" 7
146
+ # deliberately no redirect file created
147
+
148
+ resolved_local="$(resolve_watch_params "$mainrepo_only" "$AGENT")"
149
+ assert_eq "no-redirect case resolves to the local path" "$local_params" "$resolved_local"
150
+ assert_eq "no-redirect case reads the local file's real content" "7" "$(roster_count "$resolved_local")"
151
+
152
+ rm -r -- "$mainrepo_only"
153
+ trap - EXIT
154
+
155
+ # =============================================================================
156
+ # Scenario 3 (bonus): thrum-watch-pane-capture.sh's PARAMS_FILE resolution.
157
+ # That script is NOT structured as sourceable functions (its redirect logic
158
+ # is a flat sequence of top-level assignments between REPO_ROOT and
159
+ # FALLBACK_SCRIPT, guarded by no function boundary) -- sourcing the whole
160
+ # file would run its lock-dir/roster-load/capture logic as a side effect.
161
+ # So this reproduces JUST that resolution snippet as a tiny inline harness,
162
+ # verified byte-for-byte against the live file below (so the harness can't
163
+ # silently drift from the real script) before asserting on it.
164
+ # =============================================================================
165
+ echo "--- Scenario 3 (bonus): thrum-watch-pane-capture.sh PARAMS_FILE resolution (inline harness) ---"
166
+
167
+ pane_capture_script="$SCRIPT_DIR/thrum-watch-pane-capture.sh"
168
+ if [ ! -f "$pane_capture_script" ]; then
169
+ echo "SKIP: $pane_capture_script not found -- skipping Scenario 3"
170
+ else
171
+ expected_snippet='THRUM_DIR="${REPO_ROOT}/.thrum"
172
+ if [ -f "${THRUM_DIR}/redirect" ]; then
173
+ THRUM_DIR="$(tr -d '"'"'[:space:]'"'"' < "${THRUM_DIR}/redirect")"
174
+ fi
175
+ PARAMS_FILE="${THRUM_DIR}/agents/${AGENT_NAME}/watch_params.json"'
176
+ live_snippet="$(sed -n '/^THRUM_DIR="\${REPO_ROOT}\/\.thrum"$/,/^PARAMS_FILE=/p' "$pane_capture_script")"
177
+ if [ "$live_snippet" != "$expected_snippet" ]; then
178
+ echo "FAIL: Scenario 3 harness has drifted from the live PARAMS_FILE resolution snippet in $pane_capture_script -- update the harness (this is a test-maintenance guard, not a product assertion)"
179
+ echo " live snippet was:"
180
+ echo "$live_snippet" | sed 's/^/ /'
181
+ fail=$((fail + 1))
182
+ else
183
+ echo "PASS: inline harness matches the live PARAMS_FILE resolution snippet verbatim"
184
+ pass=$((pass + 1))
185
+
186
+ resolve_pane_capture_params() {
187
+ # Args: repo_root, agent_name. Mirrors the verbatim snippet above.
188
+ local REPO_ROOT="$1" AGENT_NAME="$2" THRUM_DIR
189
+ THRUM_DIR="${REPO_ROOT}/.thrum"
190
+ if [ -f "${THRUM_DIR}/redirect" ]; then
191
+ THRUM_DIR="$(tr -d '[:space:]' < "${THRUM_DIR}/redirect")"
192
+ fi
193
+ printf '%s/agents/%s/watch_params.json\n' "${THRUM_DIR}" "${AGENT_NAME}"
194
+ }
195
+
196
+ pc_worktree="$(mktemp -d)"
197
+ pc_mainrepo="$(mktemp -d)"
198
+ trap 'rm -r -- "$pc_worktree" "$pc_mainrepo" 2>/dev/null || true' EXIT
199
+
200
+ mkdir -p "$pc_worktree/.thrum"
201
+ printf '%s/.thrum' "$pc_mainrepo" > "$pc_worktree/.thrum/redirect"
202
+ pc_mainrepo_params="$pc_mainrepo/.thrum/agents/$AGENT/watch_params.json"
203
+ pc_decoy_params="$pc_worktree/.thrum/agents/$AGENT/watch_params.json"
204
+ mk_watch_params "$pc_mainrepo_params" 25
205
+ mk_watch_params "$pc_decoy_params" 24
206
+
207
+ pc_resolved="$(resolve_pane_capture_params "$pc_worktree" "$AGENT")"
208
+ assert_eq "pane-capture: redirect present -> resolves to main-repo path" "$pc_mainrepo_params" "$pc_resolved"
209
+ assert_ne "pane-capture: does NOT resolve to worktree-local decoy" "$pc_decoy_params" "$pc_resolved"
210
+ assert_eq "pane-capture: resolved file has the edited count (25)" "25" "$(roster_count "$pc_resolved")"
211
+
212
+ rm -r -- "$pc_worktree" "$pc_mainrepo"
213
+ trap - EXIT
214
+
215
+ pc_mainrepo_only="$(mktemp -d)"
216
+ trap 'rm -r -- "$pc_mainrepo_only" 2>/dev/null || true' EXIT
217
+ pc_local_params="$pc_mainrepo_only/.thrum/agents/$AGENT/watch_params.json"
218
+ mk_watch_params "$pc_local_params" 3
219
+ pc_resolved_local="$(resolve_pane_capture_params "$pc_mainrepo_only" "$AGENT")"
220
+ assert_eq "pane-capture: no redirect -> resolves to local path" "$pc_local_params" "$pc_resolved_local"
221
+ rm -r -- "$pc_mainrepo_only"
222
+ trap - EXIT
223
+ fi
224
+ fi
225
+
226
+ # ---------------------------------------------------------------------------
227
+ # Scenario 4: load_roster's capture_daemon_overrides field.
228
+ # Regression guard for a REAL bug caught during that scrub: the plain-string
229
+ # roster-entry branch of load_roster's python emitter originally printed
230
+ # only 3 `|` separators (agent|||override) for what is now a 5-FIELD
231
+ # schema (agent|host|tmux|runtime|override, needing 4 separators) -- the
232
+ # missing separator silently shifted every subsequent field left by one,
233
+ # so a string-roster-entry agent's override always read back empty even
234
+ # when set. Covers both roster-entry shapes (plain string and the
235
+ # {agent_id,hostname,tmux_session} dict form) so a similar off-by-one in
236
+ # either emitter branch fails loud here instead of silently dropping an
237
+ # agent's --daemon-id routing in production.
238
+ # ---------------------------------------------------------------------------
239
+ echo "--- Scenario 4: capture_daemon_overrides read into ROSTER_DAEMON_OVERRIDE ---"
240
+ d4_dir="$(mktemp -d)"
241
+ trap 'rm -r -- "$d4_dir" 2>/dev/null || true' EXIT
242
+ d4_params="$d4_dir/watch_params.json"
243
+ python3 -c '
244
+ import json, sys
245
+ data = {
246
+ "roster": [
247
+ "agent_plain_no_override",
248
+ "agent_plain_with_override",
249
+ {"agent_id": "agent_dict_with_override", "hostname": "boxc", "tmux_session": "sess"},
250
+ ],
251
+ "capture_daemon_overrides": {
252
+ "agent_plain_with_override": "d_PLAINOVERRIDE00000000000",
253
+ "agent_dict_with_override": "d_DICTOVERRIDE0000000000000",
254
+ },
255
+ }
256
+ json.dump(data, open(sys.argv[1], "w"))
257
+ ' "$d4_params"
258
+
259
+ load_roster "$d4_params"
260
+
261
+ assert_eq "capture_daemon_overrides: roster has 3 entries" "3" "${#ROSTER[@]}"
262
+ assert_eq "capture_daemon_overrides: entry 0 agent id" "agent_plain_no_override" "${ROSTER[0]}"
263
+ assert_eq "capture_daemon_overrides: plain string entry with NO override reads empty" "" "${ROSTER_DAEMON_OVERRIDE[0]}"
264
+ assert_eq "capture_daemon_overrides: entry 1 agent id" "agent_plain_with_override" "${ROSTER[1]}"
265
+ assert_eq "capture_daemon_overrides: plain string entry WITH override reads its daemon id" "d_PLAINOVERRIDE00000000000" "${ROSTER_DAEMON_OVERRIDE[1]}"
266
+ assert_eq "capture_daemon_overrides: entry 2 agent id" "agent_dict_with_override" "${ROSTER[2]}"
267
+ assert_eq "capture_daemon_overrides: dict-form entry's hostname still reads correctly (field alignment intact)" "boxc" "${ROSTER_HOSTNAME[2]}"
268
+ assert_eq "capture_daemon_overrides: dict-form entry's tmux_session still reads correctly" "sess" "${ROSTER_TMUX_SESSION[2]}"
269
+ assert_eq "capture_daemon_overrides: dict-form entry WITH override reads its daemon id" "d_DICTOVERRIDE0000000000000" "${ROSTER_DAEMON_OVERRIDE[2]}"
270
+
271
+ rm -r -- "$d4_dir"
272
+ trap - EXIT
273
+
274
+ # Default/absent capture_daemon_overrides -> every entry inert (empty).
275
+ d4b_dir="$(mktemp -d)"
276
+ trap 'rm -r -- "$d4b_dir" 2>/dev/null || true' EXIT
277
+ d4b_params="$d4b_dir/watch_params.json"
278
+ python3 -c '
279
+ import json, sys
280
+ json.dump({"roster": ["agent_a", "agent_b"]}, open(sys.argv[1], "w"))
281
+ ' "$d4b_params"
282
+ load_roster "$d4b_params"
283
+ assert_eq "capture_daemon_overrides absent from watch_params.json: entry 0 inert" "" "${ROSTER_DAEMON_OVERRIDE[0]}"
284
+ assert_eq "capture_daemon_overrides absent from watch_params.json: entry 1 inert" "" "${ROSTER_DAEMON_OVERRIDE[1]}"
285
+ rm -r -- "$d4b_dir"
286
+ trap - EXIT
287
+
288
+ # ---------------------------------------------------------------------------
289
+ echo "----------------------------------------"
290
+ echo "roster-watch: $pass passed, $fail failed"
291
+ [ "$fail" -eq 0 ]
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  # thrum-capture-fallback.sh — capture/key an agent, falling back to an SSH
3
3
  # hop when the local `thrum tmux capture|key` proxy path fails with a known
4
- # cross-daemon proxy-routing error signature (thrum-7vwgy / thrum-zkqut
5
- # class bugs: same-host dual-daemon caller-identity collision, and a
4
+ # cross-daemon proxy-routing error signature (known bug classes:
5
+ # same-host dual-daemon caller-identity collision, and a
6
6
  # phantom-routing regression on daemon builds predating EnsureProxies).
7
7
  #
8
8
  # `thrum tmux capture <agent>` ALREADY reaches remote agents by name via the
@@ -3,7 +3,7 @@
3
3
  # thrum-watch-pane-capture.sh
4
4
  #
5
5
  # The generic, roster-driven pane-watch capture-loop script that ships with
6
- # the persistent-watcher-archetype plugin skill (thrum-3mhrt). Works for
6
+ # the persistent-watcher-archetype plugin skill. Works for
7
7
  # BOTH a persistent-watcher-archetype watcher and the brainstorm-steward
8
8
  # (one parameterized script serves both — the only differences, roster
9
9
  # contents and cadence, are already `watch_params.json` fields, not script
@@ -18,8 +18,8 @@
18
18
  # capture each member's pane (local `thrum tmux capture` first, ALWAYS —
19
19
  # `thrum tmux capture <agent>` already reaches remote agents by name via
20
20
  # the normal rpcrouter proxy; the SSH hop below is a resilience FALLBACK
21
- # for when that path is broken on the caller's side, e.g. thrum-7vwgy/
22
- # thrum-zkqut-class bugs, never a fleet-wide-watching enabler in its own
21
+ # for when that path is broken on the caller's side, e.g. known
22
+ # cross-daemon proxy-routing bug classes, never a fleet-wide-watching enabler in its own
23
23
  # right, and never SSH-first) -> run each capture through `thrum detect
24
24
  # --category permission` -> for a STABLE (unchanged across two captures,
25
25
  # two seconds apart) detected permission prompt, flag it in the report ->
@@ -55,7 +55,7 @@
55
55
  #
56
56
  # THRUM MONITOR EXAMPLE (see SKILL.md for the full, current recipe —
57
57
  # --notify-on-success is MANDATORY: a --schedule'd job delivers NOTHING on
58
- # --match alone, thrum-ruz1z/S5c):
58
+ # --match alone):
59
59
  # thrum monitor start --name <you>-pane-watch \
60
60
  # --match "^watch-tick: (cycle done|RESOLUTION-FAIL)" \
61
61
  # --to @<you> \
@@ -72,7 +72,19 @@ AGENT_NAME="$(basename -- "${SCRIPT_DIR}")"
72
72
  # -- never hardcode REPO_ROOT (the prior box-specific adaptation this
73
73
  # script generalizes did, and that was exactly the thing to fix).
74
74
  REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../../.." >/dev/null 2>&1 && pwd)"
75
- PARAMS_FILE="${SCRIPT_DIR}/watch_params.json"
75
+ # Resolve watch_params.json through .thrum/redirect. The
76
+ # agents/ tree is the SHARED, redirect-resolved (main-repo) tree, not
77
+ # per-worktree (see internal/paths/paths.go AgentDir) -- reading it
78
+ # straight off SCRIPT_DIR (this copy's own worktree-local location) reads
79
+ # the wrong, stale file whenever this script is deployed into a feature
80
+ # worktree that redirects: a roster-editing agent's Edit-tool write lands
81
+ # in the redirect-resolved main-repo copy, and this script would silently
82
+ # keep reading the worktree-local one and never see the edit.
83
+ THRUM_DIR="${REPO_ROOT}/.thrum"
84
+ if [ -f "${THRUM_DIR}/redirect" ]; then
85
+ THRUM_DIR="$(tr -d '[:space:]' < "${THRUM_DIR}/redirect")"
86
+ fi
87
+ PARAMS_FILE="${THRUM_DIR}/agents/${AGENT_NAME}/watch_params.json"
76
88
  FALLBACK_SCRIPT="${SCRIPT_DIR}/thrum-capture-fallback.sh"
77
89
 
78
90
  LOG_DIR="${REPO_ROOT}/.thrum/var/log"
@@ -139,7 +151,7 @@ fi
139
151
  # script (no mapfile/readarray, no `local -n`, no bash-4 associative
140
152
  # arrays -- parallel indexed arrays instead). Test with `/bin/bash
141
153
  # thrum-watch-pane-capture.sh`, not just `bash ...` or `./...`, or this
142
- # class of bug only surfaces on the live scheduled run (thrum-dnatz).
154
+ # class of bug only surfaces on the live scheduled run.
143
155
  ROSTER=()
144
156
  while IFS= read -r line; do
145
157
  [ -n "${line}" ] && ROSTER+=("${line}")
@@ -182,7 +194,7 @@ log "roster: ${ROSTER[*]}"
182
194
  # against the current CLI, regardless of how the field/scope names read.
183
195
  is_placeholder() {
184
196
  case "$1" in
185
- ""|PENDING-LEON|PENDING|TODO|TBD) return 0 ;;
197
+ ""|PENDING|TODO|TBD) return 0 ;;
186
198
  *) return 1 ;;
187
199
  esac
188
200
  }
@@ -267,7 +279,7 @@ resolve_roster_targets() {
267
279
  build_topology_table
268
280
  resolve_roster_targets
269
281
 
270
- # Fork default (coordinator-resolved, thrum-3mhrt plan): if EVERY roster
282
+ # Fork default (coordinator-resolved plan): if EVERY roster
271
283
  # agent failed resolution, that is itself worth surfacing as a real
272
284
  # problem (likely a fleet-wide topology/agent_pool data gap), not a clean
273
285
  # tick -- flip the summary line's marker so `--match` treats it as a
@@ -81,6 +81,16 @@ lifetime.
81
81
  If the session will exceed ~30 minutes, or coordination with other agents is
82
82
  needed, defer to `thrum prime`.
83
83
 
84
+ **Exception: post-`/thrum:compact` resume — MANUAL FALLBACK ONLY.** After
85
+ `/thrum:compact` or `/thrum:compact-extended`, the SessionStart hook now
86
+ auto-injects a zero-turn light briefing (`thrum prime --light`) directly into
87
+ context on a healthy daemon — no turn spent reading a file or running a skill.
88
+ This skill is the correct choice here only as a **fallback**, when that
89
+ auto-injection didn't fire or failed (e.g. the hook's "auto-injection failed"
90
+ notice, daemon unreachable). In that fallback case: read the snapshot first,
91
+ then run this skill. See `/thrum:compact`'s "How resume
92
+ works" section.
93
+
84
94
  ## After Step 2
85
95
 
86
96
  Once both steps complete, proceed with the work the wake-primer asked for.
@@ -23,8 +23,7 @@ later ones.
23
23
  3. **Re-run-evolved** (config present, project has drifted) — compute a
24
24
  sectional diff, present for approval, write only on confirmation.
25
25
 
26
- Read the shared partial at the absolute path:
27
- `claude-plugin/commands/_generator-idempotency-protocol.md`
26
+ Load the /thrum:generator-idempotency-protocol skill and follow it.
28
27
 
29
28
  > **Note: human-in-the-loop required.** This skill uses interactive prompts for
30
29
  > first-run incident-prose seeding and re-run-evolved diff approval. It cannot
@@ -29,8 +29,7 @@ later ones.
29
29
  3. **Re-run-evolved** (doc present, project has drifted) — compute a sectional
30
30
  diff, present via `AskUserQuestion`, write only on confirmation.
31
31
 
32
- Read the shared partial at the absolute path:
33
- `claude-plugin/commands/_generator-idempotency-protocol.md`
32
+ Load the /thrum:generator-idempotency-protocol skill and follow it.
34
33
 
35
34
  > **Note: human-in-the-loop required.** This skill uses `AskUserQuestion` for
36
35
  > first-run project-specific prompts, re-run-evolved diff approval, and
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: project-rules-protocol
3
+ description: "Use when loading project-specific rules - shared rule skill for role skills."
4
+ ---
5
+
6
+
7
+ # Project-Specific Rules Protocol
8
+ Project-local rules of kind `agent_rule` at `--scope role` were loaded at
9
+ session start by your preamble (see the memory rules in your role preamble).
10
+ If a project-local rule conflicts with a universal rule above, the
11
+ project-local rule wins; surface the conflict in your reply so the user can
12
+ decide whether to graduate or remove the override.
@@ -50,7 +50,7 @@ describes the artifact in front of you. Parse the binding keys from the
50
50
  **highest-cycle** (last-appended) `stage=plan` line — stamps are only ever
51
51
  appended, never reordered, so `tail -1` is the binding verdict; never `sort`
52
52
  (a `cycle=10` line must beat `cycle=9`, which a lexical sort inverts) — then
53
- re-derive the current values per `_stamp-protocol.md` § "Review-object
53
+ re-derive the current values per `stamp-protocol.md` § "Review-object
54
54
  binding" and compare:
55
55
 
56
56
  ```bash
@@ -95,7 +95,7 @@ Notes on the check:
95
95
  marker. Surface any `THRUM-DEFER` lines still present in the doc as part
96
96
  of the override audit.
97
97
  - **The Deviations block has no override.** Its absence always fails the
98
- gate — see `claude-plugin/commands/_deviations-protocol.md` ("empty must
98
+ gate — see the /thrum:deviations-protocol skill ("empty must
99
99
  be WRITTEN, not omitted").
100
100
  - **`verify=` has no silent-omission path.** A stamp missing `verify=`
101
101
  entirely FAILS this third check exactly like a missing stamp fails the
@@ -117,7 +117,7 @@ Notes on the check:
117
117
  silent-omission path** — a stamp missing `plan_obj`/`src_lock` entirely
118
118
  fails exactly like a missing `verify=` field (fourth check above). The
119
119
  remedy is the same shape as the `verify=` remedy: re-run the plan review so
120
- the stamp is emitted with live binding keys (`_stamp-protocol.md` § "Review-
120
+ the stamp is emitted with live binding keys (`stamp-protocol.md` § "Review-
121
121
  object binding"), or a coordinator adds an explicit `verdict=OVERRIDE`
122
122
  (with `reason=`) to the `stage=plan` line — the SAME auditable escape used
123
123
  for the first check, not a new mechanism. There is no `PREDATES` value for
@@ -381,16 +381,13 @@ bd dep add <later-epic-id> <earlier-epic-id>
381
381
  ### Create Tasks
382
382
 
383
383
  When creating > 6 tasks, delegate to parallel sub-agents — one per epic. Each
384
- sub-agent (sonnet-low is sufficient — the work is mechanical) gets the epic ID, the
384
+ sub-agent (a cheap tier is sufficient — the work is mechanical; model per `choosing-subagent-models`) gets the epic ID, the
385
385
  list of `bd create --title=... --type=task --priority=N --description=...`
386
386
  commands to run, the within-epic `bd dep add <later_id> <earlier_id>` ordering
387
387
  commands, and instructions to return the created task IDs and titles. Invoke
388
388
  `efficient-multi-agent-research` § Core Pattern for launch-and-wait mechanics.
389
389
 
390
- > **Model tiers:** pass an explicit `model:` on every dispatch — `sonnet`
391
- > (low effort) mechanical, `sonnet` (medium effort) judgment, Opus only on
392
- > operator-ask or a skill step that names it. See the
393
- > `choosing-subagent-models` skill for the full policy.
390
+ > **Model tiers:** pass an explicit `model:` on every dispatch; choose model and effort per the `choosing-subagent-models` skill (read `runtime.role_models` at the moment of use). Never let a sub-agent inherit your own model.
394
391
 
395
392
  After sub-agents return IDs, set cross-epic dependencies directly (requires IDs
396
393
  from multiple sub-agents):
@@ -478,20 +475,23 @@ template's "Logging Refactoring Opportunities" section.
478
475
 
479
476
  If not found, create one:
480
477
 
481
- `--description` is multi-line prose — never double-quoted inline. On
482
- `scripts/bd-shared`, `--stdin`/`--body-file` are refused (remote-path
483
- resolution + silent-empty-body hazards), so write it to a scratch file and
484
- pass `-d "$(cat <file>)"`; see the role preamble's 🔴 PROSE INTO A COMMAND
485
- rule.
478
+ `--description` is multi-line prose — never double-quoted inline. Use
479
+ `--body-file` to pass the content from a file instead; see the heredoc-or-file rule in your role preamble. `scripts/bd-shared` delivers `--body-file` (and
480
+ `--stdin`) content: it reads the file on your box and pipes those bytes to bd,
481
+ and an empty or blank body is refused loudly; only `-f`/`--file` and `--graph`
482
+ (bulk-plan files) are refused there. Scratch files go in `/private/tmp` on macOS,
483
+ where `/tmp` is a symlink (check `[ -L /tmp ]`), and in `/tmp` elsewhere.
486
484
 
487
485
  ```bash
488
- cat > /tmp/refactor-epic-desc.md <<'EOF'
486
+ # Scratch dir: /private/tmp on macOS (where /tmp is a symlink), /tmp elsewhere.
487
+ SCRATCH=$([ -L /tmp ] && echo /private/tmp || echo /tmp)
488
+ cat > "$SCRATCH/refactor-epic-desc.md" <<'EOF'
489
489
  Persistent backlog for refactoring, DRY improvements, and code
490
490
  organization opportunities discovered during feature work. Tasks are added by
491
491
  implementation agents as they encounter opportunities. Reviewed and prioritized
492
492
  by the coordinator periodically.
493
493
  EOF
494
- bd create --title="Refactoring & DRY Opportunities" --type=epic --priority=3 -d "$(cat /tmp/refactor-epic-desc.md)"
494
+ bd create --title="Refactoring & DRY Opportunities" --type=epic --priority=3 --body-file "$SCRATCH/refactor-epic-desc.md"
495
495
  ```
496
496
 
497
497
  This epic is **project-wide and long-lived** — it persists across feature epics.
@@ -604,8 +604,9 @@ Include in each option's description:
604
604
 
605
605
  - Path: `~/.workspaces/{repo}/{feature}` (from CLAUDE.md convention)
606
606
  - Branch: `feature/{feature-name}`, cut from the configured merge target
607
- (`jq -r '.orchestration.merge_target' .thrum/config.json`) — never a
608
- remembered branch name
607
+ (`CONFIG=.thrum/config.json; [ -f .thrum/redirect ] && CONFIG="$(cat .thrum/redirect)/config.json"; jq -r '.orchestration.merge_target' "$CONFIG"`
608
+ — a worktree's `.thrum/` holds only a `redirect` file, not its own
609
+ `config.json`) — never a remembered branch name
609
610
 
610
611
  **For agent names**, suggest a name derived from the feature in each option
611
612
  description (e.g., `impl_{feature}`). The user can override.
@@ -626,8 +627,11 @@ git status
626
627
  # (rebase onto a moved base is banned project-wide; see CLAUDE.md
627
628
  # "Rebase onto current tip before sending MERGE-READY")
628
629
  # Resolve the branch, never hardcode it — a name in a document goes stale and
629
- # this one already did:
630
- TARGET=$(jq -r '.orchestration.merge_target' .thrum/config.json)
630
+ # this one already did. Resolve through .thrum/redirect when present -- a
631
+ # worktree's .thrum/ holds only a `redirect` file, not its own config.json:
632
+ CONFIG=.thrum/config.json
633
+ [ -f .thrum/redirect ] && CONFIG="$(cat .thrum/redirect)/config.json"
634
+ TARGET=$(jq -r '.orchestration.merge_target' "$CONFIG")
631
635
  git fetch origin "$TARGET"
632
636
  git merge "origin/$TARGET"
633
637
  # OR if branches diverged: ask user before merging
@@ -810,6 +814,7 @@ worktree-related values come from the Phase 3 assignments:
810
814
  | `{{EPIC_TITLE}}` | Epic title (used in commit messages) |
811
815
  | `{{WORKTREE_PATH}}` | **From Phase 3 worktree assignment** |
812
816
  | `{{BRANCH_NAME}}` | **From Phase 3 worktree assignment** |
817
+ | `{{BASE_BRANCH}}` | Resolved from the project merge target: `CONFIG=.thrum/config.json; [ -f .thrum/redirect ] && CONFIG="$(cat .thrum/redirect)/config.json"; jq -r '.orchestration.merge_target' "$CONFIG"` (a worktree's `.thrum/` holds only a `redirect` file). Never assume `main` |
813
818
  | `{{PROJECT_ROOT}}` | Absolute path to the project root |
814
819
  | `{{DESIGN_DOC}}` | **Absolute path** to the design spec |
815
820
  | `{{REFERENCE_CODE}}` | Relevant reference code paths (relative OK if committed) |
@@ -864,11 +869,11 @@ architecture notes specific to this feature:
864
869
 
865
870
  > ⚠️ Verify base before acting: `git diff <sha>..origin/<merge_target> -- <files cited>` -- non-empty ⇒ cited code moved. (Resolve `<merge_target>` through its remote-tracking ref, never a bare local branch name — a local branch of the same name can be stale or absent.)
866
871
 
867
- <!-- Derive stamp per `claude-plugin/commands/_stamp-protocol.md`. -->
872
+ <!-- Derive stamp per the /thrum:stamp-protocol skill. -->
868
873
 
869
874
  ## Deviations from Source
870
875
 
871
- <!-- Derive per `claude-plugin/commands/_deviations-protocol.md` — diff
876
+ <!-- Derive per the /thrum:deviations-protocol skill — diff
872
877
  against the plan/spec, never recall from memory. Required even when
873
878
  empty. -->
874
879
 
@@ -953,7 +958,7 @@ prompt file:
953
958
  ```
954
959
 
955
960
  2. **After the post-setup prompt review terminates `Ready:Yes`**, compute
956
- `prompt_obj`/`plan_lock` per `_stamp-protocol.md` § "Review-object binding"
961
+ `prompt_obj`/`plan_lock` per `stamp-protocol.md` § "Review-object binding"
957
962
  (strip-then-hash the prompt against itself for `prompt_obj`; plain-hash the
958
963
  LOCKED plan for `plan_lock`), then append the verdict stamp:
959
964