diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index ecb23d4..047cd23 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -9,7 +9,7 @@
{
"name": "superpowers",
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
- "version": "5.2.0",
+ "version": "5.3.0",
"source": "./",
"author": {
"name": "Jesse Vincent",
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index 4d1cb97..f489e23 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "superpowers",
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
- "version": "5.2.0",
+ "version": "5.3.0",
"author": {
"name": "Jesse Vincent",
"email": "jesse@fsck.com"
diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json
index f2daa59..db19b5c 100644
--- a/.cursor-plugin/plugin.json
+++ b/.cursor-plugin/plugin.json
@@ -2,7 +2,7 @@
"name": "superpowers",
"displayName": "Superpowers",
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
- "version": "5.1.0",
+ "version": "5.3.0",
"author": {
"name": "Jesse Vincent",
"email": "jesse@fsck.com"
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 56c4824..1c95e4e 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -1,5 +1,30 @@
# Superpowers Release Notes
+## v5.3.0 (2026-04-29)
+
+### New Features
+
+**Compaction-recovery hooks (Claude Code, Cursor)**
+
+Long autonomous runs are now resilient to context compaction. Two hooks ship in `hooks/hooks.json`:
+
+- **`SessionStart` (matcher `compact|resume`)** — re-injects a `` block into the resumed session containing the original task (extracted from the first user message in the transcript) and the last 30 superpowers activity entries. This re-anchors a compacted **subagent** to its original assignment and re-anchors the **lead orchestrator** to its place in the pipeline. The hook fires inside each session against its own transcript, so subagents recover their own task context automatically.
+- **`PostToolUse` (matcher `Skill|Agent|Task.*`)** — appends each `Skill`, `Agent`, and `Task*`-family (`Task`, `TaskCreate`, `TaskList`, `TaskGet`, `TaskUpdate`, etc.) invocation to `.claude/superpowers-state/in-progress.jsonl` (capped at 200 lines; wiped on `startup|clear`, or when the session source can't be determined). Append+rotate is guarded by a `flock` when available so concurrent writes from the lead and subagents don't corrupt the JSONL. This is the activity log that the SessionStart hook replays.
+
+The state file is project-local and in JSONL format. Both hooks no-op gracefully when `jq` is unavailable. On hosts without a documented hooks system (Codex, OpenCode), the same recovery pattern is described in prose as a manual discipline.
+
+**Subagent watchdog cadence (every 5–10 minutes)**
+
+`subagent-driven-development` now prescribes a 5–10 minute health-check cadence on background subagents: confirm still-active, output flowing, no API/rate-limit/transport errors, not flailing off-task. Includes corrective playbooks for stuck agents (send a redirecting message, or terminate and re-dispatch with a one-line note about what went wrong). Tools mentioned (`TaskList`, `TaskOutput`, `SendMessage`, `TaskStop`, `ScheduleWakeup`) are wrapped in `` blocks; Codex and OpenCode get host-conditional equivalents (scratch-context tracking, status pings via thread / `@mention`).
+
+**Quality-based subagent rotation**
+
+`subagent-driven-development` now prescribes replacing — not re-prompting — a `subagent_type` that is consistently low-quality. Track per-session quality signals (review rejections, corrective messages, attributable failures); rotate triggers are 2 consecutive review rejections on the same task, 3 cumulative quality issues across tasks, or 2 instances of ignored guidance. Rotation should be stated visibly in user-facing text so the user can redirect.
+
+### Why
+
+A subagent that compacts mid-flight, hits a transient API error, or quietly drifts off-task can burn 30+ minutes of autonomous run time before anyone notices. The hook automation handles compaction recovery deterministically; the watchdog and rotation patterns rely on the orchestrator applying them consistently. Together they close the most common silent-degradation paths in the autonomous pipeline.
+
## v5.0.0 (2026-03-04)
### New Features
diff --git a/hooks/hooks.json b/hooks/hooks.json
index 2dacc8a..49087bd 100644
--- a/hooks/hooks.json
+++ b/hooks/hooks.json
@@ -11,6 +11,18 @@
}
]
}
+ ],
+ "PostToolUse": [
+ {
+ "matcher": "Skill|Agent|Task.*",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "'${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd' record-activity",
+ "timeout": 10
+ }
+ ]
+ }
]
}
}
diff --git a/hooks/record-activity b/hooks/record-activity
new file mode 100755
index 0000000..d5ab221
--- /dev/null
+++ b/hooks/record-activity
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+# PostToolUse hook: record superpowers-relevant activity
+#
+# Appends an entry to .claude/superpowers-state/in-progress.jsonl every time
+# the lead/orchestrator agent invokes a Skill, Agent, or Task* tool
+# (Task, TaskCreate, TaskList, TaskGet, TaskUpdate, etc.). The session-start
+# hook replays this file on compact|resume so the agent can resume the
+# pipeline it was running and check on dispatched subagents.
+
+set -euo pipefail
+
+# Need stdin (the PostToolUse JSON payload) and jq to do anything useful.
+[ -t 0 ] && exit 0
+command -v jq >/dev/null 2>&1 || exit 0
+
+hook_input=$(cat || true)
+[ -z "$hook_input" ] && exit 0
+
+tool_name=$(printf '%s' "$hook_input" | jq -r '.tool_name // empty' 2>/dev/null || true)
+cwd_dir=$(printf '%s' "$hook_input" | jq -r '.cwd // empty' 2>/dev/null || true)
+[ -z "$cwd_dir" ] && cwd_dir="${PWD}"
+
+case "$tool_name" in
+ Skill)
+ skill=$(printf '%s' "$hook_input" | jq -r '.tool_input.skill // ""' 2>/dev/null || true)
+ args=$(printf '%s' "$hook_input" | jq -r '.tool_input.args // ""' 2>/dev/null || true)
+ detail="skill=${skill}"
+ [ -n "$args" ] && detail="${detail} args=${args:0:80}"
+ ;;
+ Agent|Task)
+ sa_type=$(printf '%s' "$hook_input" | jq -r '.tool_input.subagent_type // "general-purpose"' 2>/dev/null || true)
+ desc=$(printf '%s' "$hook_input" | jq -r '.tool_input.description // ""' 2>/dev/null || true)
+ bg=$(printf '%s' "$hook_input" | jq -r '.tool_input.run_in_background // false' 2>/dev/null || true)
+ detail="agent=${sa_type} desc=\"${desc:0:120}\" bg=${bg}"
+ ;;
+ Task*)
+ # Task family tools (TaskCreate/TaskList/TaskGet/TaskUpdate/etc.)
+ # don't share a single payload schema; record a generic best-effort
+ # summary from common fields rather than failing closed.
+ sa_type=$(printf '%s' "$hook_input" | jq -r '.tool_input.subagent_type // ""' 2>/dev/null || true)
+ desc=$(printf '%s' "$hook_input" | jq -r '.tool_input.description // .tool_input.title // ""' 2>/dev/null || true)
+ task_id=$(printf '%s' "$hook_input" | jq -r '.tool_input.id // .tool_input.task_id // ""' 2>/dev/null || true)
+ detail="task_tool=${tool_name}"
+ [ -n "$sa_type" ] && detail="${detail} agent=${sa_type}"
+ [ -n "$task_id" ] && detail="${detail} id=${task_id}"
+ [ -n "$desc" ] && detail="${detail} desc=\"${desc:0:120}\""
+ ;;
+ *)
+ exit 0
+ ;;
+esac
+
+STATE_DIR="${cwd_dir}/.claude/superpowers-state"
+mkdir -p "$STATE_DIR" 2>/dev/null || exit 0
+STATE_FILE="${STATE_DIR}/in-progress.jsonl"
+LOCK_FILE="${STATE_DIR}/.in-progress.lock"
+
+ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+entry=$(jq -nc \
+ --arg ts "$ts" \
+ --arg tool "$tool_name" \
+ --arg detail "$detail" \
+ '{ts: $ts, tool: $tool, detail: $detail}' 2>/dev/null || true)
+[ -z "$entry" ] && exit 0
+
+# Append + cap under a lock so concurrent invocations from lead + subagents
+# can't corrupt the JSONL during the rotate step. POSIX `O_APPEND` makes the
+# raw append safe across processes already; the rotate step is what needs
+# serialization (tail-to-tmp + mv races can clobber the file).
+append_line() {
+ printf '%s\n' "$entry" >> "$STATE_FILE" 2>/dev/null || return 0
+}
+
+# Mutex-protected rotate. Uses flock when available; falls back to
+# `mkdir` (atomic on POSIX) so platforms without util-linux flock
+# (notably macOS) still serialize rotation correctly.
+rotate_if_needed() {
+ [ -f "$STATE_FILE" ] || return 0
+ [ "$(wc -l < "$STATE_FILE" 2>/dev/null || echo 0)" -gt 200 ] || return 0
+ # Use a PID-tagged tmp so concurrent rotates don't clobber each other's tmp.
+ local tmp="${STATE_FILE}.tmp.$$"
+ tail -n 200 "$STATE_FILE" > "$tmp" 2>/dev/null \
+ && mv "$tmp" "$STATE_FILE" 2>/dev/null
+ rm -f "$tmp" 2>/dev/null || true
+}
+
+append_line
+
+if command -v flock >/dev/null 2>&1; then
+ (
+ flock -w 5 9 || exit 0
+ rotate_if_needed
+ ) 9>"$LOCK_FILE"
+else
+ # mkdir is an atomic mutex on POSIX; only one rotater wins, others bail.
+ if mkdir "${LOCK_FILE}.d" 2>/dev/null; then
+ trap 'rmdir "${LOCK_FILE}.d" 2>/dev/null || true' EXIT
+ rotate_if_needed
+ rmdir "${LOCK_FILE}.d" 2>/dev/null || true
+ trap - EXIT
+ fi
+fi
+
+exit 0
diff --git a/hooks/session-start b/hooks/session-start
index 0eba96b..7c5755f 100755
--- a/hooks/session-start
+++ b/hooks/session-start
@@ -1,25 +1,108 @@
#!/usr/bin/env bash
# SessionStart hook for superpowers plugin
+#
+# On every session start (startup|resume|clear|compact) this loads the
+# using-superpowers skill so the skills system is available before the first
+# turn. On compact|resume it ALSO injects a "resumption context" block that:
+# - replays recent superpowers activity from .claude/superpowers-state/in-progress.jsonl
+# (so a lead orchestrator knows which skills + subagents were in flight), and
+# - extracts the first user message from the session transcript
+# (so a subagent that just compacted can re-read its original task).
set -euo pipefail
-# Determine plugin root directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
-# Check if legacy skills directory exists and build warning
+# Read hook payload from stdin (JSON). Tolerate empty/no-stdin invocations.
+hook_input=""
+if [ ! -t 0 ]; then
+ hook_input=$(cat || true)
+fi
+[ -z "$hook_input" ] && hook_input='{}'
+
+source_kind=""
+transcript_path=""
+cwd_dir="${PWD}"
+if command -v jq >/dev/null 2>&1; then
+ source_kind=$(printf '%s' "$hook_input" | jq -r '.source // empty' 2>/dev/null || true)
+ transcript_path=$(printf '%s' "$hook_input" | jq -r '.transcript_path // empty' 2>/dev/null || true)
+ cwd_from_hook=$(printf '%s' "$hook_input" | jq -r '.cwd // empty' 2>/dev/null || true)
+ [ -n "$cwd_from_hook" ] && cwd_dir="$cwd_from_hook"
+fi
+
+STATE_DIR="${cwd_dir}/.claude/superpowers-state"
+STATE_FILE="${STATE_DIR}/in-progress.jsonl"
+
+# Wipe stale state at the start of a brand-new (or cleared) session.
+# If the session source can't be determined (e.g. jq is unavailable) treat
+# it as a fresh session and fail closed, so stale activity from a prior
+# session can't leak into the resumption context.
+if [ -z "$source_kind" ] || [ "$source_kind" = "startup" ] || [ "$source_kind" = "clear" ]; then
+ rm -f "$STATE_FILE"
+fi
+
+# Build the resumption context (only on compact|resume).
+resumption_section=""
+if { [ "$source_kind" = "compact" ] || [ "$source_kind" = "resume" ]; } && command -v jq >/dev/null 2>&1; then
+ activity=""
+ if [ -f "$STATE_FILE" ]; then
+ activity=$(tail -n 30 "$STATE_FILE" \
+ | jq -r '"- " + .ts + " " + .tool + " " + (.detail // "")' 2>/dev/null \
+ || tail -n 30 "$STATE_FILE")
+ fi
+
+ original_task=""
+ if [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
+ original_task=$(jq -rs '
+ map(select((.type // "") == "user")) |
+ (first // empty) as $first |
+ ($first.message.content // $first.content // "") |
+ if type == "string" then .
+ elif type == "array" then ([.[] | select((.type // "") == "text") | (.text // "")] | join("\n"))
+ else "" end
+ ' "$transcript_path" 2>/dev/null || true)
+ original_task="${original_task:0:4000}"
+ fi
+
+ if [ -n "$activity" ] || [ -n "$original_task" ]; then
+ resumption_section=$'\n\n'
+ case "$source_kind" in
+ compact) source_phrase="just had a context-compaction event" ;;
+ resume) source_phrase="just resumed from a previous transcript" ;;
+ *) source_phrase="was just initialized with source=${source_kind}" ;;
+ esac
+ resumption_section+=$'\nThe session '"$source_phrase"$'. BEFORE answering the next message, reorient yourself using the context below. If you were mid-pipeline (brainstorming → writing-plans → alignment-check → subagent-driven-development → finishing-a-development-branch → pr-monitoring), pick up where you left off rather than re-deriving intent.'
+ if [ -n "$original_task" ]; then
+ # The first user message can contain arbitrary markup (tag-like
+ # tokens, even a literal ""),
+ # which would terminate or nest inside the resumption block we
+ # just opened. Wrap it in a fenced code block and pick a fence
+ # length that does not appear inside the content.
+ fence='```'
+ while printf '%s' "$original_task" | grep -qF "$fence"; do
+ fence="${fence}\`"
+ done
+ resumption_section+=$'\n\n## Original task (first user message in this session)\n\n'"${fence}"$'\n'"${original_task}"$'\n'"${fence}"
+ fi
+ if [ -n "$activity" ]; then
+ resumption_section+=$'\n\n## Recent superpowers activity (most recent 30 events)\n\n'"$activity"
+ resumption_section+=$'\n\nIf any of those entries are dispatched subagents, check their status (still active? errored? rate-limited? zombied?) using your host\'s task-listing mechanism before proceeding. See superpowers:subagent-driven-development for the watchdog cadence and host-specific tools.'
+ fi
+ resumption_section+=$'\n'
+ fi
+fi
+
+# Legacy skills warning (preserved from upstream).
warning_message=""
legacy_skills_dir="${HOME}/.config/superpowers/skills"
if [ -d "$legacy_skills_dir" ]; then
- warning_message="\n\nIN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read. Move custom skills to ~/.claude/skills instead. To make this message go away, remove ~/.config/superpowers/skills"
+ warning_message=$'\n\nIN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:⚠️ **WARNING:** Superpowers now uses Claude Code\'s skills system. Custom skills in ~/.config/superpowers/skills will not be read. Move custom skills to ~/.claude/skills instead. To make this message go away, remove ~/.config/superpowers/skills'
fi
-# Read using-superpowers content
using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill")
# Escape string for JSON embedding using bash parameter substitution.
-# Each ${s//old/new} is a single C-level pass - orders of magnitude
-# faster than the character-by-character loop this replaces.
escape_for_json() {
local s="$1"
s="${s//\\/\\\\}"
@@ -32,12 +115,10 @@ escape_for_json() {
using_superpowers_escaped=$(escape_for_json "$using_superpowers_content")
warning_escaped=$(escape_for_json "$warning_message")
-session_context="\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${using_superpowers_escaped}\n\n${warning_escaped}\n"
+resumption_escaped=$(escape_for_json "$resumption_section")
+
+session_context="\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${using_superpowers_escaped}\n\n${warning_escaped}${resumption_escaped}\n"
-# Output context injection as JSON.
-# Keep both shapes for compatibility:
-# - Cursor hooks expect additional_context.
-# - Claude hooks expect hookSpecificOutput.additionalContext.
cat <
+
+Hooks automate it. The plugin's `SessionStart` hook (matcher `compact|resume`) fires inside the compacted session and injects a `` block containing:
+
+- The first user message from the transcript (the "original task") — this is what re-anchors a compacted **subagent** to its assignment.
+- Recent superpowers activity (last 30 entries from `.claude/superpowers-state/in-progress.jsonl`) — this is what re-anchors the **lead** in the pipeline.
+
+Activity is captured by a `PostToolUse` hook (matcher `Skill|Agent|Task.*`) that appends each invocation to the JSONL state file (capped at 200 lines; wiped on `startup|clear` or when the session source can't be determined). The state file is project-local at `.claude/superpowers-state/in-progress.jsonl`.
+
+You don't opt in. When you see the resumption block, treat it as authoritative and reorient before responding.
+
+
+
+
+
+Hooks are not documented on this host. Apply the pattern manually: at the start of every reply that follows a context compaction, scroll back to the first user message in your visible transcript, re-state the task to yourself, then proceed. If you keep a running activity log in your scratch context, re-read it before issuing new subagent instructions.
+
+
+
+### 2. Watchdog cadence (every 5–10 minutes)
+
+When you have one or more subagents running in the background, check on them on a 5–10 minute cadence. Don't fire-and-forget.
+
+**On each check, verify:**
+- Still running? A hung/zombied agent might not have produced any signal — explicit status confirms it's still active.
+- Producing output? An agent that has stopped emitting tool calls or text for >5 minutes is suspect.
+- Errored? Look for API errors, rate limits, transport failures, "context length exceeded" — these often surface as a stalled output stream rather than a crash.
+- Off-track? Spot-check that the latest output is actually progressing the assigned task, not flailing on a tangent.
+
+**If a subagent looks stuck:**
+- Send a corrective message to redirect (e.g. "you have been silent for 7 minutes; report current state and the next concrete step you will take").
+- If unrecoverable: terminate it, then re-dispatch a fresh subagent with the same brief plus a one-line note about what the prior attempt got wrong.
+
+
+
+Use `TaskList` to confirm active subagents, `TaskOutput` to read recent stdout, `SendMessage` (with `to: `) to send corrective input, and `TaskStop` to terminate. When using `ScheduleWakeup` to pace a `/loop` self-paced run, factor watchdog checks into the cadence — don't sleep past your next check window.
+
+
+
+
+
+Codex doesn't expose a structured task list. Track dispatched subagents in your own scratch context (one line per subagent: id, started-at, last-checked-at, current-stage). On each watchdog interval, ask each subagent for a status report directly via its thread; "report your current step and what you plan to do next, in one paragraph" forces it to surface progress or admit it's stuck.
+
+
+
+
+
+Use `@mention` to peer sessions to ping each background subagent for a status report on cadence. If a peer has gone silent past the check window, treat it as suspect.
+
+
+
+### 3. Quality-based rotation (replace consistently-failing teammates)
+
+Subagents are teammates, not infrastructure. If one keeps producing low-quality output, replace it instead of re-prompting forever.
+
+**Track per subagent_type, per session:**
+- Number of code-review rejections it produced (spec compliance OR code quality stage).
+- Number of times you had to send corrective input to redirect it.
+- Number of test/build failures attributable to its work.
+
+**Rotation triggers:**
+- 2 consecutive code-review rejections on the same task → switch to a different `subagent_type` for the retry, or escalate to a higher model tier (see `agents/model-tiers.md` for the role-to-host model mapping).
+- 3 cumulative quality issues across tasks in one session → stop dispatching that subagent_type for the remainder of the session; re-route its work to an alternative.
+- A subagent that ignores explicit guidance twice in a row → stop using it; the issue is the agent profile, not the prompt.
+
+When you rotate, briefly state the rotation in user-facing text ("Rotating off `general-purpose` for review tasks — two consecutive rejections; using `superpowers:code-reviewer` instead") so the user has a chance to redirect.
+
+### Why these patterns
+
+The hook automation handles compaction recovery deterministically on hosts that support it. The watchdog cadence and rotation rules rely on you applying them consistently — without them, a subagent that compacts, hits a transient API error, or quietly drifts off-task can burn 30+ minutes before anyone notices.