Repo Cleanup
A holistic hygiene pass on a single repository, tuned for context-coding with Cursor and Claude Code on this stack:
- Frontend: React, Next.js (App Router) or Vite, Tailwind, shadcn
- Backend / data: Supabase (Postgres, Auth, Storage), Drizzle, Zod
- Deploy: Vercel
- AI: OpenAI API, Anthropic Claude API, Google Gemini API, OpenAI Agents SDK / Vercel AI SDK
The skill triages docs, archives unused code and HTML scratch, flags unused dependencies, tightens the .claude/ surface, and leaves a manifest of every move. Everything is reversible.
$ARGUMENTS selects the scope: docs, code, html, deps, assets, or all (default).
Core principles (read first)
- Archive, do not delete. Every move lands in
_archive/<YYYY-MM-DD>/with a manifest. Usegit mvso blame survives. - Report first, act second. Phases 1–3 produce a report at
_docs/_build/repo-cleanup-<YYYY-MM-DD>.md. Wait for explicit confirmation before Phase 4 (archive). Phase 5 verifies. - Stack-aware, not stack-assumed. Detect what's actually present (
package.json,next.config.*,vite.config.*,supabase/,drizzle.config.*). Only run checks relevant to detected layers. - Defer to focused skills. This is the cross-cutting coordinator. Use:
- $frontend-cleanup — unused React components,
-v2debt, folder restructure - $tailwind-cleanup — hardcoded colors, arbitrary values, primitive bypasses
- $validate — six-layer type-chain alignment (db → schemas → services → routes → hooks → ui)
- $fewer-permission-prompts —
.claude/settings.jsonallowlist tidy - $docs-type-safety — type-doc currency (if
_docs/type/exists) - $tenant-check — multi-tenant config audit (if movemental-style)
- $frontend-cleanup — unused React components,
- Protect context-coding affordances. Never move:
CLAUDE.md,AGENTS.md,README.md,LICENSE,.claude/,.cursor/,.github/, config files, migrations, generated code. See the "Never touch" list below. - One repo at a time. This skill operates on a single repo. Multi-repo cleanup happens by running it multiple times.
Phase 0 — Sniff the stack
Before any cleanup, detect what's in the repo. Read these files (skip silently if absent) and write a stack profile:
# Identity
package.json # name, scripts, deps; presence of next/vite/react/tailwind
CLAUDE.md # repo conventions, six-layer chain status, pinned MCP IDs
AGENTS.md # agent-specific rules (often points to .claude/skills/)
README.md # public surface
# Framework
next.config.{ts,js,mjs} # Next.js
vite.config.{ts,js,mts} # Vite
src/app/ vs src/pages/ # App Router vs Pages Router
proxy.ts middleware.ts # Next 16 vs Next 14 middleware
# Styling
tailwind.config.{ts,js} # Tailwind v3
src/app/globals.css # check for `@theme` (Tailwind v4)
components.json # shadcn
# Data
drizzle.config.{ts,js,mts} # Drizzle
supabase/ # Supabase project (migrations, functions)
.env.example .env.local # env contracts
# AI
src/agents/ packages/agent-runtime/ # Agents SDK / blueprint pattern
src/lib/ai/ src/lib/openai* # AI client wrappers
@openai/agents @ai-sdk/* @anthropic-ai/sdk @google/generative-ai # in package.json deps
# Build infra
vercel.json # Vercel project config
scripts/ # custom CLI scripts (validate-*, generate-*)
.claude/ .cursor/ # context-coding surface
Record findings in the TodoWrite list so subsequent phases can branch on them.
Phase 1 — Discovery
Build six inventories. For large repos, delegate the listings to Agent({ subagent_type: "Explore", ... }) so your main context window stays clean.
1a. Doc inventory
# Markdown files outside vendor/build directories
find . -type f -name "*.md" \
-not -path "./node_modules/*" \
-not -path "./.next/*" \
-not -path "./.vercel/*" \
-not -path "./dist/*" \
-not -path "./build/*" \
-not -path "./_archive/*" \
-not -path "./.git/*"
For each: path, size, last-modified, first heading, and whether it's referenced by CLAUDE.md, AGENTS.md, README.md, any .claude/skills/**/SKILL.md, or any docs/build/prompts/*.md.
1b. Top-level loose files
Anything in the repo root that is not a config file, a canonical doc, or a directory. Classic offenders:
*_REPORT.md,*_AUDIT.md,*_SUMMARY.md,*_COMPLETE.md,*_DIAGNOSIS.md,*_NOTES.md— completed work-productRECIPE-*.md— one-off recipescursor.md(ifCLAUDE.mdexists and is canonical)*.bak,*.backup,*.old,*.orig,*~- Stray
*.htmlnot underpublic/or a known templates path - Stray
*.zip,*.tar,*.tgz
1c. Code inventory (lightweight; defer the heavy lift)
This skill does not rebuild the full import graph — $frontend-cleanup does that. Here we look only for obvious dead-code signals:
- Directories with version suffixes:
-v2,-v3,-new,-old,-backup,-deprecated,-temp,-wip,-draft,-copy,-legacy,-experimental - Files matching
*.bak.ts,*.old.tsx,*.unused.*,* copy.tsx,* copy 2.tsx src/components/_archive/orsrc/_old/(already-archived, can be moved into_archive/)- Empty directories under
src/ - Top-level scripts (
*.mjs,*.cjs,*.sh) not referenced bypackage.jsonscripts or any markdown
1d. HTML / templates / prototypes
Common dumping grounds in this stack:
html/ docs/html/ docs/build/stitch/
templates/ src/templates/ public/templates/
prototype/ prototypes/ scratch/ sandbox/
*.html in repo root
For each .html file: is it referenced by code? Is the parent directory still active per CLAUDE.md? Stitch source HTML is usually fine to archive once the React migration of that screen is shipped — confirm against the active migration list before moving.
1e. Asset inventory (public/)
find public/ -type f \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.gif" -o -name "*.svg" -o -name "*.webp" -o -name "*.pdf" -o -name "*.mp4" -o -name "*.mov" -o -name "*.zip" \)
For each: total bytes, last-modified, and any grep reference from src/. Special handling:
favicon.ico,robots.txt,sitemap.xml,manifest.json,.well-known/*— never archiveog/*.png,social/*.png— referenced byMetadata.openGrapheven ifgrepmisses it. Checkmetadata.tsfiles.
1f. Dependency inventory
Use pnpm dlx knip if available, else pnpm dlx depcheck. If neither, do a manual sweep:
# List runtime deps not imported anywhere under src/
node -e "const p=require('./package.json'); console.log(Object.keys({...p.dependencies||{}, ...p.devDependencies||{}}).join('\\n'))" \
| while read dep; do
if ! grep -rq "['\"]${dep}\(/.*\)*['\"]" src/ scripts/ 2>/dev/null; then
echo "MAYBE-UNUSED: $dep"
fi
done
False-positive watch: tsx, dotenv-cli, peer deps like @types/* matching their runtime, Tailwind plugins, PostCSS plugins, ESLint configs, Drizzle Kit, Husky/lint-staged, Sentry CLI. Flag these as "review" not "remove."
1g. .claude/ surface check
# Symlink coverage
ls -la .claude/skills/ 2>/dev/null | head
# Settings allowlist
cat .claude/settings.json 2>/dev/null
cat .claude/settings.local.json 2>/dev/null
Symptoms of drift:
- Broken symlinks in
.claude/skills/(target path doesn't exist) - Skills in
.claude/skills/that no longer exist inmy-skills/ settings.local.jsonreferencing absolute paths from a different machine- Allowlist entries for skills not symlinked into the repo
(Run $fewer-permission-prompts to compact the allowlist after this skill finishes.)
Phase 2 — Classification & triage
Sort every item into one of these buckets:
| Bucket | Criteria | Default action |
|---|---|---|
| Keep | Canonical doc, active code, referenced asset, declared dep, live skill, recent (≤ 60 days) work-log | Leave in place |
| Archive | Not referenced anywhere; last-modified > 90 days; classified as completed work-product or shadowed-by-newer | Move to _archive/<YYYY-MM-DD>/<bucket>/ |
| Rename/Restructure | Live code under a version-suffixed path (-v2) where original is dead |
Promote to canonical name; archive the dead original |
| Review | Ambiguous: dynamically imported, referenced only in a doc, peer-of-installed | Flag in the report; do nothing automatically |
| Never touch | See list below | Do not move under any condition |
Never-touch list
README.md (top-level)
CLAUDE.md (every directory)
AGENTS.md (every directory)
LICENSE LICENSE.md COPYING
package.json pnpm-workspace.yaml pnpm-lock.yaml
tsconfig*.json eslint.config.*
next.config.* vite.config.* tailwind.config.* postcss.config.*
drizzle.config.* components.json vercel.json
proxy.ts middleware.ts
src/app/ src/pages/ src/components/ src/lib/ src/hooks/ src/agents/
(the dirs themselves; specific orphan
files inside them can still be archived
after import-graph confirmation)
public/favicon.ico public/robots.txt public/sitemap.xml public/manifest.json
public/.well-known/
supabase/migrations/ (NEVER move; migrations are history)
drizzle/migrations/ (same)
.claude/ .cursor/ .github/ .vscode/
.env.example (env contract)
Doc-triage shortcuts
| Filename pattern | Default classification | Notes |
|---|---|---|
*_REPORT.md, *_AUDIT.md, *_SUMMARY.md |
Archive if > 30 days and not referenced | Completed work product |
*_DIAGNOSIS.md, *_FIX.md |
Archive once the fix is shipped | Verify against git log for the fix commit |
*_COMPLETE.md, *_SETUP_COMPLETE.md, *_IMPLEMENTATION.md |
Archive | Onboarding receipts |
RECIPE-*.md, RUNBOOK-*.md |
Keep if referenced, else archive | These often live in docs/build/ |
*-NOTES.md, *-LOG.md |
Keep if recent, else archive | Personal scratch — keep if author still iterates |
_inbox/, _drafts/, _scratch/ |
Archive contents > 30 days old | Don't delete; the inbox pattern is intentional |
_quarantine/ |
Leave alone | Already an intentional archive |
BOOK_CORPUS_QUARANTINE.md and similar provenance docs |
Keep | Quarantine notes are load-bearing |
Phase 3 — Report
Write the cleanup plan to _docs/_build/repo-cleanup-<YYYY-MM-DD>.md. Required sections:
# Repo Cleanup Plan — <YYYY-MM-DD>
**Repo:** `<repo name>`
**Stack detected:** Next.js 16 | Vite + React | Tailwind v4 | Supabase | Drizzle | Vercel | OpenAI Agents | Anthropic | Gemini
**Scope:** docs + code + html + deps + assets
**Total items inventoried:** N
**Total items proposed for archive:** N
**Bytes reclaimed if approved:** ~X MB
## Stack profile
- Framework: ...
- Styling: ...
- Data: ...
- AI providers in use: ...
- Context-coding surface: `.claude/skills/` symlink count, settings.json allowlist size
## Buckets
### Keep (N items)
_Not enumerated unless verbose mode requested._
### Archive (N items)
| Path | Size | Last-modified | Reason | Destination |
|---|---|---|---|---|
| `RECIPE-VIDEO-TO-TRANSCRIPT.md` | 4.2 KB | 2026-03-01 | Completed recipe, referenced only by an archived report | `_archive/2026-05-13/docs/RECIPE-VIDEO-TO-TRANSCRIPT.md` |
| `src/components/Header-v2/` | 22 KB | 2026-02-14 | Shadowed by `src/components/Header/` (active) | `_archive/2026-05-13/code/components/Header-v2/` |
| `html/dashboard-export.html` | 91 KB | 2025-12-04 | Stitch export; React migration shipped 2026-02 | `_archive/2026-05-13/html/dashboard-export.html` |
| `public/old-hero.png` | 1.4 MB | 2025-11-20 | Not referenced; replaced by `public/hero-2026.png` | `_archive/2026-05-13/public/old-hero.png` |
### Rename/Restructure (N items)
| From | To | Reason |
|---|---|---|
### Review (N items)
| Path | Suspicion | Action requested |
|---|---|---|
### Unused dependencies (N items, manual review)
| Package | Why flagged | Likely action |
|---|---|---|
### `.claude/` hygiene
- Broken symlinks: N
- Skills referenced in settings but not symlinked: N
- Outdated `additionalDirectories` paths: N
## Verification commands (run after archive)
```bash
pnpm typecheck
pnpm lint
pnpm build:check # if present
pnpm validate:all # if six-layer chain repo
git status # confirm only intended moves
How to revert
Every move below is in one commit. To revert: git revert <commit-sha>.
The _archive/2026-05-13/MANIFEST.md lists every move with its origin.
**Stop here.** Print the report path and the headline numbers, and ask: *"Approve Phase 4 (archive) for the items in the Archive bucket? Reply `yes` to proceed, `yes minus deps` to skip the dep changes, or list specific paths to exclude."*
---
## Phase 4 — Archive (only after confirmation)
### 4a. Create the archive root
```bash
DATE=$(date +%Y-%m-%d)
ARCHIVE_ROOT="_archive/${DATE}"
mkdir -p "${ARCHIVE_ROOT}"/{docs,code,html,public,scripts}
If _archive/<DATE>/ already exists (a previous run today), append a sequence: 2026-05-13-2, -3, etc.
4b. Move with git mv
For each item in the Archive bucket:
git mv "<from>" "${ARCHIVE_ROOT}/<bucket>/<basename>"
For directories, preserve the original parent path under the bucket:
# src/components/Header-v2/ → _archive/2026-05-13/code/components/Header-v2/
mkdir -p "${ARCHIVE_ROOT}/code/components"
git mv "src/components/Header-v2" "${ARCHIVE_ROOT}/code/components/Header-v2"
Never use rm. Never use mv outside of git mv (loses rename detection).
4c. Handle Rename/Restructure
For each row in the Rename/Restructure bucket:
# Promote the live -v2 to canonical, archive the dead original
git mv "src/components/Header" "${ARCHIVE_ROOT}/code/components/Header-original"
git mv "src/components/Header-v2" "src/components/Header"
Then update imports. Search-and-replace, but only in src/, scripts/, and tests/:
grep -rl "components/Header-v2" src/ scripts/ tests/ \
| xargs sed -i 's|components/Header-v2|components/Header|g'
4d. Write the manifest
_archive/<DATE>/MANIFEST.md:
# Archive Manifest — <YYYY-MM-DD>
Generated by $repo-cleanup. Each entry: original path → archive path, reason, originating cleanup report.
## Moves
- `RECIPE-VIDEO-TO-TRANSCRIPT.md` → `docs/RECIPE-VIDEO-TO-TRANSCRIPT.md` — completed recipe
- `src/components/Header-v2/` → `code/components/Header-v2/` — promoted to canonical name; original archived alongside
- ...
## Renames
- `src/components/Header-v2/` → `src/components/Header/` (canonical promotion)
- `src/components/Header/` → `_archive/2026-05-13/code/components/Header-original/` (the dead original)
## Source report
`_docs/_build/repo-cleanup-<YYYY-MM-DD>.md`
## To revert this cleanup
git revert
4e. Suggested dep removals (do not auto-edit package.json)
Print the list of likely-unused deps to the chat with one pnpm remove ... line per package, ready to copy. Do not modify package.json automatically — the false-positive rate on unused-dep detection is too high to risk auto-removal.
4f. .claude/ tidy
- Remove broken symlinks:
find .claude/skills -xtype l -print -delete(only after the user confirms). - For symlinks pointing at moved-or-missing skills, repoint or remove.
- Suggest the user run $fewer-permission-prompts after this skill completes.
Phase 5 — Verify
After the archive commit lands, run the appropriate stack-aware checks:
pnpm install # in case removed deps were already gone
pnpm typecheck # always
pnpm lint # always
pnpm build:check 2>/dev/null || true
pnpm validate:all 2>/dev/null || true # six-layer chain repos only
pnpm test:run 2>/dev/null || true # if a fast unit suite exists
If any check fails, the first reflex is git revert HEAD and re-examine the report. Do not patch forward from a broken cleanup; the whole point of git mv is reversibility.
If all checks pass, write a one-paragraph summary to the bottom of the cleanup report:
## Result
Cleanup committed at `<sha>`. typecheck ✓ · lint ✓ · build:check ✓ · validate:all ✓.
Bytes reclaimed: X MB. Items archived: N. Items renamed: N.
Deps still pending manual review: N (see Phase 4e).
Stack-specific addenda
Run these in addition to the generic phases when the layer is detected. Each is intentionally short — defer the deep work to the named skill.
React + Next.js (App Router)
- Check for orphaned
page.tsxfiles insrc/app/(folder exists but route is unreachable via nav/sitemap). - Look for
app/**/loading.tsxandapp/**/error.tsxthat are empty or default-only — leave them; they are intentional. src/middleware.tsvsproxy.ts: Next 16 usesproxy.ts. If both exist and the project is Next 16,middleware.tsis stale.- Defer component-level cleanup to $frontend-cleanup.
React + Vite
- Confirm
index.htmlreferences the live entry (src/main.tsxorsrc/index.tsx). vite-project/nested folders inside a parent app (seen in some Movemental tenants) are usually scaffolds — verify before archiving.
Tailwind
- Defer hardcoded-color / arbitrary-value / shadow violations to $tailwind-cleanup.
- Look for unused custom plugins in
tailwind.config.*(plugins: [...]). - If
src/app/globals.csshas@theme inline(Tailwind v4) and atailwind.config.tsalso exists, the config file is usually a stale leftover — flag for review.
Supabase
supabase/migrations/: never archive. Migrations are history.supabase/functions/: archive only if the function is unreferenced by any client AND has no recent deploy.- Look for stale
supabase/.temp/andsupabase/.branches/— these are local-dev caches, can be added to.gitignoreif not already. - Out-of-date
database.types.ts: do not archive; suggest re-runningsupabase gen types typescript.
Vercel
vercel.json: keep. Check forcrons:entries that point at archived routes — flag for review..vercel/directory: should be.gitignored; if checked in, flag.- Stale env keys referenced in
vercel.jsonenv:block that don't exist in.env.example— flag.
Drizzle / six-layer chain
- If
scripts/validate-*.tsandscripts/generate-*.tsexist, this is a six-layer-chain repo. - Generated layers (
src/lib/schemas/,src/lib/services/simplified/,src/app/api/simplified/,src/hooks/simplified/) — do not archive individual files; they regenerate from the schema. If a layer is suspect, run $validate. src/lib/db/schema.tsis source of truth — never archive.
AI surface (OpenAI / Anthropic / Gemini / Agents SDK)
src/agents/<slug>/directories with no API route and no UI reference: candidate for archive (verify againstsrc/app/api/agents/).- Static instruction overrides loaded via
AI_LAB_INSTRUCTION_OVERRIDE_PATHetc. — do not move if the env var still resolves at runtime. exported-ai-lab-instructions.jsonand similar prompt exports: keep — they're an SSOT snapshot.- Old prompt-pack JSON dumps in
_docs/_prompts/> 90 days: candidate for archive if a newer pack supersedes them. src/lib/openai*,src/lib/anthropic*,src/lib/gemini*wrappers: keep if any agent or route imports them; archive if all callers have been migrated to a shared adapter.- Look for hard-coded API keys in source (defense-in-depth check). If found, stop the cleanup, alert the user, and route to $env-setup.
Documentation (docs/)
- Files referenced by
CLAUDE.md,AGENTS.md, or any activeSKILL.md: keep. docs/build/prompts/*.md: keep if the prompt is still cited; archive if the prompt was for a one-time build that shipped.docs/_inbox/anddocs/_drafts/: archive items > 30 days untouched.docs/arguments/,docs/design/,docs/05-agents/_inventory.md,docs/09-skills/_registry.md: SSOT — keep.
.claude/ and .cursor/
- Broken symlinks: remove (after confirmation).
settings.local.jsonwith absolute paths from a different machine: open and edit to use this machine's paths, or delete and regenerate via $fewer-permission-prompts.additionalDirectoriesentries pointing at paths that don't exist: prune.
Output format (chat)
When the skill finishes a phase, print one of:
✓ Phase 1: discovered 412 items (docs 187, code 144, html 12, public 47, deps 38)
✓ Phase 2: classified — keep 318, archive 76, rename 6, review 12
✓ Phase 3: report written to _docs/_build/repo-cleanup-2026-05-13.md
→ 76 items proposed for archive · ~12.4 MB reclaim · 8 deps flagged for review
→ Reply `yes` to proceed, `yes minus deps`, or list paths to exclude.
✓ Phase 4: archived under _archive/2026-05-13/ (commit a3f7c12) — manifest written
✓ Phase 5: verified — typecheck ✓ · lint ✓ · build:check ✓ · validate:all ✓
If the user does not confirm Phase 4, stop after Phase 3 cleanly. The report and inventory remain on disk; rerun the skill to resume.
Don't-do list
- Don't delete. Always archive. The
_archive/<DATE>/folder is the only valid destination. - Don't auto-edit
package.json. Print the suggestedpnpm removelines; let the user apply. - Don't touch Supabase migrations or Drizzle migrations. Ever.
- Don't try to be the import-graph builder. Defer that to $frontend-cleanup.
- Don't run on a dirty working tree. Check
git statusfirst; if anything is uncommitted, ask the user to commit or stash before proceeding. - Don't run on a repo without git.
git rev-parse --git-dirmust succeed. - Don't cross repo boundaries. Operate on the cwd repo only. Symlinks pointing outside the repo are followed for reading but never moved.
- Don't archive the cleanup report itself. Subsequent runs should be able to read prior reports.
Related skills
Invoke these alongside or after repo-cleanup:
- $frontend-cleanup — for the deep React import-graph and component-tree work
- $tailwind-cleanup — for hardcoded colors, arbitrary values, shadow violations
- $validate — six-layer type-chain check
- $fewer-permission-prompts — compact
.claude/settings.jsonallowlist - $docs-type-safety — keep
_docs/type/current after schema changes - $tenant-check — multi-tenant repo configuration audit (movemental tenants)
- $env-setup — re-establish
.env.exampleand rotate any leaked secrets - $analytics-audit — verify analytics wiring after route or component moves
A clean order for a full pass:
git statusclean$repo-cleanup(this skill) — coarse pass, archive obvious bloat$frontend-cleanup— component-tree precision pass$tailwind-cleanup— style-token precision pass$validate— type-chain integrity$fewer-permission-prompts— final settings tidy- Commit and run
pnpm build