Meta — Single-agent orchestration with built-in critic gate. Audits and grooms the project's .agents/skill-artifacts/ tree, classifying every artifact and (with explicit per-category confirmation) MOVING stale/orphan/legacy/ephemeral files to a dated archive. Never deletes.
Core Question: "Is this artifact still load-bearing for the project, or is it accumulated cruft from a skill run that finished weeks ago?"
Critical Gates — Read First
- MOVE, never delete. Even after explicit operator confirmation, the destructive action is
mvinto.agents/skill-artifacts/.archive/[YYYY-MM-DD]/. A separate--purge-archiveflag (out of scope for v1) is the only path to actual deletion. This guarantees recoverability. - HARD-NEVER paths. The skill MUST refuse to touch any of these, even if they appear stale:
brand/— canonical brand identity (lifecycle: canonical)research/— canonical audience + market record (lifecycle: canonical)architecture/— canonical system blueprint (lifecycle: canonical).git/and.gitmodules— version control state- any submodule directory (entries in
.gitmodules) .agents/manifest.json— infrastructure, regenerated bymanifest-sync.ts.agents/experience/— Q&A substrate, append-only by every skill.agents/skill-artifacts/meta/roadmap.mdandtasks.md— session anchors
- Default mode is
--dry-run. No file is moved without an explicit--applyflag AND per-category operator confirmation. - Critic gate is non-negotiable. Before any prompt for confirmation, the runner spot-checks 5 random STALE/ORPHAN candidates by grepping references across
.agents/skill-artifacts/,brand/,research/,architecture/. Any live reference → escalate to operator review with the references shown. The critic catches "would-have-archived-something-still-referenced" before destructive action. - Manifest-sync after. After any move, re-run
bun scripts/manifest-sync.tsso.agents/manifest.jsonreflects the new disk state.
Inputs Required
- A project root containing a
.agents/skill-artifacts/tree (the working directory by default) - Optional scope: a subpath under
.agents/skill-artifacts/to limit the audit (e.g.,skills-resources/loops/) - Optional mode:
--dry-run(default) or--apply - Optional staleness threshold in days (default: 90)
Output
.agents/skill-artifacts/meta/records/[YYYY-MM-DD]-cleanup-artifacts-<slug>.md— dated, slug-suffixed, immutable per-run record (lifecycle: snapshot; seeagent-skills/CLAUDE.md§"Artifact Placement"). Use a kebab-case<slug>capturing scope/mode (e.g.,2026-05-08-cleanup-artifacts-dry-run-full.md,2026-05-08-cleanup-artifacts-apply-research.md). Reports accumulate as audit trail; do NOT overwrite prior reports.
Chain Position
- After: any session that piled up artifacts (long brief runs, fresh-eyes review batches, panel sessions)
- Before: a release commit, when the operator wants the working tree to reflect what's still load-bearing
- Standalone re-run: any time
.agents/skill-artifacts/count exceeds the operator's tolerance threshold
Pre-Dispatch
Run the Pre-Dispatch protocol (references/_shared/pre-dispatch-protocol.md) before dispatching the cleanup-runner agent.
Needed dimensions: scope (full .agents/skill-artifacts/ vs. subdir), mode (--dry-run vs --apply), staleness threshold (default 90d), excluded paths the operator wants untouched.
Read order:
- Conversation context — usually the operator just signalled
.agents/skill-artifacts/is messy; mode and scope often inferable. - Pipeline:
.agents/manifest.jsonfor current artifact inventory + freshness signals. - Experience:
.agents/experience/technical.mdfor any priorcleanup-artifacts excluded pathsentry (durable across runs).
Warm Start (scope clear from invocation, e.g., "groom .agents/skill-artifacts before commit"):
Found:
- scope → "[full .agents/skill-artifacts/ | <subpath>]"
- artifact disk snapshot →
! `find .agents/skill-artifacts -name "*.md" -type f 2>/dev/null | wc -l | tr -d ' '` files on disk;
! `find .agents/skill-artifacts -name "*.md" -type f -mtime +90 2>/dev/null | wc -l | tr -d ' '` older than 90d
- manifest last touched →
! `git log -1 --format='%cr' .agents/manifest.json 2>/dev/null | grep . || echo 'untracked or no git history'`
- excluded paths from experience → "[list or none]"
Mode defaults to --dry-run (preview only). Threshold defaults to 90 days.
Override (e.g., --apply, --threshold-days 30) or proceed?
The two ! \...`lines are inline shell interpolation (see this skill's generated support notes). When/cleanup-artifacts` is invoked, Claude Code substitutes the command output before the LLM sees the prompt — so the warm-start summary lands with real counts instead of placeholders the orchestrator has to derive.
Cold Start (general "clean up artifacts" with no scope hint):
cleanup-artifacts grooms the .agents/skill-artifacts/ tree — classifies every file
(KEEP/STALE/ORPHAN/LEGACY/EPHEMERAL), surfaces references and risk,
and (with --apply) MOVES candidates to .agents/skill-artifacts/.archive/.
Never deletes. Before I scan:
1. **Scope** — pick one:
- full (.agents/skill-artifacts/ — most common)
- subpath (e.g., skills-resources/loops/)
2. **Mode** — dry-run (default; preview only) or apply (executes after
per-category confirmation, MOVES to archive, never deletes).
3. **Staleness threshold** — days since last update before an artifact
is flagged STALE. Default 90.
4. **Excluded paths** — anything off-limits even if it looks stale?
(Persisted to experience/technical.md for future runs.)
Answer 1-4 in one response. I'll inventory, classify, run the critic gate,
then surface candidates.
Write-back:
| Q | File | Key |
|---|---|---|
| 4. Excluded paths | .agents/experience/technical.md |
Technical — cleanup-artifacts excluded paths (durable across runs) |
Scope, mode, threshold are run-specific — not persisted.
Single-Agent Architecture (with built-in critic gate)
This skill uses a single execution agent — agents/cleanup-runner.md — because file management is mechanical, not multi-perspective. The critic gate runs as an explicit sub-routine inside the same agent, not as a second dispatched agent. This keeps the orchestration cheap and matches the roadmap's design call ("file management is not multi-agent territory; critic gate IS non-negotiable").
Execution Flow
1. Read .agents/manifest.json + walk disk under scope target
2. Per-artifact classify → KEEP / STALE / ORPHAN / LEGACY / EPHEMERAL
3. Risk-surface each non-KEEP candidate (references, gitignore, bytes, mtime)
4. CRITIC GATE — spot-check 5 random STALE/ORPHAN by grep across the tree
- Any live reference → escalate to operator, do NOT prompt for confirmation
5. Emit dry-run report (always written, even in --apply mode — audit trail)
6. [--apply only] HARD GATE: per-category explicit operator confirmation
7. [--apply only] mv candidates to .agents/skill-artifacts/.archive/[date]/
8. Re-run bun scripts/manifest-sync.ts
9. Emit final report with status + reclaim totals + any escalations
Why a critic gate (and why inside the runner)
The destructive failure mode this skill must prevent is "moved an artifact still referenced by a live skill or doc." Reference detection is mechanical (grep across the tree) but easy to skip under time pressure. Codifying it as an explicit step inside the runner — with an escalate branch that bypasses confirmation entirely when references are live — is the cheapest way to make the safeguard non-skippable.
A separate critic agent would double dispatch cost without changing the failure mode it catches. See agents/cleanup-runner.md §"Critic Gate Sub-Routine" for the exact procedure.
Classification Vocabulary
Every artifact gets one classification. The runner uses these to phrase its recommendation. Full taxonomy + concrete file-pattern examples in references/cleanup-rules.md.
| Class | Meaning | Default action |
|---|---|---|
| KEEP | manifest fresh, recently consumed downstream, OR explicitly canonical/anchor | Leave in place |
| STALE | manifest entry > threshold AND no recent downstream consumer | Propose ARCHIVE; require confirm |
| ORPHAN | file on disk but no manifest entry; producing skill renamed/removed; or hand-created and abandoned | Propose ARCHIVE; require confirm |
| LEGACY | frontmatter declares status: superseded/archived OR lifecycle: ephemeral |
Strong-recommend ARCHIVE |
| EPHEMERAL | matches known ephemeral pattern (see references/cleanup-rules.md) |
Strong-recommend ARCHIVE |
HARD-NEVER overrides
The classifier MUST refuse to flag any path under the HARD-NEVER list (see Critical Gates) as anything other than KEEP, regardless of mtime or manifest state. If the disk shows a HARD-NEVER path with stale-looking signals, the runner emits a KEEP (hard-never) line in the report and moves on.
Risk Surfacing (per non-KEEP candidate)
For every STALE / ORPHAN / LEGACY / EPHEMERAL candidate, the runner records:
- Live references — grep result count for path-string + basename + slug across
.agents/skill-artifacts/,brand/,research/,architecture/. Zero is required to proceed without escalation. - Producing skill state — does the manifest still know about the producing skill? If the skill was renamed/removed, flag it (the artifact may be load-bearing under the old name).
- Gitignored? — if yes, archiving is fully reversible only via the archive itself; if tracked, git history is the safety net.
- Bytes + last-modified date — context for the operator.
- Submodule check — if the path crosses into a submodule directory, mark HARD-NEVER and skip.
Reference-detection grep patterns are spec'd in references/cleanup-rules.md.
Confirmation Gate (--apply only)
When the operator runs --apply, the runner does NOT proceed silently. It emits a per-category summary and waits for explicit confirmation:
Found 3 categories of candidates:
STALE: 12 files (oldest: 2026-01-12, total 480KB)
ORPHAN: 4 files (no manifest entry, total 92KB)
EPHEMERAL: 7 files (matched ephemeral patterns, total 38KB)
LEGACY: 0
HARD-NEVER overrides: 2 (kept; see report)
Critic gate: PASS (spot-check found no live references)
Confirm per category — answer y/n for each:
Archive STALE (12)?
Archive ORPHAN (4)?
Archive EPHEMERAL (7)?
(Files MOVE to .agents/skill-artifacts/.archive/2026-05-08/, never deleted.
A separate --purge-archive flag, out of scope for v1, is the only path to actual deletion.)
If the operator declines a category, those files stay in place; the report records the decline.
If the critic gate FAILED (live references found), the runner skips the confirmation prompt entirely and instead surfaces the offending references to the operator for review. No --apply action runs in that branch.
Critical Safety Rules
The runner enforces all of these. Any violation is an escalation, not a workaround:
- NEVER touch HARD-NEVER paths — even on explicit operator request inside this skill. (Operator can move/delete those manually outside the skill.)
- NEVER delete — only
mvto.agents/skill-artifacts/.archive/[YYYY-MM-DD]/. Archive structure mirrors source (e.g.,.agents/skill-artifacts/meta/records/foo.md→.agents/skill-artifacts/.archive/2026-05-08/.agents/skill-artifacts/meta/records/foo.md). - NEVER skip the critic gate — even if scope is small or operator says "just do it."
- Always write the report — even in
--dry-run, even if zero candidates found, even if critic escalated. The report is the audit trail. - Always re-run manifest-sync — after any actual move. Stale manifest is itself a hygiene problem.
- Never recurse into
.git/, submodule dirs, ornode_modules/— runner walk must skip these. - Never follow symlinks —
find -Land equivalents are banned in the runner's walk.
Anti-Patterns
| Anti-Pattern | Problem | INSTEAD |
|---|---|---|
Treating .agents/skill-artifacts/meta/records/ as ephemeral |
These are dated, immutable snapshots (audit trail). Pruning by age alone destroys the trail. | Use the EPHEMERAL pattern list in references/cleanup-rules.md. Records are STALE-only, never EPHEMERAL. |
Pruning .agents/experience/*.md |
Append-only Q&A substrate, read by every skill. | HARD-NEVER. |
Pruning tasks.md or roadmap.md |
Session anchors, always loaded. | HARD-NEVER. |
| Skipping the critic gate to save time | The whole point of the skill is the gate. Without it, this is rm -rf with extra ceremony. |
The gate is mandatory; delete operations bypass it only when escalating to operator. |
| Deleting instead of moving | "Reversible" means the operator can recover from a mistake without git surgery. | MOVE to dated archive. Always. |
Operating without .agents/manifest.json fresh |
Stale manifest classifies wrong. | Run bun scripts/manifest-sync.ts first if manifest mtime > 1 day. |
| Recursing into submodule dirs | Each submodule is its own repo with its own cadence; this skill operates at one repo level only. | Read .gitmodules; skip those paths. |
| Reporting with no per-file rationale | Operator can't audit a "trust me, this is stale" list. | Every candidate gets class + last-mod + bytes + reference count. |
Configuration
| Parameter | Default | Description |
|---|---|---|
| scope | .agents/skill-artifacts/ |
Path to audit. Subpaths allowed; HARD-NEVER paths refused. |
| mode | --dry-run |
--dry-run (preview only) or --apply (move with confirmation) |
| threshold-days | 90 | Days since last update before STALE classification kicks in |
| critic-spot-check-N | 5 | Number of random STALE/ORPHAN candidates the critic greps |
| excluded-paths | (from experience/technical.md) | Operator-declared off-limits even if they look stale |
Worked Example
Operator: "groom .agents/skill-artifacts — it's a junk drawer. Apply."
Pre-Dispatch (warm):
Found:
- scope → ".agents/skill-artifacts/" (full)
- manifest snapshot → "47 artifacts, 6 stale (>90d), 3 with no manifest entry"
- excluded paths → none recorded
Mode set to --apply per your request. Threshold 90 days. Proceed?
Operator: y
Runner Layer 1 (walk + classify):
- 47 artifacts found under
.agents/skill-artifacts/ - Classifications:
- KEEP (38) — manifest fresh, or under HARD-NEVER (manifest, experience, tasks, roadmap)
- STALE (4) — old fresh-eyes records from prior sessions
- ORPHAN (3) —
.agents/skill-artifacts/meta/sketches/foo.mdreferenced by no manifest entry; producing skill (prioritize) hasn't been run in this project - LEGACY (1) —
.agents/skill-artifacts/meta/specs/old-scope.mdwithstatus: supersededin frontmatter - EPHEMERAL (1) —
.agents/skill-artifacts/.archive/2026-04-01/foo-candidates.md(already archived; matches-candidates.mdpattern but already in archive — KEEP, it's the archive)
Runner Critic Gate:
- Spot-check 5 random STALE/ORPHAN: grep path + basename + slug across
.agents/skill-artifacts/,brand/,research/,architecture/ - 1 of 4 STALE files (
2026-02-15-fresh-eyes-prior-pass.md) cited by.agents/skill-artifacts/meta/roadmap.mdline 412 — LIVE REFERENCE - ESCALATE: skip confirmation, surface to operator
Runner Output:
Critic gate FAILED — 1 of 9 STALE/ORPHAN candidates has a live reference:
STALE: 2026-02-15-fresh-eyes-prior-pass.md
referenced by: .agents/skill-artifacts/meta/roadmap.md:412
Recommendations:
- Re-run with that file excluded, OR
- Edit roadmap.md to drop the stale citation, then re-run
No files were moved. Report: .agents/skill-artifacts/meta/records/2026-05-08-cleanup-artifacts-apply-blocked.md
Status: BLOCKED — live reference found; operator decision needed.
Operator decides: edits roadmap.md to drop the stale reference, re-runs cleanup-artifacts --apply. Critic passes; per-category prompts fire; operator confirms STALE+ORPHAN+LEGACY; runner moves 8 files to .agents/skill-artifacts/.archive/2026-05-08/; manifest-sync runs; report saved with status DONE.
Report Template
---
skill: cleanup-artifacts
version: 1
date: {YYYY-MM-DD}
status: done | done_with_concerns | blocked | needs_context
mode: dry-run | apply
scope: <path>
threshold_days: 90
total_candidates: N
total_archived: N
critic_gate: pass | fail | skipped
---
# Cleanup Artifacts Report
## Summary
- Mode: [dry-run | apply]
- Scope: [path]
- Threshold: [N] days
- Total artifacts surveyed: [N]
- KEEP: [N] STALE: [N] ORPHAN: [N] LEGACY: [N] EPHEMERAL: [N]
- HARD-NEVER overrides applied: [N]
- Total archived this run: [N] (0 if dry-run or critic-blocked)
- Total bytes archived: [X KB / MB]
## Critic Gate
- Verdict: [PASS | FAIL | SKIPPED-because-zero-candidates]
- Spot-check sample: [N candidates checked]
- Live references found: [N]
- [path → referencing file:line] (if any)
## Per-Candidate Detail
| # | Path | Class | Bytes | Last-mod | Refs | Action | Confirmed? |
|---|------|-------|-------|----------|------|--------|------------|
| 1 | .agents/skill-artifacts/meta/records/foo.md | STALE | 12KB | 2026-01-15 | 0 | archived | y |
| 2 | .agents/skill-artifacts/meta/sketches/bar.md | ORPHAN | 4KB | 2025-11-30 | 0 | declined | n |
...
## HARD-NEVER Overrides Applied
- [path] — kept because [canonical / submodule / infrastructure]
...
## Manifest-Sync
- Re-run: [yes/no]
- Manifest delta: [N entries removed, N entries unchanged]
## Operator Decisions
- STALE: [confirmed / declined]
- ORPHAN: [confirmed / declined]
- LEGACY: [confirmed / declined]
- EPHEMERAL: [confirmed / declined]
## Notes
[Any escalations, surprising classifications, references the operator should know about]
Output Files
| File | Description |
|---|---|
.agents/skill-artifacts/meta/records/[YYYY-MM-DD]-cleanup-artifacts-<slug>.md |
Per-run audit report (lifecycle: snapshot — dated, immutable). Reports accumulate as audit trail. |
.agents/skill-artifacts/.archive/[YYYY-MM-DD]/... |
Archive of moved files, mirroring source paths (created on --apply only). |
Future Work (out of scope for v1)
--purge-archiveflag — actually delete files from.archive/[date]/directories older than N days. v1 only moves; v1 does not delete. Re-entry condition: archive itself becomes a junk drawer.--paranoidmode — per-file confirmation instead of per-category. Re-entry: operator reports false positives at category granularity.- Hook integration — pre-commit or pre-PR cadence. Re-entry: when manual cadence misses cycles.
Completion Status
Every run ends with explicit status:
- DONE — critic passed, per-category confirmations resolved, files moved (or
--dry-runreport written), manifest-sync ran clean. - DONE_WITH_CONCERNS — applied successfully but flagged: e.g., a tracked file moved (git history is the safety net but operator should know), or a category was declined and the operator should revisit. Report enumerates concerns.
- BLOCKED — critic gate found live references AND the run was
--apply; no moves executed; operator decision needed. Or HARD-NEVER violation attempted; halted. - NEEDS_CONTEXT —
.agents/manifest.jsonmissing or corrupt; cannot classify reliably. Recommendbun scripts/manifest-sync.tsfirst.
References
references/cleanup-rules.md— full classification taxonomy with per-class file-pattern examples, ephemeral pattern list, reference-detection grep patterns, risk-tier rules, and manifest-driven freshness rules.agents/cleanup-runner.md— the single execution agent (walk, classify, critic, prompt, move, sync).references/_shared/manifest-spec.md— manifest contract the classifier reads.agent-skills/CLAUDE.md§"Artifact Placement" — the lifecycle taxonomy this skill enforces.