Doctor
Session-start health checks for swain projects. Validates and repairs health across all swain skills — governance, tools, directories, settings, scripts, caches, and runtime state. Idempotent — run it every session; it only writes when repairs are needed.
Run checks in the order listed below. Collect all findings into a summary table at the end.
Session-start governance check
-
Detect the agent platform and locate the context file:
| Platform | Context file | Detection |
|---|
| Claude Code | (project root) | Default — use if no other platform detected |
| Cursor | .cursor/rules/swain-governance.mdc
| directory exists |
-
Check whether governance rules are already present:
bash
grep -l "swain governance" CLAUDE.md AGENTS.md .cursor/rules/swain-governance.mdc 2>/dev/null
If any file matches, governance is already installed. Proceed to
Legacy skill cleanup.
-
Legacy skill cleanup
Clean up skill directories that have been superseded by renames or retired entirely. Read the legacy mapping from
references/legacy-skills.json
in this skill's directory.
Renamed skills
For each entry in the
map:
- Check whether
.claude/skills/<old-name>/
exists.
- If it does NOT exist, skip (nothing to clean).
- If it exists, check whether
.claude/skills/<new-name>/
also exists. If the replacement is missing, skip and warn — the update may not have completed:
Skipping cleanup of
— its replacement
is not installed.
- If both exist, fingerprint check: read
.claude/skills/<old-name>/SKILL.md
and check whether its content matches ANY of the fingerprints listed in . Specifically, grep the file for each fingerprint string — if at least one matches, the skill is confirmed to be a swain skill.
- If no fingerprint matches, skip and warn — this may be a third-party skill with the same name:
Skipping cleanup of
.claude/skills/<old-name>/
— it does not appear to be a swain skill (no fingerprint match). If this is a stale swain skill, delete it manually.
- If fingerprint matches and replacement exists, delete the old directory:
bash
rm -rf .claude/skills/<old-name>
Tell the user:
Removed legacy skill
.claude/skills/<old-name>/
(replaced by
).
Retired skills
For each entry in the
map (pre-swain skills absorbed into the ecosystem):
- Check whether
.claude/skills/<old-name>/
exists.
- If it does NOT exist, skip (nothing to clean).
- If it exists, fingerprint check: same as for renamed skills — read
.claude/skills/<old-name>/SKILL.md
and check whether its content matches ANY fingerprint in .
- If no fingerprint matches, skip and warn:
Skipping cleanup of
.claude/skills/<old-name>/
— it does not appear to be a known pre-swain skill (no fingerprint match). Delete manually if stale.
- If fingerprint matches, delete the old directory:
bash
rm -rf .claude/skills/<old-name>
Tell the user:
Removed retired pre-swain skill
.claude/skills/<old-name>/
(functionality now in
).
After processing all entries, check whether the governance block in the context file references old skill names. If the governance block (between
<!-- swain governance -->
and
<!-- end swain governance -->
) contains any old-name from the
map, delete the entire block (inclusive of markers) and proceed to
Governance injection to re-inject a fresh copy with current names.
Platform dotfolder cleanup
The
command (or older versions of swain-update without autodetect) creates dotfolder stubs (e.g.,
,
) for agent platforms that are not installed. These directories only contain symlinks back to
and clutter the working tree. See
GitHub issue #21.
Read the platform data from
references/platform-dotfolders.json
in this skill's directory. Each entry in the
array has a
name and one or both detection strategies:
(CLI binary name) and
(HOME config directory path). Entries with collision-prone command names (e.g.,
,
,
,
) omit
and rely on HOME detection only.
Step 1 — Autodetect installed platforms
Iterate over the
array. For each entry, a platform is considered
installed if either check succeeds:
- If the entry has a field → run
command -v <command> &>/dev/null
.
- If the entry has a field → expand the path (replace with , evaluate env var defaults like ) and check whether the directory exists.
Always consider
installed (current platform — never a cleanup candidate).
Requires: (for reading the JSON). If
is not available, skip this section and warn.
bash
installed_dotfolders=(".claude")
while IFS= read -r entry; do
dotfolder=$(echo "$entry" | jq -r '.project_dotfolder')
cmd=$(echo "$entry" | jq -r '.command // empty')
det=$(echo "$entry" | jq -r '.detection // empty')
found=false
if [[ -n "$cmd" ]] && command -v "$cmd" &>/dev/null; then
found=true
fi
if [[ -n "$det" ]] && ! $found; then
det_expanded=$(echo "$det" | sed "s|~|$HOME|g")
det_expanded=$(eval echo "$det_expanded" 2>/dev/null)
[[ -d "$det_expanded" ]] && found=true
fi
$found && installed_dotfolders+=("$dotfolder")
done < <(jq -c '.platforms[]' "SKILL_DIR/references/platform-dotfolders.json")
(Replace with the actual path to this skill's directory.)
Step 2 — Build cleanup candidates
Every entry in
whose
is NOT in the
list is a cleanup candidate.
Step 3 — Remove installer stubs
For each candidate dotfolder:
-
Check whether the directory exists in the project root.
-
If it does NOT exist, skip.
-
If it exists,
verify it is installer-generated — the directory should contain only a
subdirectory (possibly with symlinks or further subdirectories). Check:
bash
# Count top-level entries (excluding . and ..)
entries=$(ls -A "<dotfolder>" 2>/dev/null | wc -l)
# Check if the only entry is "skills"
if [[ "$entries" -le 1 ]] && [[ -d "<dotfolder>/skills" || "$entries" -eq 0 ]]; then
# Safe to remove — installer-generated stub
fi
- If the directory is empty OR contains only a subdirectory → remove it:
- If the directory contains other files or directories besides → skip and warn:
Skipping
— contains user content beyond installer symlinks. Remove manually if unused.
-
After processing all entries, report:
Removed N platform dotfolder(s) created by
(installer stubs for unused agent platforms).
If none were found, this step is silent.
Governance injection
When governance rules are not found (or were deleted during legacy cleanup), inject them into the appropriate context file.
Claude Code
Determine the target file:
- If exists and its content is just (the include pattern set up by swain-init), inject into instead.
- Otherwise, inject into (create it if it doesn't exist).
Read the canonical governance content from
references/AGENTS.content.md
(relative to this skill's directory) and append it to the target file.
Cursor
Write the governance rules to
.cursor/rules/swain-governance.mdc
. Create the directory if needed.
Prepend Cursor MDC frontmatter to the canonical content from
references/AGENTS.content.md
:
markdown
---
description: "swain governance — skill routing, pre-implementation protocol, issue tracking"
globs:
alwaysApply: true
---
Then append the full contents of
references/AGENTS.content.md
after the frontmatter.
After injection
Tell the user:
Governance rules installed in
. These ensure swain-design, swain-do, and swain-release skills are routable. You can customize the rules — just keep the
<!-- swain governance -->
markers so this skill can detect them on future sessions.
Beads gitignore hygiene
This section runs every session, after governance checks. It is idempotent.
Skip entirely if does not exist (the project has not initialized bd yet).
Step 1 — Validate .beads/.gitignore
The following are the canonical ignore patterns. This list is kept in sync with
references/.beads-gitignore
in this skill's directory.
Canonical patterns (non-comment, non-blank lines):
dolt/
dolt-access.lock
bd.sock
bd.sock.startlock
sync-state.json
last-touched
.local_version
redirect
.sync.lock
export-state/
ephemeral.sqlite3
ephemeral.sqlite3-journal
ephemeral.sqlite3-wal
ephemeral.sqlite3-shm
dolt-server.pid
dolt-server.log
dolt-server.lock
dolt-server.port
dolt-server.activity
dolt-monitor.pid
backup/
*.db
*.db?*
*.db-journal
*.db-wal
*.db-shm
db.sqlite
bd.db
.beads-credential-key
-
If
does not exist, create it from the reference file (
references/.beads-gitignore
) and skip to Step 2.
-
Read
. For each canonical pattern above, check whether it appears as a non-comment line in the file.
-
Collect any missing patterns. If none are missing, this step is silent — move to Step 2.
-
If patterns are missing, append them to
:
# --- swain-managed entries (do not remove) ---
<missing patterns, one per line>
-
Tell the user:
Patched
with N missing entries. These entries prevent runtime and database files from being tracked by git.
Step 2 — Clean tracked runtime files
After ensuring the gitignore is correct, check whether git is still tracking files that should now be ignored:
bash
cd "$(git rev-parse --show-toplevel)" && git ls-files --cached .beads/ | while IFS= read -r f; do
if git check-ignore -q "$f" 2>/dev/null; then
echo "$f"
fi
done
This lists files that are both tracked (in the index) and matched by the current gitignore rules.
If no files are found, this step is silent.
If files are found:
-
Remove them from the index (this untracks them without deleting from disk):
bash
git rm --cached <file1> <file2> ...
-
Tell the user:
Untracked N file(s) from git that are now covered by
. These files still exist on disk but will no longer be committed. You should commit this change.
Governance content reference
The canonical governance rules live in
references/AGENTS.content.md
(relative to this skill's directory). Both swain-doctor and swain-init read from this single source of truth. If the upstream rules change in a future swain release, update that file and bump the skill version. Consumers who want the updated rules can delete the
<!-- swain governance -->
block from their context file and re-run this skill.
Tool availability
Check for required and optional external tools. Report results as a table. Never install tools automatically — only inform the user what's missing and how to install it.
Required tools
These tools are needed by multiple skills. If missing, warn the user.
| Tool | Check | Used by | Install hint (macOS) |
|---|
| | All skills | Xcode Command Line Tools |
| | swain-status, swain-stage, swain-session, swain-do | |
Optional tools
These tools enable specific features. If missing, note which features are degraded.
| Tool | Check | Used by | Degradation | Install hint (macOS) |
|---|
| | swain-do, swain-status (tasks) | Task tracking falls back to text ledger; status skips task section | |
| | swain-stage (MOTD TUI), swain-do (plan ingestion) | MOTD falls back to bash script; plan ingestion unavailable | |
| | swain-status (GitHub issues), swain-release | Status skips issues section; release can't create GitHub releases | |
| | swain-stage | Workspace layouts unavailable (only relevant if user wants tmux features) | |
| | swain-design (specwatch live mode) | Live artifact watching unavailable; on-demand still works | |
Reporting format
After checking all tools, output a summary:
Tool availability:
git .............. ok
jq ............... ok
bd ............... ok
uv ............... ok
gh ............... ok
tmux ............. ok (in tmux session: yes)
fswatch .......... MISSING — live specwatch unavailable. Install: brew install fswatch
Only flag items that need attention. If all required tools are present, the check is silent except for missing optional tools that meaningfully degrade the experience.
Memory directory
The Claude Code memory directory stores
,
, and
. Skills that write to this directory will fail silently or error if it doesn't exist.
Step 1 — Compute the correct path
The directory slug is derived from the full absolute repo path, not just the project name:
bash
REPO_ROOT="$(git rev-parse --show-toplevel)"
_PROJECT_SLUG=$(echo "$REPO_ROOT" | tr '/' '-')
MEMORY_DIR="$HOME/.claude/projects/${_PROJECT_SLUG}/memory"
Step 2 — Create if missing
bash
if [[ ! -d "$MEMORY_DIR" ]]; then
mkdir -p "$MEMORY_DIR"
fi
If created, tell the user:
Created memory directory at
. This is where swain-status, swain-session, and swain-stage store their caches.
If it already exists, this step is silent.
Step 3 — Validate existing cache files
If the memory directory exists, check that any existing JSON files in it are valid:
bash
for f in "$MEMORY_DIR"/*.json; do
[[ -f "$f" ]] || continue
if ! jq empty "$f" 2>/dev/null; then
echo "warning: $f is corrupt JSON — removing"
rm "$f"
fi
done
Report any files that were removed due to corruption. This prevents skills from reading garbage data.
Requires: (skip this step if jq is not available — warn instead).
Settings validation
Swain uses a two-tier settings model. Malformed JSON in either file causes silent failures across multiple skills (swain-stage, swain-session, swain-status).
Check project settings
If
exists in the repo root:
bash
jq empty swain.settings.json 2>/dev/null
If this fails, warn:
contains invalid JSON. Skills will fall back to defaults. Fix the file or delete it to use defaults.
Check user settings
If
${XDG_CONFIG_HOME:-$HOME/.config}/swain/settings.json
exists:
bash
jq empty "${XDG_CONFIG_HOME:-$HOME/.config}/swain/settings.json" 2>/dev/null
If this fails, warn:
User settings file contains invalid JSON. Skills will fall back to project defaults. Fix the file or delete it.
Requires: (skip these checks if jq is not available).
Script permissions
All shell and Python scripts in
must be executable. Skills invoke these via
, which works regardless, but
and direct execution require the executable bit.
Check and repair
bash
find skills/*/scripts/ -type f \( -name '*.sh' -o -name '*.py' \) ! -perm -u+x
If any files are found without the executable bit:
Tell the user:
Fixed executable permissions on N script(s).
If all scripts are already executable, this step is silent.
.agents directory
The
directory stores per-project configuration for swain skills:
execution-tracking.vars.json
— swain-do first-run config
- — swain-design stale reference log
- — swain-search pool refresh log
Check and create
bash
if [[ ! -d ".agents" ]]; then
mkdir -p ".agents"
fi
If created, tell the user:
Created
directory for skill configuration storage.
If it already exists, this step is silent.
Status cache bootstrap
If the memory directory exists but
does not, and the status script is available, seed an initial cache so that swain-stage MOTD and other consumers have data on first use.
bash
STATUS_SCRIPT="skills/swain-status/scripts/swain-status.sh"
if [[ -f "$STATUS_SCRIPT" && ! -f "$MEMORY_DIR/status-cache.json" ]]; then
bash "$STATUS_SCRIPT" --json > /dev/null 2>&1 || true
fi
If the cache was created, tell the user:
Seeded initial status cache. The MOTD and status dashboard now have data.
If the script is not available or the cache already exists, this step is silent. If the script fails, ignore — the cache will be created on the next
invocation.
bd health (extended .beads checks)
This extends the existing
Beads gitignore hygiene section.
Skip entirely if does not exist.
bd doctor
If
is available and
exists, run the bd built-in health check:
bash
bd doctor --json 2>/dev/null
If the exit code is non-zero, attempt automatic repair:
bash
bd doctor --fix 2>/dev/null
Report the result to the user. If
resolves all issues, note it. If issues persist, list them and suggest the user investigate.
Stale runtime files
Check for runtime files that may have been left behind by a crashed bd process:
bash
for f in .beads/bd.sock .beads/bd.sock.startlock .beads/dolt-server.pid .beads/dolt-server.lock .beads/.sync.lock; do
if [[ -f "$f" ]]; then
echo "stale: $f"
fi
done
If stale files are found, warn:
Found stale bd runtime files. If bd is not currently running (
shows nothing), these can be safely removed. Remove them? (list the files)
Do not auto-delete — ask the user first, since a bd process might actually be running.
Summary report
After all checks complete, output a concise summary table:
swain-doctor summary:
Governance ......... ok
Legacy cleanup ..... ok (nothing to clean)
Platform dotfolders ok (nothing to clean)
.beads/.gitignore .. ok
Tools .............. ok (1 optional missing: fswatch)
Memory directory ... ok
Settings ........... ok
Script permissions . ok
.agents directory .. ok
Status cache ....... seeded
bd health .......... ok
3 checks performed repairs. 0 issues remain.
Use these status values:
- ok — nothing to do
- repaired — issue found and fixed automatically
- warning — issue found, user action recommended (give specifics)
- skipped — check could not run (e.g., jq missing for JSON validation)
If any checks have warnings, list them below the table with remediation steps.