vt-t-gc¶
Periodic garbage collection for drift detection. Scans for spec consistency, orphaned files, stale references, and CLAUDE.md validation. Delegates to doc-sync/repo-health/repo-audit for their domains.
Plugin: vt-toolkit
Category: Other
Command: /vt-t-gc
Garbage Collection — Drift Detection¶
Detect and report entropy that accumulates across sessions: spec/config inconsistencies, orphaned files, stale cross-references, and CLAUDE.md drift. Designed to run at session start, via /loop, or on-demand.
When to Use¶
- At the start of a work session (quick health check)
- Periodically via
/loop 30m /vt-t-gc - Before
/vt-d-5-finalize(ensure no drift before merge) - After bulk renames or component removals
Invocation¶
/vt-t-gc # Full scan (all 4 categories)
/vt-t-gc --category specs # Spec consistency only
/vt-t-gc --category orphans # Orphaned files only
/vt-t-gc --category refs # Stale cross-references only
/vt-t-gc --category claude-md # CLAUDE.md validation only
/vt-t-gc --fix # Auto-fix correctable issues
/vt-t-gc --full # Force full scan (ignore incremental state)
Execution Instructions¶
Calling the status vocabulary. Every status-vocab.sh call in this skill is written in the
cross-plugin form "$CLAUDE_PLUGIN_ROOT/../vt-base/bin/status-vocab.sh". The ../vt-base/ hop is
deliberate: the CLI lives in the base plugin, while $CLAUDE_PLUGIN_ROOT names this skill's own
plugin. If $CLAUDE_PLUGIN_ROOT is unset or that file does not exist, run the copy relative to this
skill file's directory instead, ../../../vt-base/bin/status-vocab.sh. If neither exists, stop the
step and report both paths you tried; never classify from memory or from a list written here. Do not
shorten the call to a path under the toolkit's plugins/ directory: that resolves only when the
working directory is the toolkit itself, so the step could not read the vocabulary in a product repo.
Never paste a PATH or a VALUE read out of a file into a command: a status and a file name are both
repo content, and a $(…) in one runs when it reaches a shell (review pass 4, SEC-20). Single
quotes are NOT the remedy — that advice shipped here and was measured wrong (review pass 6,
SEC6-3): they stop $(…), but a directory committed as 999-x'$(touch PWNED)' CLOSES the quote
and executes. Let the shell expand the glob and bind the loop variable, and paste only a number,
which cannot carry a quote. classify-file and accepts-file take the file and read the value
themselves.
An exit 2 is not an unknown literal. exit 3 says the vocabulary was read and this value is
not in it — warn, and never default it. exit 2 says the vocabulary could not be read AT ALL, or
that the artifact was refused before being scanned: a missing or unparseable data file, a key this
reader will not identify, a duplicate declaration of the same field, or a file over the size bound
(review pass 7, SEC7-4). Report it and stop; do not carry on as though the answer were empty and
do not treat the value as acceptable. Stated once here rather than in each step, because it is the
contract of every call this file makes.
Step 1: Parse Arguments¶
Extract flags from invocation:
- --category {name} — run only the specified category (specs, orphans, refs, claude-md)
- --fix — attempt auto-correction for fixable issues
- --full — force full scan, ignore .gc-state.yaml timestamp
If no flags: run all 4 categories with incremental scanning.
Step 2: Load Incremental State¶
Read .gc-state.yaml from the project root (same level as .design-state.yaml).
If file exists and --full not passed:
- Read last_run timestamp
- Display: "Incremental scan (changes since {last_run})"
- In subsequent category checks, prioritize recently modified files
If file does not exist (EC-1: first run): - Display: "First run — establishing baseline with full scan" - Run full scan across all categories
If --full flag:
- Display: "Full scan requested"
- Ignore .gc-state.yaml timestamp
Step 3: Run Category Checks¶
Run the requested categories. For each, collect findings with severity levels.
CRITICAL severity rule (NFR-2): Only assign CRITICAL when a referenced path has been verified non-existent via Glob or ls. Never infer critical findings from naming patterns alone.
Category 1: Spec Consistency¶
Check .design-state.yaml ↔ actual spec directories.
- Read
.design-state.yamlspecs_statussection - For each entry with
specs_dir: - Verify the directory exists (Glob:
specs/{N}-*/) - Verify
spec.mdexists inside it - If directory missing →
[CRITICAL] SPEC-{NNN} registered in .design-state.yaml but specs/{dir}/ does not exist - Scan
specs/*/directories: - For each directory: check if it has a matching
.design-state.yamlentry - If no entry →
[WARNING] specs/{dir}/ exists but has no .design-state.yaml registration - For each spec with
state.yaml, check its status against the declared vocabulary: The shell expands the glob; the directory name is NEVER pasted into the command. A directory committed as999-x'$(touch PWNED)'closes a single quote and executes — measured, review pass 6, SEC6-3.
for f in specs/*/state.yaml; do
[ -e "$f" ] || continue
printf '%s\t' "$f"
"$CLAUDE_PLUGIN_ROOT/../vt-base/bin/status-vocab.sh" classify-file "$f"
done
Exit 0 prints
namespace field literal canonical class, exit 3 means the literal is declared nowhere, exit 4
means the file carries no status: at all. Pass the FILE, never the status read out of it: a
status is repo content, and a $(…) in one ran when it was pasted into an argument (SEC-20)
- On exit 3 → [WARNING] specs/{dir}/state.yaml has an undeclared status: {value}
- ⚠ This used to be a list of six literals written here, and it had already gone stale: it
never learned the end state A1 added, so a correctly archived spec was reported as invalid.
5. Check for stale in_progress specs (activated >30 days ago and not yet ended):
- [INFO] SPEC-{NNN} has been in_progress since {date} (>30 days)
--fix actions:
- Missing .design-state.yaml entry → create it with the single writer, which reads that metadata
itself from the spec's frontmatter and state.yaml:
python3 "$CLAUDE_PLUGIN_ROOT/../vt-base/bin/spec-status-write.py" --cwd "$(git rev-parse --show-toplevel)" \
--create SPEC-NNN --dir <N>-kebab-title
If $CLAUDE_PLUGIN_ROOT is not set, locate the writer relative to this skill file at
../../../vt-base/bin/spec-status-write.py, resolving the skill file's real path first (deployed
skills are symlinks into the toolkit checkout). Never call it by a repo-relative
plugins/vt-base/… path (BUG-342): this skill runs in the project, not the toolkit, so that path
exists only when the project is the toolkit. If the writer does not resolve, that is a toolkit
defect — report the path tried and stop.
- Invalid status → skip (ambiguous — needs human decision)
Category 2: Orphaned Files¶
Detect files not referenced by code or configuration.
-
Scan for backup/temporary files:
Each found →Glob: plugins/**/*.bak Glob: plugins/**/*.old Glob: plugins/**/*.orig Glob: plugins/*/hooks/.backups/*[INFO] Backup file: {path} -
Check manifest integrity (4 manifest files):
- Read
plugins/*/.claude-plugin/skill-symlinks.manifest - Read
plugins/*/.claude-plugin/agent-symlinks.manifest - Read
plugins/*/.claude-plugin/command-symlinks.manifest - Read
plugins/*/.claude-plugin/hook-symlinks.manifest - For each entry: verify the target directory/file exists
-
If target missing →
[WARNING] Manifest entry '{name}' points to non-existent '{target}' -
Reverse check: scan skill/agent/command directories for files not in any manifest:
- List
plugins/*/skills/*/SKILL.md - For each: check if it has a manifest entry
- If no entry →
[WARNING] Skill '{name}' has no manifest entry (not deployed)
--fix actions: - Missing manifest entry → add to appropriate manifest file - Backup files → skip (user may want to keep them)
Category 3: CLAUDE.md Validation¶
Verify CLAUDE.md references are current.
- Read
CLAUDE.mdfrom project root - Extract skill/command references matching
/vt-<x>-*patterns (one letter per plugin: c d o t p b v f) - For each reference: check if the skill exists in
plugins/*/.claude-plugin/skill-symlinks.manifest - If not found →
[WARNING] CLAUDE.md references '/vt-<x>-{name}' which is not in the skill manifest - Extract file/directory path references (patterns like
plugins/...,docs/...,specs/...) - For each path: verify it exists via Glob
- If not found →
[INFO] CLAUDE.md references path '{path}' which does not exist
--fix actions:
- Stale skill reference → suggest correct name if a manifest entry differs only by prefix, suffix, or a single segment change (e.g., vt-d-wf-plan → vt-d-wf-work)
- Stale path reference → skip (ambiguous)
Category 4: Stale Cross-References¶
Detect broken references between skills and agents.
- Scan all SKILL.md files for references to other skills:
- For each unique reference found: check if the target skill exists in the manifest
- If not found →
[WARNING] {source-skill} references '/vt-<x>-{target}' which is not in manifest - Scan agent files for
skills:YAML references: - For each skill reference in agent files: verify it exists
- If not found →
[WARNING] Agent '{agent}' declares skill dependency '{skill}' which does not exist
--fix actions:
- Stale skill reference → replace with correct name if unambiguous match found
- Ambiguous match (multiple candidates) → [MANUAL] Cannot auto-fix: '{ref}' has multiple possible matches: {candidates}
Step 4: Generate Report¶
Display findings grouped by category with severity tags:
Garbage Collection Report
═══════════════════════════════════════════════════════════════
Category: Spec Consistency
─────────────────────────────────────────────────────────────
[CRITICAL] specs/27-pd-capture-decisions/ registered but directory missing
[WARNING] specs/36-gsd-wave-execution/ missing state.yaml
[INFO] SPEC-033 in_progress since 2026-02-20 (>30 days)
Category: Orphaned Files
─────────────────────────────────────────────────────────────
[WARNING] Manifest entry 'vt-c-foo' points to non-existent 'foo/'
[INFO] 3 backup files in hooks/.backups/
Category: CLAUDE.md Validation
─────────────────────────────────────────────────────────────
All clear ✓
Category: Stale Cross-References
─────────────────────────────────────────────────────────────
All clear ✓
Summary: 1 critical | 2 warning | 2 info
Scan type: incremental (since 2026-03-22)
Related checks (not covered by GC — run separately):
/vt-t-doc-sync --report-only — documentation ↔ toolkit state
/vt-c-repo-health — governance traffic light
/vt-c-repo-audit — deployment integrity
═══════════════════════════════════════════════════════════════
For no drift found (EC-2):
Step 5: Update State¶
Write or update .gc-state.yaml:
last_run: "{ISO-8601 timestamp}"
categories_run: [specs, orphans, refs, claude-md]
findings_count: {total findings}
Step 6: Fix Summary (--fix mode only)¶
If --fix was passed, display a fix summary after the report:
Auto-Fix Results
─────────────────────────────────────────────────────────────
[FIXED] Added .design-state.yaml entry for specs/27-pd-capture-decisions/
[FIXED] Added manifest entry for skill 'vt-d-adr'
[SKIP] Cannot auto-fix: stale reference in skills/foo has 2 possible matches
[SKIP] Backup files in hooks/.backups/ — manual review needed
Fixed: 2 | Skipped: 2
Relationship to Other Skills¶
| Skill | Relationship | Overlap? |
|---|---|---|
/vt-t-doc-sync |
Documentation ↔ toolkit state (counts, terms, versions) | None — GC delegates |
/vt-c-repo-health |
Quick governance traffic light | None — GC delegates |
/vt-c-repo-audit |
Deployment integrity (partitions, LFS, hooks) | None — GC delegates |
/vt-c-repo-init |
Initial governance setup | None — different lifecycle |
/vt-d-activate |
Reads spec state (GC validates what activate created) | Complementary |