npx claudepluginhub adamfeldman/compound-workflows --plugin compound-workflowsThis skill uses the workspace's default tool permissions.
Recovers context from a dead or exhausted Claude Code session by reading its JSONL session log, cross-referencing external state, and producing a structured recovery manifest. This is the **reactive** counterpart to `/do:compact-prep` (proactive).
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Recovers context from a dead or exhausted Claude Code session by reading its JSONL session log, cross-referencing external state, and producing a structured recovery manifest. This is the reactive counterpart to /do:compact-prep (proactive).
<session_id> #$ARGUMENTS </session_id>
The session log directory uses the project's absolute path with / replaced by -:
SESSION_DIR="$HOME/.claude/projects/${PWD//\//-}"
echo "Session log directory: $SESSION_DIR"
ls "$SESSION_DIR"/*.jsonl 2>/dev/null | wc -l
If the directory does not exist or contains zero .jsonl files, tell the user: "No session logs found for this project at $SESSION_DIR. This command requires Claude Code session logs to exist." Then stop.
which bd 2>/dev/null && echo "BEADS=available" || echo "BEADS=not_available"
Note beads availability for Phase 3.
git worktree list --porcelain | head -1 | sed 's/^worktree //'
Set WORKFLOWS_ROOT=<main-root>/.workflows. All .workflows/ paths in this skill use $WORKFLOWS_ROOT, NOT relative .workflows/. This ensures artifacts are found at the correct location regardless of whether you are in a worktree or the main checkout.
If <session_id> is non-empty, skip to Phase 2 using that session ID to locate the .jsonl file at $SESSION_DIR/<session_id>.jsonl. If the file does not exist, tell the user: "No session log found for session ID <session_id>." Then stop.
If <session_id> is empty, proceed to Phase 1 (Session Discovery).
Build a picker showing recent sessions for the user to choose from.
List the 10 most recent .jsonl files by modification time:
ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -10
For each file, extract metadata via targeted JSONL parsing. Do not load full message content — parse only structural fields to keep context lean.
For each .jsonl file, use bash to extract the needed fields. Parse each line as JSON and collect:
.jsonl extension)slug field (first non-null occurrence)type is "user" and message.content contains a text block (not tool_result) and the entry does NOT have isMeta: true. Extract first 80 characters of the text content as a preview.timestamp of the first entry and the last entry (ISO format)type is "system" and the message contains compact_boundary or compactMetadata. Note the preTokens value from the last such entry.assistant type entry (session ended mid-assistant-turn, suggesting exhaustion or crash)Parsing approach: Use jq or line-by-line JSON parsing. For large files, avoid reading the entire file into memory. Use head and tail with line counts to extract the first and last portions. Example approach:
# Get first entry timestamp and slug
head -1 "$FILE" | jq -r '[.timestamp, .slug] | @tsv'
# Get last entry timestamp and type
tail -1 "$FILE" | jq -r '[.timestamp, .type] | @tsv'
# Get first user text message (scan first ~200 lines)
head -200 "$FILE" | jq -r 'select(.type == "user" and .isMeta != true) | .message.content[]? | select(.type == "text") | .text' 2>/dev/null | head -1 | cut -c1-80
# Get last user text message (scan last ~200 lines)
tail -200 "$FILE" | jq -r 'select(.type == "user" and .isMeta != true) | .message.content[]? | select(.type == "text") | .text' 2>/dev/null | tail -1 | cut -c1-80
# Count compact boundaries
grep -c '"compact_boundary"' "$FILE" 2>/dev/null || echo "0"
# Check if last entry is assistant (exhaustion heuristic)
tail -1 "$FILE" | jq -r '.type' 2>/dev/null
Adapt the parsing as needed for the actual JSONL structure — the key constraint is do not load full content, only structural fields and short previews.
Format each session as a numbered entry:
N. [slug] — "first user message preview..." (time ago, M compactions) [flags]
Last activity: "last user message preview..."
Flags:
POSSIBLE EXHAUSTION — if session ended mid-assistant-turnCURRENT SESSION — if the session ID matches the current session (warn the user that recovery is for dead sessions)Present the formatted list via AskUserQuestion: "Which session would you like to recover? Enter the number."
After the user selects a session (or if they ask to see more), offer via AskUserQuestion: "Would you like to see more sessions?"
head -20head -50head limitOnly offer this after the initial 10 are shown. If the user already selected a session number, skip this and proceed to Phase 2.
Parse the selected session's JSONL file using a head + tail strategy. The goal is to extract enough context for recovery without exhausting the current session's context.
Context budget: 50KB total extraction. 2KB max per individual entry.
Extract the first 5 intent-bearing user entries. An "intent-bearing" entry meets ALL of these criteria:
type is "user"isMeta is NOT truemessage.content contains at least one block with type: "text" (not only tool_result blocks)For each qualifying entry, extract:
text content (truncated to 2KB)timestampThese capture the original intent — what the user asked the session to do.
# Example: extract first 5 intent-bearing user messages
# Scan the file line by line, filter for qualifying entries, take first 5
# Truncate text content to 2048 characters per entry
Locate the last compact_boundary entry in the file:
grep -n '"compact_boundary"' "$FILE" | tail -1
If a compact boundary exists, extract all intent-bearing entries (same criteria as Step 2.1) from that line forward, taking the last 30. If no compact boundary exists, extract the last 30 intent-bearing entries from the end of the file.
For each qualifying entry, extract:
text content (truncated to 2KB)timestamptype (user or assistant — include assistant text entries in the tail for conversation flow)Include both user and assistant entries in the tail (both with text content, excluding tool_use/tool_result blocks that contain file contents). This captures the conversation flow, not just user messages.
Total tail budget: 50KB. If the extracted entries exceed this, truncate from the oldest entries first.
Scan for the last active command invocation.
Dual-namespace detection: Session logs from before v3.0.0 use
/compound:*command names; logs from v3.0.0+ use/do:*. Search for BOTH patterns to handle old and new sessions.
user entries with isMeta: true — these are command invocations<command-name> tags — these mark command execution/do:work, /do:brainstorm, or pre-v3.0.0 /compound:work, /compound:brainstorm)If a command is detected, infer its phase by looking at subsequent activity:
Note the last detected command and its inferred phase. If multiple commands were invoked in the session, track the LAST one (most relevant for recovery) and note prior ones as completed context.
Find AskUserQuestion interactions:
assistant entries containing tool_use with name: "AskUserQuestion" — extract the question textuser entry with a tool_result for that tool_use_id — extract the user's responseCollect all Q&A pairs. These are the user's explicit decisions during the session.
Find Read, Write, and Edit tool_use calls in assistant entries:
file_path parameter from eachThis identifies which files the session was actively working on.
Find tool_result entries in user messages where is_error is true:
These identify failures that may have contributed to session death.
Find Agent and Task tool_use calls in assistant entries:
run_in_background was trueThis identifies background work that may or may not have completed.
Check each recovery source to build a picture of the project state at recovery time.
ls -lt $WORKFLOWS_ROOT/brainstorm-research/ $WORKFLOWS_ROOT/plan-research/ $WORKFLOWS_ROOT/deepen-plan/ $WORKFLOWS_ROOT/compound-research/ $WORKFLOWS_ROOT/code-review/ $WORKFLOWS_ROOT/work-review/ $WORKFLOWS_ROOT/recover/ 2>/dev/null
For directories modified within the dead session's time range (from Phase 2 timestamps), note:
status fieldIf $WORKFLOWS_ROOT does not exist, note "No .workflows/ directory found at $WORKFLOWS_ROOT" and skip.
If beads is available (from Phase 0):
bd list --status=in_progress 2>/dev/null
bd list --status=open 2>/dev/null | head -5
If beads is not available, note "Beads: not available" and skip.
git status --short
git log --oneline -10
git stash list
Note uncommitted changes, recent commits (especially those within the session's time range), and any stashes.
ls -lt docs/plans/*.md 2>/dev/null | head -5
For recent plan files (modified within the session's time range), read the YAML frontmatter and check for:
status: active- [ ] itemsCheck if the session log contains compact_boundary entries with activity both before and after them. If so, note that compact-prep likely ran during the session — meaning memory was updated and work was committed at that point. Activity after the last compact boundary is the unpreserved context.
Write three files to $WORKFLOWS_ROOT/recover/<session-id>/. Create the directory if it does not exist. Overwrite if prior recovery exists (recovery is idempotent — external state may have changed).
mkdir -p $WORKFLOWS_ROOT/recover/<session-id>
summary.md# Recovery Summary: [session slug or "untitled"]
**Session:** [session-id]
**Time range:** [first timestamp] to [last timestamp]
**Compactions:** [N] (last at [preTokens] tokens)
**Status:** [POSSIBLE EXHAUSTION / normal end / compact-prep ran before end]
**Branch:** [gitBranch from session, note if different from current branch]
## What Was Happening
[Synthesize from head + tail extracts:
- Original intent (from head): what did the user ask to do?
- Last active task (from tail): what was happening when the session died?
- Active command/phase if detected
Keep this to 3-5 sentences — enough to orient a new session.]
## Key Decisions Made
[AskUserQuestion Q&A pairs from Phase 2.4. Format as:]
- **Q:** [question] **A:** [user's response]
[If no decisions found: "No explicit decisions detected in the session log."]
## Files Being Worked On
[Deduplicated file paths from Phase 2.5, grouped by action:]
- **Written/Edited:** [files]
- **Read:** [files]
[If no file activity found: "No file operations detected."]
## External State
- **Beads:** [N in_progress issues, M open issues | not available]
- **Git:** [uncommitted changes summary | clean working tree]
- **Stashes:** [list | none]
- **$WORKFLOWS_ROOT/:** [active artifacts found with types | none | no directory]
- **Plans:** [active plans with unchecked items | none found]
## Recommended Next Step
[If compound command detected:]
"Resume `/do:[command]` — the session was in Phase [N] ([phase description]).
To resume, run: /do:[command] [arguments]
[If the command has built-in recovery (deepen-plan): note it will auto-detect the interrupted state.]
[If the command does not have built-in recovery: note the user starts fresh with recovery context available on disk.]"
[If interactive work (no command):]
"Continue working on [topic summary]. Recovery context is available at $WORKFLOWS_ROOT/recover/<session-id>/."
[If clean — no interrupted work detected:]
"No interrupted work detected. The session appears to have ended normally."
session-extract.md# Session Extract: [session-id]
## Original Intent (Head)
[First 3-5 user messages from Phase 2.1, preserving the user's words.
Each entry prefixed with its timestamp.
Truncated entries noted with "[truncated]".]
## Recent Context (Tail from last compaction)
[Last conversation exchanges from Phase 2.2.
Include both user and assistant text entries to show conversation flow.
Each entry prefixed with its timestamp and role (User/Assistant).
Summarize rather than quote if entries are very long.]
## Active Command
[Detected /do:* (or pre-v3.0.0 /compound:*) command and inferred phase from Phase 2.3, or:]
"No compound command detected — interactive session."
[If multiple commands were invoked, note prior ones as completed.]
## Decisions
[AskUserQuestion Q&A pairs from Phase 2.4:]
- **Q:** [question text]
**A:** [user response]
[If none: "No AskUserQuestion interactions found."]
## Errors
[Tool errors from Phase 2.6:]
- [timestamp] [tool name]: [brief error summary]
[If none: "No tool errors found."]
## Subagents
[Agent/Task dispatches from Phase 2.7:]
- [description preview] — [completed | possibly incomplete | background]
[If none: "No subagent dispatches found."]
state-snapshot.md# State Snapshot: [current ISO timestamp]
Captured at recovery time — reflects current state, not session-time state.
## Beads
[bd list output from Phase 3.2, or:]
"Beads not available."
## Git
### Status
[git status --short output from Phase 3.3]
### Recent Commits
[git log --oneline -10 output]
### Stashes
[git stash list output, or "No stashes."]
## $WORKFLOWS_ROOT/ Artifacts
[Recently modified directories and their contents from Phase 3.1, or:]
"No .workflows/ directory found at $WORKFLOWS_ROOT."
## Active Plans
[Plans with status: active and unchecked item counts from Phase 3.4, or:]
"No active plans found."
Present the content of summary.md directly to the user. Do not just say "file written" — show the actual recovery summary so the user has immediate context.
Tell the user: "Full recovery manifest written to $WORKFLOWS_ROOT/recover/<session-id>/ (summary.md, session-extract.md, state-snapshot.md)."
If a /do:* (or pre-v3.0.0 /compound:*) command was detected:
Tell the user: "The session was running /do:[command] [with arguments if detected]. To resume, run:"
/do:[command] [arguments]
Note whether the command supports auto-recovery:
/do:deepen-plan — detects interrupted manifests and resumes automatically$WORKFLOWS_ROOT/recover/<session-id>/Use AskUserQuestion: "What would you like to do?"
If interactive work (no command detected):
Use AskUserQuestion: "What would you like to do?"
This fills the gap when /compact-prep never ran before the session died — decisions and rationale would otherwise be lost.
Review the session extract (from Phase 2) for:
If memory-worthy content is found, present it via AskUserQuestion:
"The dead session contained decisions/rationale that may be worth persisting to memory:
Update memory files with these?"
If no memory-worthy content is detected, skip this phase silently. Do not ask the user about it.
After JSONL-based session recovery, check for orphaned session worktrees that may contain uncommitted or unmerged work from prior sessions.
git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'
Read the output as DEFAULT_BRANCH. If the command fails (no remote HEAD configured), use main as the fallback.
git worktree list
Parse the output. Filter for entries whose path contains .worktrees/session-. Exclude the current working directory if it is already a session worktree. Track the remaining entries as orphaned session worktrees.
If no session worktrees are found (other than possibly the current CWD), skip to Step 6.4 (Orphan Branch Detection).
If more than 5 session worktrees are found, announce: "N session worktrees found. Showing first 5 — consider manual cleanup for the rest." Process only the first 5.
For each session worktree (one at a time), gather information:
git -C <path> branch --show-current
git -C <path> status --short | wc -l
git log <DEFAULT_BRANCH>..<branch> --oneline | wc -l
For last modified time, use stat -f '%Sm' <path> on macOS or stat -c '%y' <path> on Linux. Run uname first if the platform is unknown.
Present each worktree to the user via AskUserQuestion:
"Found session worktree <name> (branch: <branch>, N uncommitted files, M unmerged commits, last active: ). What would you like to do?"
Options:
/do:merge <branch> to merge into <DEFAULT_BRANCH>." Output the exact command string for the user to copy-paste. Do not invoke it programmatically.git -C <path> status and git -C <path> log --oneline -5 output, then re-present the same AskUserQuestion with the same options.bd worktree remove .worktrees/session-<name> to clean up.Check for branches matching the session worktree naming pattern that have no corresponding worktree directory. These may contain committed data that was never merged back.
git branch --list 'session-*'
git worktree list
Compare the two outputs: any branch in the session-* list that does NOT have a corresponding live worktree is an orphan branch. For each orphan branch found:
git log <DEFAULT_BRANCH>..<orphan-branch> --oneline | wc -l
If orphan branches with unmerged commits exist, present them via AskUserQuestion:
"Found N orphan branch(es) with no corresponding worktree (likely from crashed sessions):
<branch>: M unmerged commits
[repeat for each]These branches contain committed work that was never merged. What would you like to do?"
Options:
/do:merge <branch> for each branch." Output the exact command strings.git branch -D <branch> for each.Add a ## Worktrees section to the state-snapshot.md file (Phase 4, File 3) with:
## Worktrees
### Session Worktrees
[List of session worktrees found in .worktrees/session-*, with branch, uncommitted file count, unmerged commit count, and action taken (merged/discarded/skipped), or:]
"No session worktrees found."
### Orphan Branches
[List of session-* branches with no corresponding worktree directory, with unmerged commit count and action taken, or:]
"No orphan branches found."
Also add a worktree summary line to summary.md (Phase 4, File 1) in the External State section:
- **Worktrees:** [N session worktrees (M merged, K discarded, J skipped), P orphan branches | no session worktrees found]
Handle these throughout execution:
/compound-workflows:recover is designed for dead sessions — recovering a live session may produce incomplete results." Offer to proceed anyway or pick a different session.gitBranch field from the JSONL in the summary: "Note: This session was on branch [branch], which differs from the current branch [current]."cwd field if it differs from current pwd: "Note: This session's working directory was [cwd], which differs from the current directory."$WORKFLOWS_ROOT directory: Skip artifact checks. Note "No .workflows/ directory found at $WORKFLOWS_ROOT" in the state snapshot. No error.