Every script lives in scripts/ at the plugin root and is invoked as ${CLAUDE_PLUGIN_ROOT}/scripts/<name>. Two skills carry their own scripts instead: skills/init/scripts/init.sh and skills/setup/scripts/setup-checks.sh. -h/--help prints full usage on every script.
- Input: almost every script takes a bead id as an argument.
tasks.py'sepic,parents-to-closeandworkersubcommands take the task graph as JSON piped on stdin (bd list --all --limit 0 --json | tasks.py epic <id>) instead of as an argument, because Linux caps a single command-line argument at 128 KiB, which this project's bead history can exceed. - Exit codes:
0success.2invalid arguments, or a bead/state the script refuses to act on (a usage problem, not a runtime failure).1a runtime failure the caller can retry or fix (a failedbdcall, nothing to record, a verify or metrics check that failed).finish-task.pyalone uses3for a problem only a person can resolve; see messages. - Shared helpers:
lib.shis sourced, never run directly. It definesshow,get,config,childrenandworktree_of, used by most of the bash scripts. - Shared task-graph logic:
tasks.pyis both a CLI (see below) and a Python module imported byfinish-task.py,supervise.py,workers.py,stats.pyandnext-tasks.py, so dispatch order, epic lookup and "is this worker running" share one definition.
All under <git-common-dir>/sdlc/, so worktrees on the same repository share them:
| Path | Written by | Holds |
|---|---|---|
logs/<task>-<session>.jsonl |
start-worker.sh (worker stdout), appended by the running claude -p |
the worker's stream-json transcript, plus a {"type":"dispatch_run",...} marker line at the start of each run (a resume adds another) |
logs/<task>-<session>.jsonl.err |
start-worker.sh |
the worker process's stderr |
logs/<task>-<session>.jsonl.wt |
run-task.sh |
stderr of the wt switch that created the worktree (present for every start, whether or not it failed); its mtime is the task's start time, used by workers.py for "started ... ago" |
logs/<task>-<session>.jsonl.record |
start-worker.sh's wrapper |
the output of the record-task.sh call made once the worker process ends |
logs/<task>-<session>.jsonl.remove |
record-task.sh |
wt remove's stderr, kept only when removing a merged task's worktree fails |
logs/<task>-<session>.review-<N>.json, .review-<N>.err |
finish-task.py (agent_review) |
round <N>'s reviewer session output and stderr, for review level agent |
wake |
created by supervise.py (a FIFO) |
wakes the sleeping supervisor; written by the beads on_create/on_update/on_close hooks and by start-worker.sh's wrapper once a worker ends |
supervise.lock |
supervise.py |
flock, holding <pid> <generation> <argv>; a newer generation can take over a running supervisor, an older or equal one is refused |
supervise.log |
supervise.py |
one line per event handled, actions taken, and anything the previous sweep's snapshot missed |
Separately, a worker's own Claude Code transcript is ~/.claude/projects/<escaped-worktree-path>/<session>.jsonl, found by workers.py's find_transcript and read by claude --resume <session> --fork-session.
| Script | Does | Called by | Input | Writes |
|---|---|---|---|---|
create-task.sh |
validates and creates one bead (task or epic) with its metadata | /sdlc:split, /sdlc:create-task, analyse-branch.sh |
flags: --title, --description, --type, --parent, --acceptance, --scope (optional), --domains (optional), --verify, --complexity, --design, --after (repeatable), --priority, --agent, --model, --effort, --review, --role, --set KEY=VALUE (repeatable) |
the bead; blocks edges from --after; a dep add onto the parent epic's integration task, if one exists |
run-task.sh |
claims an open task, creates its worktree, starts its worker, returns | supervise.py (start action) |
task id | claims the task (dispatch_session, dispatch_host, dispatch_base, dispatch_branch, dispatch_started); on an epic's first dispatch, the epic branch and its integration task; the worktree (wt switch --create); hands off to start-worker.sh |
start-worker.sh |
builds and starts the claude -p worker command, detached |
run-task.sh, resume-task.sh |
task id, --resume, --prompt TEXT (required) |
the log's dispatch_run marker; the detached worker process (setsid) with BASH_MAX_TIMEOUT_MS and BASH_DEFAULT_TIMEOUT_MS set to 5400000; once it ends, calls record-task.sh and writes to the wake pipe; the branch's worktrunk marker (running) |
finish-task.py |
reviews, rebases, verifies, merges and closes; see task lifecycle | the worker itself, via its one allowed Bash(finish-task.py *) tool |
task id | sdlc/finish-<task>.lock (holds the pid; a second run for the task exits 1, a stale lock is taken over), dispatch_merge_commit (set then cleared around the merge), dispatch_review*, dispatch_state (awaiting-review, pr-opened, merged), dispatch_pr; the merge itself, the closed bead(s), a gh:pr or human gate |
record-task.sh |
records a worker attempt once its process has ended | start-worker.sh's wrapper (normal end); supervise.py (record/fail actions, for a crashed worker) |
task id, or --failed REASON |
dispatch_state, dispatch_cost, dispatch_model, dispatch_agents; a closed event bead; removes the worktree of a merged or pr-opened task (unless untracked files remain); the branch's worktrunk marker |
| Script | Does | Called by | Input | Writes |
|---|---|---|---|---|
resume-task.sh |
resumes a crashed worker's Claude session, detached, in its existing worktree | supervise.py (resume action), /sdlc:recover |
task id, --prompt TEXT, --ended |
releases the merge queue; hands off to start-worker.sh --resume, or to record-task.sh directly if the log already ended with a result |
resume-reviewed.sh |
resumes a task once its human review gate has closed | supervise.py (resume_reviewed action) |
task id | clears dispatch_state, then as resume-task.sh --ended; restores dispatch_state=awaiting-review and the marker if the resume itself fails |
stop-task.sh |
ends a task's (or every task under an epic's) running worker | /sdlc:stop |
task or epic id | dispatch_state=stopped (or merged, if the worker finished first); TERM then KILL to the whole process tree; releases the merge queue; a comment; the branch's worktrunk marker |
reset-task.sh |
undoes a dispatch entirely, back to a fresh open task | /sdlc:dispatch (reopening a task it can't start), /sdlc:split (a stopped/failed bead before splitting), /sdlc:recover |
task id | releases the merge queue; wt remove -D the worktree and branch; clears the marker; reopens the bead with every dispatch_* key unset |
close-prs.sh |
closes an epic-pr integration task, and its epic, once the pull request is merged |
supervise.py (close_pr action) |
task id | closes the task and, once all its epic's tasks are closed, the epic; or dispatch_state=stopped with a comment, if the pull request closed unmerged |
| Script | Does | Called by | Input | Writes |
|---|---|---|---|---|
merge-queue.sh |
one queue bead per branch, so merges into it serialize across every machine | finish-task.py (acquire/release around the merge), run-task.sh/verify.sh (ensure), reset-task.sh/stop-task.sh/clean.sh (release) |
ensure |acquire|release <branch> [holder] [--timeout SECONDS] |
the queue bead; bd kv dispatch.queue.<branch> |
verify.sh |
runs the checks that finishing a task, parent task or epic would run | finish-task.py, /sdlc:verify |
[--no-pre-merge] <task|parent|epic-id> |
nothing (read-only); a failing check's stderr carries a reproduce command and the project's debug hint |
mark-branch.sh |
sets or clears a task branch's worktrunk state marker (an emoji wt shows in wt list) |
finish-task.py, record-task.sh, reset-task.sh, stop-task.sh, clean.sh, start-worker.sh |
<branch> <state> |
the marker, via wt config state marker; never fails the caller |
| Script | Does | Called by | Input | Writes |
|---|---|---|---|---|
workers.py |
lists dispatched, non-closed tasks with where their worker runs, and the crash evidence for a crashed one | /sdlc:status, /sdlc:stop, /sdlc:dispatch (display) |
--under ID (repeatable), --alive-count |
nothing (read-only) |
next-tasks.py |
lists ready tasks in dispatch order | /sdlc:status, /sdlc:dispatch (display) |
--under ID (repeatable), --ids |
nothing (read-only) |
logs.py |
renders a task's worker logs as readable lines, oldest first | /sdlc:logs, /sdlc:recover |
task id, --follow (needs a terminal), --raw, --last N |
nothing (read-only) |
stats.py |
reports cost, duration, models and agents per task and per epic, and cost per complexity and model, from event beads | /sdlc:stats |
[id] [--under ID] |
nothing (read-only) |
tasks.py |
shared task-graph queries (see Conventions); also a CLI | create-task.sh, stop-task.sh (piped bd list JSON) |
subcommands epic, parents-to-close, worker, under, log-totals (reads a log path directly, not stdin) |
nothing (read-only) |
| Script | Does | Called by | Input | Writes |
|---|---|---|---|---|
supervise.py |
the one process that starts, resumes, records and wakes a person; see dispatch | /sdlc:dispatch, run in the background |
--under ID (repeatable), --parallel N, --sweep SECONDS, --generation N, --running |
the lock, the log, the wake pipe; drives every other script in this page as needed |
| Script | Does | Called by | Input | Writes |
|---|---|---|---|---|
clean.sh |
lists (and, with --apply, removes) worktrees, branches and merge queues left behind |
/sdlc:clean |
[--apply] |
with --apply: wt remove -D, git branch -D, merge-queue.sh release, cleared markers |
| Script | Does | Called by | Input | Writes |
|---|---|---|---|---|
metrics.sh |
wraps lizard to report or check per-function complexity and length |
analyse-branch.sh, the sdlc:analyst agent, /sdlc:review (--changed) |
[--ccn N] [--length N] <path>..., or --changed <base>, or --check <path>... |
nothing (read-only) |
analyse-branch.sh |
checks a branch head against the thresholds, files a refactor epic and analysis task when it crosses one | supervise.py (analyse action, when custom.dispatch.analysis=auto), /sdlc:analyse |
branch name | bd kv dispatch.analysed.<branch>; a refactor epic and its analysis task (via create-task.sh) |
| Script | Does | Called by | Input | Writes |
|---|---|---|---|---|
settings.sh |
prints the effective dispatch settings, or the configured external doc sources | most skills (display), lib.sh's config, supervise.py, metrics.sh, analyse-branch.sh |
[key | sources] |
nothing (read-only); see settings |
| Script | Does | Called by | Input | Writes | Exit codes |
|---|---|---|---|---|---|
skills/init/scripts/init.sh |
prepares a repository for sdlc: beads init, beads config, .claude/sdlc.local.md/sdlc.md, .gitignore, the beads wake hooks |
/sdlc:init |
[--integration MODE] [--target BRANCH] [--parallel N] [--workflow build|none] |
the beads database, custom.dispatch.* config, .claude/sdlc.local.md, .claude/sdlc.md, .gitignore entries, .beads/hooks/{on_create,on_update,on_close} |
0 done, 1 a step failed, 2 invalid arguments, not a git repository, detached HEAD, or no commits yet |
skills/setup/scripts/setup-checks.sh |
checks this machine for the tools sdlc needs, one line per item | /sdlc:setup |
none | nothing (read-only) | always 0 |