swain-stage
Stage
Tmux workspace manager for swain. Creates pane layouts, manages an animated MOTD status panel, and gives the agent direct control over the visual workspace.
Prerequisite: Must be running inside a tmux session ($TMUX must be set). Before running any subcommand, the script checks for tmux — see Error handling below.
Script location
Swain project skills live under skills/ in the project root. For this skill, use:
skills/swain-stage/scripts/swain-stage.sh— main tmux layout and pane managerskills/swain-stage/scripts/swain-motd.py— MOTD status panel (Textual TUI, runs viauv run)skills/swain-stage/scripts/swain-motd.sh— legacy bash MOTD (kept as fallback if uv/Textual unavailable)skills/swain-stage/references/layouts/— layout presetsskills/swain-stage/references/yazi/— bundled Yazi config used whenfileBrowserresolves toyazi
Commands
Layout presets
Apply a named layout. Available presets are in skills/swain-stage/references/layouts/:
| Layout | Description |
|---|---|
| focus | Agent pane + MOTD top-right + file browser bottom-right |
| review | Agent + editor (changed files) + MOTD |
| browse | Agent + file browser + MOTD |
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" layout review
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" layout browse
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" layout focus
The default layout is configured in swain.settings.json under stage.defaultLayout (default: focus).
Users can override layout definitions in swain.settings.json under stage.layouts.<name>.
Open individual panes
Open a specific pane type without applying a full layout:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" pane editor file1.py file2.py # editor with specific files
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" pane browser # file browser at repo root
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" pane browser /some/path # file browser at specific path
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" pane motd # MOTD status panel
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" pane shell # plain shell
MOTD management
The MOTD pane shows a dynamic status panel with:
- Project name, branch, and dirty state
- Animated spinner when the agent is working (braille, dots, or bar style)
- Current agent context (what it's doing)
- Active epic with progress ratio (from swain-status cache)
- Active tk task
- Ready (actionable) artifact count
- Last commit info
- Assigned GitHub issue count
- Count of touched files
The MOTD is a Textual TUI app (swain-motd.py) launched via uv run. It reads project data from .agents/status-cache.json (written by swain-status) when available, falling back to direct git/tk queries when the cache is absent or stale (>5 min). Agent state (spinner, context) is always read from .agents/stage-status.json for real-time responsiveness. Textual handles Unicode width correctly, provides proper box drawing with rounded corners, and supports color theming.
State file locations:
.agents/status-cache.json— swain-status cache (epic progress, ready items, task data); written byswain-status.sh.agents/stage-status.json— live agent state (spinner activity, current context); written bystage-status-hook.sh
Reactive status via hooks: stage-status-hook.sh is configured as a Claude Code hook (PostToolUse, Stop, SubagentStart, SubagentStop) in .claude/settings.json. It writes .agents/stage-status.json automatically so the MOTD spinner reflects real agent activity without manual motd update calls.
Control the MOTD:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" motd start # start MOTD in a new pane
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" motd stop # kill the MOTD pane
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" motd update "reviewing auth module" # update context
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" motd update "idle" # mark as idle
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" motd update "done" # mark as done/idle
Close panes
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" close right # close the right pane
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" close bottom # close the bottom pane
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" close all # reset to single pane
Status
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" status # show current layout info
Reset
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" reset # kill all panes except current
Agent-triggered pane operations
The agent should use swain-stage directly during work. Recommended patterns:
After making changes — open review
When you've finished modifying files, open them for the user to review:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" motd update "changes ready for review"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" pane editor file1.py file2.py
During research — open file browser
When exploring the codebase:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" pane browser src/components/
Update context as you work
Keep the MOTD informed of what you're doing:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" motd update "analyzing test failures"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" motd update "writing migration script"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" motd update "done"
Clean up when done
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" close right
bash "$(find "$REPO_ROOT" -path '*/swain-stage/scripts/swain-stage.sh' -print -quit 2>/dev/null)" motd update "idle"
Settings
Read from swain.settings.json (project) and ~/.config/swain/settings.json (user override). User settings take precedence.
| Key | Type | Default | Description |
|---|---|---|---|
editor |
string | auto |
Editor command. auto detects: micro > helix > nano > vim |
fileBrowser |
string | auto |
File browser command. auto detects: yazi > nnn > ranger > mc |
stage.defaultLayout |
string | focus |
Layout applied by default |
stage.motd.refreshInterval |
number | 5 |
MOTD refresh interval in seconds (idle) |
stage.motd.spinnerStyle |
string | braille |
Spinner animation: braille, dots, or bar |
stage.layouts |
object | {} |
User-defined layout overrides (same schema as preset files) |
Error handling
- If tmux binary is not installed: the script exits with
"tmux not found". Offer to install it:"tmux is not installed. Install it now? I can run \brew install tmux` for you."If the user accepts, runbrew install tmux`, then re-run the original subcommand. - If tmux is installed but not in a tmux session (
$TMUXunset): the script exits with"tmux not active — swain-stage requires a tmux session. Start tmux first."Inform the user and do not offer to install (tmux is already present). - If editor/file browser is not installed: warn the user and suggest alternatives or
swain.settings.jsonoverride. - If the file browser resolves to
yazi, swain-stage injects the bundled config inskills/swain-stage/references/yazi/so text files open with the system default app and directory colors remain readable on dark terminals. - If jq is not available: warn that settings cannot be read, use hardcoded defaults.
- Pane operations are best-effort — if a pane can't be created or found, warn but don't fail the session.
More from cristoslc/swain
swain-do
Task tracking and implementation execution for swain projects. Invoke whenever a SPEC needs an implementation plan, the user asks what to work on next, wants to check or update task status, claim or close tasks, manage dependencies, abandon work, bookmark context, or record a decision. Also invoked by swain-design after creating a SPEC that's ready for implementation. Tracks SPECs and SPIKEs — not EPICs, VISIONs, or JOURNEYs directly (those get decomposed into SPECs first). Triggers also on: 'bookmark', 'remember where I am', 'record decision'.
125swain-update
Update swain skills to the latest version. Use when the user says 'update swain', 'upgrade swain', 'pull latest swain', 'reinstall swain', 'refresh skills', or wants to update their swain skills installation. Uses npx to pull the latest swain release from GitHub, with a git-clone fallback, then invokes swain-doctor to reconcile governance and validate project health.
122swain-release
Cut a release — detect versioning context, generate a changelog from conventional commits, bump versions, create a git tag, and optionally squash-merge to a release branch. Use when the user says "release", "cut a release", "tag a release", "bump the version", "create a changelog", "ship a version", "publish", or any variation of shipping/publishing a version. This skill is intentionally generic and works across any repo — it infers context from git history and project structure rather than assuming a specific setup. Supports the trunk+release branch model (ADR-013) when a `release` branch exists.
122swain-design
Create, validate, and transition documentation artifacts (Vision, Initiative, Epic, Spec, Spike, ADR, Persona, Runbook, Design, Journey) through lifecycle phases. Handles spec writing, feature planning, epic creation, initiative creation, ADR drafting, research spikes, persona definition, runbook creation, design capture, architecture docs, phase transitions, implementation planning, cross-reference validation, and audits. Also invoke to update frontmatter fields, re-parent an artifact under a different epic or initiative, or set priority on a Vision or Initiative. Chains into swain-do for implementation tracking on SPEC; decomposes EPIC/VISION/INITIATIVE/JOURNEY into children first.
122swain
Meta-router for swain skills. Invoke when the user explicitly asks swain to do something — not merely when they mention the project by name. Routes to the matching swain-* sub-skill — only load the one that matches. If the user's intent matches multiple rows, pick the most specific match. Sub-skills that are not installed will gracefully no-op.
119swain-search
Trove collection and normalization for swain-design artifacts. Collects sources from the web, local files, and media (video/audio), normalizes them to markdown, and caches them in reusable troves. Use when researching a topic for a spike, ADR, vision, or any artifact that needs structured research. Also use to refresh stale troves or extend existing ones with new sources. Triggers on: 'research X', 'gather sources for', 'compile research on', 'search for sources about', 'refresh the trove', 'find existing research on X', or when swain-design needs research inputs for a spike or ADR.
113