opencode-thrum-pro 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/commands/thrum-_deviations-protocol.md +87 -0
- package/assets/commands/thrum-_generator-idempotency-protocol.md +15 -0
- package/assets/commands/thrum-_project-rules-protocol.md +18 -0
- package/assets/commands/thrum-_snapshot-protocol.md +265 -0
- package/assets/commands/thrum-_stamp-protocol.md +112 -0
- package/assets/commands/thrum-_verify-before-write-protocol.md +27 -0
- package/assets/commands/thrum-inbox.md +22 -0
- package/assets/commands/thrum-load-context.md +17 -0
- package/assets/commands/thrum-overview.md +17 -0
- package/assets/commands/thrum-prime.md +30 -0
- package/assets/commands/thrum-quickstart.md +14 -0
- package/assets/commands/thrum-reply.md +28 -0
- package/assets/commands/thrum-restart-extended.md +195 -0
- package/assets/commands/thrum-restart.md +293 -0
- package/assets/commands/thrum-send.md +31 -0
- package/assets/commands/thrum-sleep-extended.md +186 -0
- package/assets/commands/thrum-sleep.md +212 -0
- package/assets/commands/thrum-team.md +12 -0
- package/assets/commands/thrum-update-project.md +259 -0
- package/assets/commands/thrum-wait.md +14 -0
- package/assets/skills/adversarial-critique/SKILL.md +242 -0
- package/assets/skills/brainstorm-queue-aware/SKILL.md +167 -0
- package/assets/skills/brainstorm-steward/SKILL.md +181 -0
- package/assets/skills/choosing-subagent-models/SKILL.md +244 -0
- package/assets/skills/closing-findings-while-warm/SKILL.md +104 -0
- package/assets/skills/configure-roles/SKILL.md +229 -0
- package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +205 -0
- package/assets/skills/coordinator-assessing-agent-completion/scan.py +117 -0
- package/assets/skills/coordinator-branch-split-on-block/SKILL.md +168 -0
- package/assets/skills/coordinator-context-monitoring/SKILL.md +674 -0
- package/assets/skills/coordinator-dispatching-work/SKILL.md +288 -0
- package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +522 -0
- package/assets/skills/coordinator-interacting-with-the-owner/SKILL.md +171 -0
- package/assets/skills/coordinator-maintaining-memory/SKILL.md +89 -0
- package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +192 -0
- package/assets/skills/coordinator-merging-code/SKILL.md +331 -0
- package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +442 -0
- package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +198 -0
- package/assets/skills/coordinator-post-restart-sweep/SKILL.md +135 -0
- package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +199 -0
- package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +654 -0
- package/assets/skills/coordinator-running-review-cycles/SKILL.md +153 -0
- package/assets/skills/coordinator-single-responsibility-merge-gate/SKILL.md +14 -0
- package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +255 -0
- package/assets/skills/efficient-multi-agent-research/SKILL.md +177 -0
- package/assets/skills/fixing-documentation-errors/SKILL.md +258 -0
- package/assets/skills/implementer-maintaining-memory/SKILL.md +84 -0
- package/assets/skills/implementer-orchestrating-execution/SKILL.md +125 -0
- package/assets/skills/implementer-receiving-dispatch/SKILL.md +127 -0
- package/assets/skills/implementer-receiving-review-feedback/SKILL.md +125 -0
- package/assets/skills/implementer-status-and-handoff/SKILL.md +150 -0
- package/assets/skills/implementer-tdd-and-quality/SKILL.md +99 -0
- package/assets/skills/memory-audit-recover/SKILL.md +153 -0
- package/assets/skills/memory-curate/SKILL.md +139 -0
- package/assets/skills/memory-maintenance/SKILL.md +87 -0
- package/assets/skills/memory-read-discipline/SKILL.md +164 -0
- package/assets/skills/memory-search-advanced/SKILL.md +217 -0
- package/assets/skills/memory-write-discipline/SKILL.md +135 -0
- package/assets/skills/orchestrate/SKILL.md +795 -0
- package/assets/skills/persistent-watcher-archetype/SKILL.md +225 -0
- package/assets/skills/prime-agent/SKILL.md +92 -0
- package/assets/skills/project-hotpath-gate/SKILL.md +255 -0
- package/assets/skills/project-philosophy/SKILL.md +418 -0
- package/assets/skills/project-philosophy/resources/philosophy-template.md +155 -0
- package/assets/skills/project-philosophy/resources/reliability-class-library.md +653 -0
- package/assets/skills/project-setup/SKILL.md +1007 -0
- package/assets/skills/project-setup/resources/implementation-agent.md +826 -0
- package/assets/skills/researcher-answering-queries/SKILL.md +100 -0
- package/assets/skills/researcher-investigating/SKILL.md +152 -0
- package/assets/skills/researcher-maintaining-memory/SKILL.md +176 -0
- package/assets/skills/state-migration/SKILL.md +54 -0
- package/assets/skills/thrum/SKILL.md +161 -0
- package/assets/skills/thrum/resources/ANTI_PATTERNS.md +132 -0
- package/assets/skills/thrum/resources/BOUNDARIES.md +56 -0
- package/assets/skills/thrum/resources/CLI_REFERENCE.md +1066 -0
- package/assets/skills/thrum/resources/IDENTITY.md +49 -0
- package/assets/skills/thrum/resources/LISTENER_PATTERN.md +64 -0
- package/assets/skills/thrum/resources/MESSAGING.md +163 -0
- package/assets/skills/thrum/resources/TMUX_SESSIONS.md +118 -0
- package/assets/skills/thrum/resources/WORKTREES.md +117 -0
- package/assets/skills/thrum-memory/SKILL.md +138 -0
- package/assets/skills/trace-spec-to-plan/SKILL.md +104 -0
- package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +57 -0
- package/assets/skills/update-queue/SKILL.md +149 -0
- package/assets/skills/using-the-queue/SKILL.md +172 -0
- package/assets/skills/using-thrum-state/SKILL.md +104 -0
- package/assets/skills/verify-against-plan/SKILL.md +323 -0
- package/assets/skills/verify-against-source/SKILL.md +237 -0
- package/assets/skills/watcher-archetype/SKILL.md +84 -0
- package/assets/skills/writing-durable-markdown/SKILL.md +66 -0
- package/dist/hooks/block-sync-cd.d.ts +9 -0
- package/dist/hooks/block-sync-cd.js +71 -0
- package/dist/hooks/block-sync-cd.test.d.ts +1 -0
- package/dist/hooks/block-sync-cd.test.js +61 -0
- package/dist/hooks/shell-env.d.ts +8 -0
- package/dist/hooks/shell-env.js +22 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +25 -0
- package/dist/installer.d.ts +2 -0
- package/dist/installer.js +86 -0
- package/package.json +49 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coordinator-assessing-agent-completion
|
|
3
|
+
description: "Use when deciding which live agents are FINISHED and can be stood down - finding obsolete agents, idle agents, agents nobody is waiting on, agents still running after their work landed, reaping candidates, tidying up the fleet, 'who can I shut down', 'which agents are done', auditing a box before a deploy or restart - AND when reducing the worktree count or reaping/freeing worktrees to relieve load (too many worktrees, the identity or collision scan timing out, tmux create or launches failing, 'reduce the worktree count', 'reap dead worktrees'), because a worktree is an agent's home and reaping one is an agent-lifecycle decision, never disk cleanup. Produces a candidate list from the transcript plus a DIRECT-OBSERVATION liveness check (live tmux pane / ppid, not a snapshot-presence proxy), never a kill list."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Assessing Whether an Agent Is Finished — and Whether a Worktree Is Safe to Reap
|
|
7
|
+
|
|
8
|
+
`thrum tmux connect` tells you which agents are **alive**. Nothing tells you which
|
|
9
|
+
are **alive but done**. This is that second question.
|
|
10
|
+
|
|
11
|
+
**This produces INFORMATION. You make the judgment call.**
|
|
12
|
+
|
|
13
|
+
The scan ranks and quotes; it does not conclude. It has no flag that reaps,
|
|
14
|
+
kills, or changes a phase, and it must not grow one — a script that recommends
|
|
15
|
+
standing an agent down is read as having decided, and its phrasing then carries
|
|
16
|
+
weight its evidence does not.
|
|
17
|
+
|
|
18
|
+
Apply the gate and containment checks below and reach your own
|
|
19
|
+
conclusion. Destructive steps still follow the standing reap preconditions and
|
|
20
|
+
pacing rules; nothing here shortcuts them.
|
|
21
|
+
|
|
22
|
+
## The instruments, in order of trust
|
|
23
|
+
|
|
24
|
+
| Question | Instrument |
|
|
25
|
+
|---|---|
|
|
26
|
+
| Is it alive? | `thrum tmux connect < /dev/null` · `tmux list-sessions` |
|
|
27
|
+
| What did it last say? | its transcript JSONL — **the primary signal** |
|
|
28
|
+
| How long has it been quiet? | transcript mtime — **weak, see below** |
|
|
29
|
+
| What is it waiting for? | its own last message, read as prose |
|
|
30
|
+
|
|
31
|
+
**Do NOT use `thrum team`, `thrum agent list`, `phase`, or `last_seen_at` for any
|
|
32
|
+
of this.** They are stale, and they fail toward
|
|
33
|
+
reporting a live agent as absent.
|
|
34
|
+
|
|
35
|
+
## Locating an agent's transcript
|
|
36
|
+
|
|
37
|
+
The project directory is the agent's worktree path with `/` and `.` replaced by `-`:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
/Users/<you>/.thrum/worktrees/thrum/<session>
|
|
41
|
+
-> ~/.claude/projects/-Users-<you>--thrum-worktrees-thrum-<session>/*.jsonl
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Newest `.jsonl` by mtime is the live session. Records carry `timestamp`, `type`,
|
|
45
|
+
and `message.content`.
|
|
46
|
+
|
|
47
|
+
## The scan
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
thrum tmux connect < /dev/null | awk 'NR>1 && $1 ~ /^[0-9]+$/ {print $2"|"$3}'
|
|
51
|
+
```
|
|
52
|
+
gives `session|@agent` for every alive agent. For each, open the newest transcript
|
|
53
|
+
and take the last record of `type == "assistant"` with a non-empty text block.
|
|
54
|
+
That text is the signal.
|
|
55
|
+
|
|
56
|
+
## Reading the signal
|
|
57
|
+
|
|
58
|
+
**Terminal-state language** — the agent declaring it holds nothing:
|
|
59
|
+
|
|
60
|
+
> *standing by* · *standing down* · *task done* · *idle* · *no further action* ·
|
|
61
|
+
> *completion report sent* · *held pending X* · *inbox clear, nothing to act on*
|
|
62
|
+
|
|
63
|
+
**Not terminal**: a question to another agent, a partial result, a stated next
|
|
64
|
+
step, an error it has not resolved.
|
|
65
|
+
|
|
66
|
+
## ⚠️ Two false positives the phrase match produces, both measured
|
|
67
|
+
|
|
68
|
+
The scan is a **pre-filter**. It matches anywhere in the last message, which
|
|
69
|
+
catches two agents that are not finished:
|
|
70
|
+
|
|
71
|
+
1. **A long active report that happens to contain a terminal phrase mid-text.**
|
|
72
|
+
The agent is mid-work; the phrase is describing something else.
|
|
73
|
+
2. **A freshly-primed agent.** *"Inbox empty; standing by"* is part of its prime,
|
|
74
|
+
not a completion. Check its session start — minutes old means it just woke.
|
|
75
|
+
|
|
76
|
+
⇒ **Read the END of the message, not a keyword hit.** A finished agent's last
|
|
77
|
+
sentence IS the terminal statement; a working agent's merely contains one.
|
|
78
|
+
|
|
79
|
+
## 🔴 THE GATE — an agent saying it is finished is not evidence that it is
|
|
80
|
+
|
|
81
|
+
**Name what it is waiting for, then prove that thing is dead or landed.**
|
|
82
|
+
|
|
83
|
+
*"Held pending the coordinator merge"* is finished only if the merge landed or was
|
|
84
|
+
abandoned. If the merge is still queued, the agent is **correctly** waiting and
|
|
85
|
+
reaping it destroys live context that nobody else holds.
|
|
86
|
+
|
|
87
|
+
**If you cannot name the blocker, it is not a candidate.**
|
|
88
|
+
|
|
89
|
+
## Containment, before anything is stood down
|
|
90
|
+
|
|
91
|
+
- branch pushed, or no commits to lose
|
|
92
|
+
- no only-copy untracked state: `git -C <wt> status --porcelain --untracked-files=all --ignored -- .thrum` — **both flags**; the narrow form returns a false zero
|
|
93
|
+
- no restart snapshot under `<worktree>/.thrum/restart/` — a snapshot means a
|
|
94
|
+
sleeping agent that expects to wake with its context
|
|
95
|
+
|
|
96
|
+
## Reaping many worktrees at once — the batch path
|
|
97
|
+
|
|
98
|
+
The single-agent gate above still governs EACH worktree; batching changes only how you
|
|
99
|
+
GATHER the evidence and EXECUTE, never the judgment.
|
|
100
|
+
|
|
101
|
+
**Gather with read-only examiner sub-agents.** For dozens of candidates, partition them
|
|
102
|
+
across background sub-agents that examine each worktree read-only and RETURN a verdict —
|
|
103
|
+
they never reap. Per worktree each reports: sleeping-agent marker present?; branch
|
|
104
|
+
containment (ancestor of trunk / pushed / UNMERGED-with-commit-list); only-copy authored
|
|
105
|
+
content (docs/plans/specs, undelivered reports, restart snapshots), ignoring regenerable
|
|
106
|
+
dirt (`scripts/thrum-*.sh`, `.thrum/redirect`, `runtime_config.json`). Fence them READ-ONLY
|
|
107
|
+
(no checkout/reset/stash/clean/rebase/commit/push, no `worktree remove`, no `agent delete`,
|
|
108
|
+
no `rm`). You consolidate and decide.
|
|
109
|
+
|
|
110
|
+
**Cross-check the reap set against the live + sleeping roster before ANY deletion** — one
|
|
111
|
+
protected agent in the set aborts the batch. Build the exclusion from the live-pid set +
|
|
112
|
+
sleeping set + locked worktrees, not from names.
|
|
113
|
+
|
|
114
|
+
**Salvage only-copy state FIRST, and diff by NAME.** Copy with `cp -a`, then compare source
|
|
115
|
+
vs salvage with `find -type f | sort` (set-diff by NAME) BEFORE trusting per-file `cmp` — a
|
|
116
|
+
flat copy of two same-basename files (e.g. `.thrum/restart/<a>.md` and
|
|
117
|
+
`.thrum/context/<a>.md`) silently clobbers one, and `cmp` on what copied passes clean. Give
|
|
118
|
+
salvaged files distinct names and land the salvage somewhere durable (the main repo, not
|
|
119
|
+
`/private/tmp`).
|
|
120
|
+
|
|
121
|
+
## 🔴 The reap is worktree-removal-only — and TEST every step on ONE item first
|
|
122
|
+
|
|
123
|
+
**`thrum agent delete --force` is REFUSED by the CAS guard on a stale agent with an empty
|
|
124
|
+
`agent_pid_start_time`** (*"expected-state premise is required … refusing rather than treating
|
|
125
|
+
an empty/never-read premise as a match"*). `--force` does NOT bypass it; the guard is
|
|
126
|
+
correct. So the reap that actually frees load is **`git worktree remove --force <path>`** —
|
|
127
|
+
the delete best-effort-fails and leaves a **benign orphan DB row**, which you LEAVE (filtered
|
|
128
|
+
at query per the fleet ruling; never `agent cleanup --force` to tidy them).
|
|
129
|
+
|
|
130
|
+
⚠️ **After a large reap you may see a `DeadAgentSweeper` skip alert + elevated write-RPC
|
|
131
|
+
latency — do NOT assume the reap caused it.** This box carries a standing single-writer
|
|
132
|
+
contention condition (one serialized write conn, `SetMaxOpenConns(1)`) that spikes write-RPC
|
|
133
|
+
latency recurrently and **reap-independently**. A large reap is at most ONE coincident spike: stranding dozens of stale
|
|
134
|
+
agents (worktreeless, empty `pid_start_time` → always "due") stretches the sweep's
|
|
135
|
+
evidence-gathering run-length (that is what `held=` measures — a run duration, mostly off-lock
|
|
136
|
+
syscalls, not a lock hold or a deadlock; the skip is correct, the guard releases via `defer`),
|
|
137
|
+
but it drives almost no writes of its own. The
|
|
138
|
+
discriminator that it is contention and not poison: `write_pool in_use=0`,
|
|
139
|
+
`selfheal_write_poison_detected=0`. **So do not chase the reap as the lever** — pacing/batching
|
|
140
|
+
large reaps is mild hygiene at best (it trims run-length and a little pressure), a restart is
|
|
141
|
+
transient relief only, and the durable fix is **writer-decontention**, owned by the
|
|
142
|
+
daemon-perf agent. Symptom to recognize (reads and `daemon status` stay ~0.5s while
|
|
143
|
+
`status --self` climbs — writes slow, not reads): hand it to the daemon-perf owner, don't
|
|
144
|
+
re-diagnose the internals yourself.
|
|
145
|
+
|
|
146
|
+
**Before running any batched destructive loop, run it on ONE item and verify the EFFECT**
|
|
147
|
+
(agent gone from the registry / worktree gone from `git worktree list` and disk), not just
|
|
148
|
+
the exit code. A batch can fail wholesale for a reason a single test surfaces instantly — a
|
|
149
|
+
minimal-`PATH` loop/background context where `git`/`thrum`/`sleep` are `command not found`
|
|
150
|
+
(set `PATH` explicitly or use absolute paths); a `read` that collapses a leading empty field.
|
|
151
|
+
Batched worktree removal is also SLOW on a loaded box (seconds each) — run it in the
|
|
152
|
+
background or in chunks, never one 2-minute foreground loop.
|
|
153
|
+
|
|
154
|
+
## ⚠️ Idle time is corrupted by your own traffic
|
|
155
|
+
|
|
156
|
+
A coordinator broadcast resets every recipient's transcript mtime. After any
|
|
157
|
+
fleet-wide message, **every agent reads as freshly active** and the idle column
|
|
158
|
+
is meaningless for hours.
|
|
159
|
+
|
|
160
|
+
⇒ **Rank by the last message's CONTENT, not by elapsed time.** A nudged-then-idle
|
|
161
|
+
agent re-states that it is standing by, so the content signal survives what the
|
|
162
|
+
timing signal does not.
|
|
163
|
+
|
|
164
|
+
## ⚠️ Two absences that are not evidence
|
|
165
|
+
|
|
166
|
+
- **No transcript** does not mean no agent. Alive agents exist with no transcript
|
|
167
|
+
directory.
|
|
168
|
+
- **Absent from `connect`** means only "no tmux session on THIS box". `connect` is
|
|
169
|
+
box-local; remote agents can never appear in it.
|
|
170
|
+
|
|
171
|
+
## Idle-gopls RAM reclaim
|
|
172
|
+
|
|
173
|
+
Once you've classified an agent as idle, its `gopls` process (if any) is a
|
|
174
|
+
second, independent reclaim candidate — an idle agent's gopls is dead weight
|
|
175
|
+
sitting in RAM.
|
|
176
|
+
|
|
177
|
+
`scripts/reap-idle-gopls.sh` matches each running `gopls` process to its
|
|
178
|
+
owning agent (ppid chain → tmux pane_pid → `thrum team --json` session) and
|
|
179
|
+
uses `tmux_state` as a cheap first-pass filter — anything not idle by that
|
|
180
|
+
signal is skipped outright. That filter is bounded, not authoritative: it
|
|
181
|
+
does not replace this skill's own idle/finished judgment, which still comes
|
|
182
|
+
from the transcript per the rules above. Run the script alongside the scan
|
|
183
|
+
above, apply the same transcript-based gate to whatever it surfaces, and
|
|
184
|
+
fold the result into a single combined table: idle agent · gopls PID ·
|
|
185
|
+
RSS-MB.
|
|
186
|
+
|
|
187
|
+
- Killing an idle agent's gopls is a no-op — it respawns on the agent's next
|
|
188
|
+
LSP request. Never destructive to the agent itself.
|
|
189
|
+
- **NEVER kill a pool or active-worker gopls.** The script excludes by ROLE
|
|
190
|
+
(orchestrator, gate, coordinator, brainstormer, researcher — the roles
|
|
191
|
+
that are never reap targets) plus the non-idle-state skip above, plus one
|
|
192
|
+
named exception for a role-uncovered pool agent. It also runs a canary
|
|
193
|
+
self-check before printing anything: if it can't prove the exclusion still
|
|
194
|
+
resolves correctly, it aborts loudly instead of printing a kill-list.
|
|
195
|
+
Do not override or bypass any of this.
|
|
196
|
+
- Same framing as the rest of this skill: the script **produces information,
|
|
197
|
+
you decide**. It is read-only and dry-run — it prints a kill-list, it never
|
|
198
|
+
kills anything.
|
|
199
|
+
|
|
200
|
+
## Why this fails dangerously if you skip the gate
|
|
201
|
+
|
|
202
|
+
The failure direction is **false-GONE**: concluding a live agent is finished. It
|
|
203
|
+
authorises abandonment, and **nobody audits an agent nobody believes exists**, so
|
|
204
|
+
the error has no natural discovery path. A false-ALIVE costs one wasted check.
|
|
205
|
+
Prefer it.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Rank alive agents by whether their last message reads as terminal.
|
|
3
|
+
|
|
4
|
+
This is an INFORMATION source. It ranks and quotes; it does not conclude, and it
|
|
5
|
+
deliberately has no flag that reaps, kills, or changes a phase. The reading agent
|
|
6
|
+
makes the judgment call using the gate and containment checks in SKILL.md.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
thrum tmux connect < /dev/null | python3 scan.py [worktree-root]
|
|
10
|
+
|
|
11
|
+
Reads `session|@agent` lines on stdin (see SKILL.md for the awk that produces
|
|
12
|
+
them). Default worktree root is ~/.thrum/worktrees/thrum.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import datetime
|
|
16
|
+
import glob
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
# Phrases an agent uses to declare it holds nothing. Matching one makes an agent
|
|
23
|
+
# a CANDIDATE, never a decision -- the blocker check in SKILL.md is what decides.
|
|
24
|
+
TERMINAL = (
|
|
25
|
+
"standing by", "standing down", "stand down", "task done", "no further action",
|
|
26
|
+
"nothing to add", "nothing further", "completion report sent", "inbox is clear",
|
|
27
|
+
"inbox clear", "no action needed", "no reply needed", "idle", "held pending",
|
|
28
|
+
"awaiting", "nothing owed", "work is complete", "done and reported",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def slug(path):
|
|
33
|
+
"""Claude Code's project-dir naming: every character that is
|
|
34
|
+
not [A-Za-z0-9] becomes '-' -- not just '/' and '.'.
|
|
35
|
+
Mirrors Claude Code's own path-encoding algorithm exactly for ASCII input -- must stay byte-for-byte in sync with it.
|
|
36
|
+
"""
|
|
37
|
+
return re.sub(r"[^A-Za-z0-9]", "-", path)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def last_assistant_text(path):
|
|
41
|
+
"""Last non-empty assistant text block, or '' if none."""
|
|
42
|
+
try:
|
|
43
|
+
with open(path, encoding="utf-8") as fh:
|
|
44
|
+
lines = fh.read().splitlines()
|
|
45
|
+
except OSError:
|
|
46
|
+
return ""
|
|
47
|
+
for line in reversed(lines):
|
|
48
|
+
try:
|
|
49
|
+
rec = json.loads(line)
|
|
50
|
+
except ValueError:
|
|
51
|
+
continue
|
|
52
|
+
if rec.get("type") != "assistant":
|
|
53
|
+
continue
|
|
54
|
+
for block in rec.get("message", {}).get("content", []):
|
|
55
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
56
|
+
text = " ".join(block.get("text", "").split())
|
|
57
|
+
if text:
|
|
58
|
+
return text
|
|
59
|
+
return ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main():
|
|
63
|
+
root = sys.argv[1] if len(sys.argv) > 1 else os.path.expanduser(
|
|
64
|
+
"~/.thrum/worktrees/thrum")
|
|
65
|
+
projects = os.path.expanduser("~/.claude/projects")
|
|
66
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
67
|
+
|
|
68
|
+
rows, no_transcript = [], []
|
|
69
|
+
for line in sys.stdin:
|
|
70
|
+
line = line.strip()
|
|
71
|
+
if "|" not in line:
|
|
72
|
+
continue
|
|
73
|
+
session, agent = line.split("|", 1)
|
|
74
|
+
files = glob.glob(os.path.join(projects, slug(os.path.join(root, session)), "*.jsonl"))
|
|
75
|
+
if not files:
|
|
76
|
+
# NOT evidence the agent is gone -- see SKILL.md.
|
|
77
|
+
no_transcript.append(agent)
|
|
78
|
+
continue
|
|
79
|
+
newest = max(files, key=os.path.getmtime)
|
|
80
|
+
mtime = datetime.datetime.fromtimestamp(os.path.getmtime(newest),
|
|
81
|
+
datetime.timezone.utc)
|
|
82
|
+
text = last_assistant_text(newest)
|
|
83
|
+
low = text.lower()
|
|
84
|
+
hits = [p for p in TERMINAL if p in low]
|
|
85
|
+
rows.append(((now - mtime).total_seconds() / 3600.0, agent, hits, text))
|
|
86
|
+
|
|
87
|
+
rows.sort(key=lambda r: (not r[2], -r[0]))
|
|
88
|
+
|
|
89
|
+
print(f"alive with transcripts: {len(rows)} no transcript: {len(no_transcript)}")
|
|
90
|
+
print("\nLAST MESSAGE READS AS TERMINAL -- an observation, not a recommendation.")
|
|
91
|
+
print("Name each one's blocker and prove it dead before concluding. See SKILL.md.\n")
|
|
92
|
+
any_candidate = False
|
|
93
|
+
for idle, agent, hits, text in rows:
|
|
94
|
+
if not hits:
|
|
95
|
+
continue
|
|
96
|
+
any_candidate = True
|
|
97
|
+
print(f" {agent:<28} idle {idle:5.1f}h [{', '.join(hits)}]")
|
|
98
|
+
print(f" {text[:150]}")
|
|
99
|
+
if not any_candidate:
|
|
100
|
+
print(" (none)")
|
|
101
|
+
|
|
102
|
+
print("\nNOT terminal -- still working or mid-exchange:")
|
|
103
|
+
for idle, agent, hits, _ in rows:
|
|
104
|
+
if not hits:
|
|
105
|
+
print(f" {agent:<28} idle {idle:5.1f}h")
|
|
106
|
+
|
|
107
|
+
if no_transcript:
|
|
108
|
+
print("\nNo transcript found (NOT evidence of absence):")
|
|
109
|
+
for agent in no_transcript:
|
|
110
|
+
print(f" {agent}")
|
|
111
|
+
|
|
112
|
+
print("\nIdle hours are unreliable after any fleet broadcast -- your own "
|
|
113
|
+
"traffic resets every recipient's clock. Rank by content, not time.")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
main()
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coordinator-branch-split-on-block
|
|
3
|
+
description: "Use when a Pass-3 merge gate (hotpath, philosophy) or dual review BLOCKS on ONE concern of a branch that bundles multiple independent concerns, while the other concern(s) are clean - on a live multi-writer shared branch. Walks the split-land-recut sequence - separate the blocked concern from the clean one(s), land the clean half first, re-cut the blocked half onto the now-moved base, and verify the dropped commits are NOT ancestors of the re-cut tip before re-gating and merging."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Coordinator: Branch-Split When One Concern Blocks a Multi-Concern Branch
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
- A Pass-3 dual merge-gate (`coordinator-hotpath-merge-gate` or
|
|
11
|
+
`coordinator-philosophy-merge-gate`) — or dual review — **BLOCKS on one
|
|
12
|
+
identifiable concern** of a branch, while the branch also carries one or
|
|
13
|
+
more genuinely independent, unrelated concerns that are clean.
|
|
14
|
+
- The clean concern(s) are ready to land on their own merits and gain nothing
|
|
15
|
+
from waiting on the blocked concern's fix.
|
|
16
|
+
- The branch lives on a **live multi-writer shared branch** — other
|
|
17
|
+
coordinators/agents are also pushing, so the base moves under you and a
|
|
18
|
+
naive re-cut needs an explicit safety check, not just a rebase.
|
|
19
|
+
|
|
20
|
+
**Don't use when:**
|
|
21
|
+
|
|
22
|
+
- The BLOCK concern and the "clean" concern actually share code or intent —
|
|
23
|
+
if splitting would require cherry-picking around interdependent commits,
|
|
24
|
+
this is not an independent-concerns split; fix the blocker in place
|
|
25
|
+
instead.
|
|
26
|
+
- Only one concern exists on the branch. A single-concern BLOCK just goes
|
|
27
|
+
back to the implementer normally — there is nothing to separate.
|
|
28
|
+
|
|
29
|
+
## The split decision
|
|
30
|
+
|
|
31
|
+
When a gate names a BLOCKING finding scoped to one part of the diff, and the
|
|
32
|
+
rest of the diff is unrelated to that finding, split the branch into:
|
|
33
|
+
|
|
34
|
+
- **The clean half** — the concern(s) with zero BLOCKING findings.
|
|
35
|
+
- **The blocked half** — the concern the gate flagged.
|
|
36
|
+
|
|
37
|
+
State the split explicitly in the merge report before doing anything else:
|
|
38
|
+
name which commits belong to which half. If a commit touches both concerns,
|
|
39
|
+
that commit belongs to the blocked half (never guess a commit apart) or the
|
|
40
|
+
split isn't clean enough to attempt this play.
|
|
41
|
+
|
|
42
|
+
## Land the clean half first
|
|
43
|
+
|
|
44
|
+
Do not hold the clean half hostage to the blocked half's fix cycle. Route the
|
|
45
|
+
clean half through its own merge report to the merge-king (coordinators
|
|
46
|
+
never self-merge) and get it merged **first**. This advances the mainline
|
|
47
|
+
tip by the clean half's commits.
|
|
48
|
+
|
|
49
|
+
## Re-cut the blocked half onto the now-moved base
|
|
50
|
+
|
|
51
|
+
> **This is not the banned rebase.** The project-wide ban (see
|
|
52
|
+
> `coordinator-dispatching-work` and `orchestrate` — "merge forward, never
|
|
53
|
+
> rebase") targets rebasing a still-live, in-flight branch onto a moved base
|
|
54
|
+
> as the pre-MERGE-READY sync step: a stale-local-ref rebase there can
|
|
55
|
+
> silently revert commits landed since your last fetch, with no check
|
|
56
|
+
> catching it. The re-cut below is a different operation on a different
|
|
57
|
+
> object — you are not syncing an existing branch, you are **building a new
|
|
58
|
+
> branch from scratch** out of only the blocked half's own unpushed commits,
|
|
59
|
+
> and the operation is made safe by an explicit invariant check (below), not
|
|
60
|
+
> by care alone. Rebasing (or cherry-picking) a branch's own local, unpushed
|
|
61
|
+
> commits onto a fresh base during a deliberate re-cut is legitimate; do not
|
|
62
|
+
> "fix" this section by replacing it with merge-forward.
|
|
63
|
+
|
|
64
|
+
Once the clean half is merged, the blocked half must be **re-cut** — not
|
|
65
|
+
merely rebased — onto the new tip, so it carries **only its own commits**,
|
|
66
|
+
not a copy of the clean half's commits riding along from the original
|
|
67
|
+
branch-off point. Concretely: create the new branch from the merged tip
|
|
68
|
+
(the clean half's merge SHA, announced by SHA unprompted, per base-mover
|
|
69
|
+
discipline), then cherry-pick or rebase only the blocked half's own commits
|
|
70
|
+
onto it. The goal state is a
|
|
71
|
+
branch whose full commit range is exactly "the blocked concern's fix,"
|
|
72
|
+
nothing else.
|
|
73
|
+
|
|
74
|
+
## The not-ancestor invariant — the load-bearing check
|
|
75
|
+
|
|
76
|
+
**Pin exactly which SHAs are the "dropped set" — this is the detail the check
|
|
77
|
+
lives or dies on.** The dropped set is the clean half's commits **as they
|
|
78
|
+
existed on the original pre-split bundled branch** — their ORIGINAL SHAs,
|
|
79
|
+
before the clean half was landed. It is **not** the clean half's merged
|
|
80
|
+
form: landing the clean half can rewrite its commits (a rebase, a squash, a
|
|
81
|
+
merge-time correction), which mints **new, distinct git objects** with new
|
|
82
|
+
SHAs. Those merged-form SHAs are *expected*, *correct* ancestors of the
|
|
83
|
+
re-cut tip, because construction branched the re-cut FROM the merged tip —
|
|
84
|
+
seeing them as ancestors is not a failure, it's the point.
|
|
85
|
+
|
|
86
|
+
**After re-cutting, verify the ORIGINAL pre-split SHAs of the clean half's
|
|
87
|
+
commits are NOT ancestors of the re-cut branch's tip:**
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
git merge-base --is-ancestor <original-pre-split-clean-half-sha> <recut-blocked-tip>
|
|
91
|
+
# exit 1 (NOT an ancestor) is the PASS condition
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Run this for every commit in the dropped set (the original pre-split SHAs),
|
|
95
|
+
not just the first one — and never substitute the merged-form SHAs here, or
|
|
96
|
+
the check inverts and fails a safe re-cut.
|
|
97
|
+
|
|
98
|
+
**Why this is the check that matters:** the risk isn't that the re-cut tip
|
|
99
|
+
descends from the clean half's *landed* fix — it must, by construction. The
|
|
100
|
+
risk is that the re-cut branch was built sloppily (e.g. a naive rebase of
|
|
101
|
+
the whole original bundle instead of a cherry-pick of only the blocked
|
|
102
|
+
half's commits) and ended up carrying the clean half's **original,
|
|
103
|
+
pre-correction commit objects** as ancestors alongside the merged tip. If
|
|
104
|
+
that happens, merging the re-cut branch feeds those original objects into a
|
|
105
|
+
future merge-base calculation on the same files, and git's three-way merge
|
|
106
|
+
can resolve back toward the older, pre-correction content — silently
|
|
107
|
+
reverting whatever the clean half's merge fixed. There is no conflict, no
|
|
108
|
+
warning, no gate that catches this by construction: the merge just succeeds
|
|
109
|
+
and quietly re-applies the older version. Verification (ancestry) has a
|
|
110
|
+
shelf life, and re-deriving it AT THIS SPECIFIC HANDOFF (the re-cut) is not
|
|
111
|
+
optional.
|
|
112
|
+
|
|
113
|
+
Run the check at **re-cut time** and again immediately before the merge-king
|
|
114
|
+
executes the re-cut branch's merge (the base can move again in between on a
|
|
115
|
+
live shared branch — re-run, don't relay a stale result).
|
|
116
|
+
|
|
117
|
+
## Three-pass re-gate on the re-cut branch
|
|
118
|
+
|
|
119
|
+
The re-cut branch is a new artifact (different base, different commit set)
|
|
120
|
+
and gets a full re-gate, not a diff-against-the-old-gate. Follow the
|
|
121
|
+
three-pass order, in order:
|
|
122
|
+
|
|
123
|
+
1. **UNFRAMED first** — no defect brief, read the full changed functions,
|
|
124
|
+
flag anything nobody asked about. Run this before the other two; a
|
|
125
|
+
framed pass run first anchors every subsequent pass to the same blind
|
|
126
|
+
spot.
|
|
127
|
+
2. **CODE-DERIVED enumeration** — terms and symbols pulled from the code
|
|
128
|
+
itself (not from the original defect description), enumerate every call
|
|
129
|
+
site / consumer.
|
|
130
|
+
3. **BY-EFFECT** — assert the specific defect the original BLOCK named
|
|
131
|
+
cannot occur, phrased as a checkable behavior. Example from the worked
|
|
132
|
+
case: "an inconclusive liveness probe produces neither a tombstone nor a
|
|
133
|
+
repair" — a concrete, testable claim, not "the fix looks right."
|
|
134
|
+
|
|
135
|
+
A code-derived enumeration is not a substitute for the unframed pass — it is
|
|
136
|
+
still aimed at symbols the code review chose to look at. Both are required.
|
|
137
|
+
|
|
138
|
+
## Hold the implementer's retirement until BOTH halves merge
|
|
139
|
+
|
|
140
|
+
Do not retire (agent.delete / worktree teardown) the implementer who owns
|
|
141
|
+
this work until **both** the clean half and the blocked half have merged.
|
|
142
|
+
The implementer's context on the blocked half's fix is exactly what the
|
|
143
|
+
three-pass re-gate cycle needs if it surfaces further findings; retiring
|
|
144
|
+
early forces a cold restart mid-fix-cycle.
|
|
145
|
+
|
|
146
|
+
## Gate that closes it
|
|
147
|
+
|
|
148
|
+
Both halves merged, both efficacy rows recorded (one row per merged branch,
|
|
149
|
+
not one row for the original bundled branch), the not-ancestor invariant's
|
|
150
|
+
PASS result is part of the merge report the merge-king relied on (not
|
|
151
|
+
asserted after the fact).
|
|
152
|
+
|
|
153
|
+
## See also
|
|
154
|
+
|
|
155
|
+
- `coordinator-hotpath-merge-gate` / `coordinator-philosophy-merge-gate` —
|
|
156
|
+
the Pass-3 gates whose BLOCK verdict triggers this play.
|
|
157
|
+
- `coordinator-running-review-cycles` — the dual-review cycle that may also
|
|
158
|
+
originate a scoped BLOCK this play applies to.
|
|
159
|
+
|
|
160
|
+
## Project-specific rules (already loaded)
|
|
161
|
+
|
|
162
|
+
Read the shared partial at the absolute path:
|
|
163
|
+
`claude-plugin/commands/_project-rules-protocol.md`
|
|
164
|
+
|
|
165
|
+
If you accumulate a new rule mid-session about branch-splitting, capture it
|
|
166
|
+
via the `coordinator-maintaining-memory` skill — it references the
|
|
167
|
+
`memory-write-discipline` common for the canonical `thrum memory create`
|
|
168
|
+
shape.
|