spine-scan
Session-start vault scanner. Auto-fixes broken wikilinks, missing tags, and orphan docs. Detects coverage gaps from recent commits. Runs automatically at session start.
Install
mkdir -p .claude/skills/spine-scan && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16389" && unzip -o skill.zip -d .claude/skills/spine-scan && rm skill.zipInstalls to .claude/skills/spine-scan
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Session-start vault scanner. Auto-fixes broken wikilinks, missing tags, and orphan docs. Detects coverage gaps from recent commits. Runs automatically at session start.About this skill
Spine Scan — Session-Start Vault Scanner
Automatically scan the Spine vault for decay and coverage gaps. Auto-fixes low-risk issues, reports findings as a non-blocking banner.
Tier 3 Gate
Before doing anything, check if Tier 3 is enabled:
- Read
~/.spine/config.json - Check the
tier3field - If
tier3isfalseor missing, skip silently — do not scan, do not print a banner, do nothing.
This skill only runs when the user has opted into Tier 3 autonomous behavior.
Vault Path Resolution
Resolve the vault path using this config chain:
$SPINE_VAULT_PATHenvironment variable~/.spine/config.json→ read thevaultPathfield- Default:
~/Documents/SpineVault/
If no vault is found, or the vault directory doesn't exist, skip silently — no banner, no output contract, nothing. Not every repo uses Spine. The output contract is only emitted when the scan actually runs.
Detect Current Repo
basename "$(git remote get-url origin 2>/dev/null)" .git
Fall back to the current directory name if no git remote. If {vault}/{repo}/ doesn't exist, skip silently — this repo isn't tracked by Spine.
Phase 1: Auto-Fixes
Scan the vault for mechanical issues and fix them silently. Log every action to {vault}/.spine/curator-log.md.
Ensure {vault}/.spine/ directory exists before writing.
Conflict Guard
Before reading each file for auto-fix analysis, record its modification time and file size (size catches same-second edits on filesystems with only second-resolution timestamps). Before writing the fix back:
- Re-check mtime and size against the recorded values
- If either changed, skip the fix for that file and log:
**Skipped:** \{file}` — modified by another process during scan` - Proceed with the next file
This prevents overwriting concurrent edits from Obsidian, Obsidian Sync, or parallel sessions. The dual check (mtime + size) handles HFS+/ext4 second-resolution timestamps where a sub-second edit would otherwise be invisible.
1a. Broken Wikilinks
For each markdown file in {vault}/{repo}/:
- Find all
[[wikilinks]]in the file content - Check if each linked note exists anywhere in the vault (search by filename without extension)
- If a linked note doesn't exist:
- Search for notes with similar names (typos, renamed docs)
- If a close match is found, update the wikilink to the correct name
- If no match, remove the broken wikilink and log it
- Log:
**Auto-fixed:** Broken wikilink in \{file}` → `[[{corrected}]]``
1b. Missing or Wrong Type Tags
For each markdown file in {vault}/{repo}/ (skip spine notes):
- Read the YAML frontmatter
- Determine the expected
type/*tag from the filename:- Filename starts with date +
Fix -→type/fix - Filename starts with date +
Feature -→type/feature - Filename starts with
Architecture -→type/architecture - Filename starts with
Plan -→type/plan - Filename starts with
Decision -→type/decision
- Filename starts with date +
- Check if the frontmatter
tagsarray contains the expectedtype/*tag - If missing or wrong, add/correct it in the frontmatter
- Log:
**Auto-fixed:** Missing \{tag}` tag on `{filename}``
1c. Orphan Docs
For each feature folder in {vault}/{repo}/:
- Find the spine note (
{Feature}.mdwithtype/spinetag) - List all other markdown files in the folder
- For each file, check if it's referenced as a
[[wikilink]]in the spine note - If not linked, add a wikilink under the appropriate section:
type/fix→## Fixestype/feature→## Featurestype/architecture→## Architecturetype/plan→## Planstype/decision→## Decisions
- Log:
**Auto-fixed:** Orphan doc \{filename}` linked into `{spine-note}``
1d. Stale and Obsolete Doc Detection
For each doc in {vault}/{repo}/ that has a **Files changed:** section:
- Extract the file paths listed
- For each file path, run existence check first:
git ls-files -- {filepath}- If the command returns empty, the file no longer exists in the repo
- Classify the doc based on results:
- All referenced files missing → the feature was likely removed. Set
obsolete: truein frontmatter. Do NOT also setstale. - Some referenced files missing → partial removal. Set
stale: trueand add aremoved_fileslist to frontmatter. - All files exist → run activity check:
git log --oneline --since="{doc-date}" -- {filepath}. If 3+ commits, setstale: true.
- All referenced files missing → the feature was likely removed. Set
- Log accordingly:
**Flagged obsolete:** \{doc}` — all referenced files removed from repo (feature likely deleted)`**Flagged stale:** \{doc}` — `{filepath}` removed from repo (partial)`**Flagged stale:** \{doc}` — `{filepath}` has {N} commits since doc date`
Important: Never auto-delete or archive obsolete docs — only flag them. The user decides what to do.
Phase 2: Coverage Gap Detection
Find significant commits that have no corresponding Obsidian doc.
2a. Determine Time Window
- Read
{vault}/.spine/last-scan-timestamp- If the file doesn't exist, default to 2 weeks ago
- Also read
{vault}/.spine/pending-commits.jsonfor any leftover commits from abrupt session ends
2b. Find Undocumented Commits
- Run
git log --oneline --since="{last-scan-timestamp}"in the current repo - Filter out trivial commits:
- Skip commit messages matching
^(style|lint|chore|docs|merge) - Skip commits with < 20 total line changes AND <= 1 file
- Skip commit messages matching
- Group remaining commits by feature area:
- Match changed file paths against existing feature folders in the vault
- Group commits touching the same feature together
- For each group, check if a corresponding doc exists in that feature folder with a date on or after the commit date
- Collect any groups with no matching doc as coverage gaps
Phase 3: Banner
Print a single non-blocking summary. Do NOT ask for input or wait for a response.
Format:
🦴 Spine: {N} commits since last session — {fixes summary} (auto). {gaps summary}.
Include obsolete/stale flags in the summary if any were found:
- Obsolete docs (all files removed): mention by name — user should review these
- Stale docs (high activity or partial removal): count only, don't list names
Examples:
🦴 Spine: 5 commits since last session — 2 wikilinks fixed, 1 tag corrected (auto). 2 coverage gaps (auth, payments). Run /spine-capture when ready.🦴 Spine: No new commits. 1 orphan doc linked (auto). Vault is healthy.🦴 Spine: 3 commits since last session. No issues found. Vault is clean.🦴 Spine: 4 commits — 1 doc flagged obsolete (TradeIn feature removed?): \2026-03-12 Feature - TradeIn Flow.md`. Review with /spine-health.`🦴 Spine: 2 commits — 2 docs flagged stale. Vault mostly healthy.
If there are zero commits and zero issues, either print 🦴 Spine: Vault is clean. or skip the banner entirely.
Health Check Reminder
Check {vault}/.spine/last-health-timestamp. If the file doesn't exist or the timestamp is older than 14 days, append a reminder to the banner:
Last full health check: {N} days ago — consider running /spine-health.
Examples with the reminder:
🦴 Spine: Vault is clean. Last full health check: 18 days ago — consider running /spine-health.🦴 Spine: 2 wikilinks fixed (auto). 1 coverage gap (auth). Last full health check: 21 days ago — consider running /spine-health.
If the last health check was within 14 days, do not mention it.
Phase 4: Structured Output
After the human-readable banner, emit a structured observation block. This is the contract that downstream skills (especially /spine-capture --batch) rely on.
spine_scan_result:
status: success | warning | error
summary: "5 commits since last session — 2 wikilinks fixed, 1 coverage gap"
auto_fixes:
- { type: "wikilink", file: "Auth/Login.md", detail: "[[Logn]] → [[Login]]" }
- { type: "tag", file: "2026-04-10 Fix - Cache Bug.md", detail: "added type/fix" }
- { type: "orphan", file: "Architecture - API Layer.md", linked_to: "API.md" }
stale_docs:
- { file: "2026-03-18 Fix - Session Bug.md", reason: "session.hook.ts has 5 commits since" }
obsolete_docs:
- { file: "2026-03-12 Feature - TradeIn Flow.md", reason: "all referenced files removed" }
coverage_gaps:
- { feature: "auth", commits: 3, files: ["src/auth/login.ts", "src/auth/session.ts"] }
next_actions:
- { action: "/spine-capture", reason: "1 undocumented feature group" }
- { action: "review obsolete", file: "2026-03-12 Feature - TradeIn Flow.md" }
recovery_hint: null
Status values:
success— scan completed, no issues foundwarning— scan completed, issues found (gaps, stale, obsolete)error— scan failed (vault missing, git error, etc.) — includerecovery_hint
Cross-skill handoff: Write coverage gaps to {vault}/.spine/scan-gaps.json so /spine-capture --batch can pre-populate drafts without re-scanning. Delete this file after capture consumes it.
Phase 5: Update Timestamp
Write the current ISO timestamp to {vault}/.spine/last-scan-timestamp:
2026-04-19T14:30:00Z
Curator Log Format
Append a dated section to {vault}/.spine/curator-log.md (create the file from templates/curator-log.md if it doesn't exist). Newest entries at the top of the file (prepend, don't append).
## {YYYY-MM-DD} — Session Scan
- **Auto-fixed:** {description of each fix}
- **Flagged obsolete:** `{doc}` — all referenced files removed from repo (feature likely deleted)
- **Flagged stale:** {description of each stale doc}
- **Coverage gap:** {description of each gap}
If no actions were taken, do not add an entry.