brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
housekeep-dead-codeRemove dead code by category — one commit each, typecheck and tests between, bisect on red — then add a Knip ratchet. Use when "delete the dead code" or "wire up Knip". Audit first → plan-dead-code. README/deps → workflow-housekeep.
Independent GitHub snapshot · Automated classification · Original documentation governs
Degree of freedom: MIXED. Category order, the --fix-type sequence,
one-commit-per-category, and verification between commits [LOW freedom — run exactly]; whether a specific flagged item is truly dead
[HIGH freedom].
Apply-now. plan-dead-code produced the reviewed keep/kill list and the
baseline; this makes the repo match it and installs the gate that keeps it
matched. Net dead code strictly ≤ before, and no behavior change.
This is the execution arm of plan-dead-code, the same way
housekeep-gates executes audit-gate-logic and housekeep-design
executes plan-uiux-unification.
Deletion is the approved execution of a plan. The plan-* preservation
contract ("no removal without an approved proposal") is satisfied by that
approval — not bypassed by this skill.
| Skill | Owns |
|---|---|
| housekeep-dead-code (this) | Delete per approved list, cascade, install the ratchet |
plan-dead-code | Prove it is dead first — config, baseline, keep/kill list |
housekeep-gates | The aggregator this skill's CI job plugs into |
housekeep-backlog | Parked-work register — not deletion |
workflow-housekeep | README sync, build artifacts, dependency bumps |
workflow-refactor | Restructuring code that stays (duplication fixes land here) |
burndown-full | One mechanical change to 100% when a prior pass stalled |
db-migrator | Authoring any DROP migration |
plan-data-integrity | Whether a destructive migration is safe at all |
workflow-green-repo | Repo already red before this pass started |
Never carry two categories in one commit. The whole value of the sequence is that a failure names its own cause.
Scope: approved list has 9 unused files (7 unused shadcn components).
knip --fix-type files --allow-remove-files→ 9 deleted. Verify:tsc --noEmitclean, 214 tests pass, build ok → commitchore(dead-code): remove 9 unreferenced modules. Cascade: re-run Knip — unused exports fell 22 → 6 and unused deps 4 → 6 (two deps were only used by deleted files). The plan's numbers were already stale in the right direction. Next:--fix-type exports,types→ typecheck flags 11 now-unused locals →remove-unused-vars→ re-run Knip → clean. Then:--fix-type dependencies→ 6 removed →npm ci+ build. Red: build breaks.date-fnswas referenced only inside a Tailwind config string. Restore that one dep, tag the findingkeep-config, add the config file as anentry. Do not restore the other five. Ratchet: baseline was 41/12; now 3/0. CI pinned at--max-issues 3.
plan-dead-code.md exists with a keep/kill list. No
plan → run plan-dead-code first. Never delete from a raw tool dump.workflow-green-repo's job first.package.json/CI, don't invent them.plan-test-coverage first for anything load-bearing.Config is already settled by the plan. If knip.json is still producing
hints, you are in the plan phase — go back. Knip's FAQ, verbatim:
"Auto-fixing in this state can lead to deleting code that your application
relies on."
--fix never runs bare. Always --fix-type, one class at a time. Fix
types are files, exports, types, dependencies, catalog.
--fix-type implies --fix, so it needs no companion flag.
--allow-remove-filesalone is also a bare fix. With no--fix-type, Knip enables every fix type plus file removal. Never pass it by itself.
After every category: typecheck → affected tests → build → commit.
Knip deletes what it reports now, not what the plan listed, and it has no dry-run. So read the set first, diff it, then fix:
npx knip --files --reporter json > knip-files.json # what would be deleted
# diff that set against the plan's approved list BEFORE fixing
npx knip --fix-type files --allow-remove-files
git status --short # confirm the deletion set
--allow-remove-files is opt-in precisely to prevent accidental deletion;
it is safe here only because a human already reviewed this exact list in
the plan. If Knip removed a file the plan did not list, git restore it
immediately and fix the config that caused it. Do not commit it, and do not
treat it as a new finding.
Files go first because their removal cascades: it collapses phantom export and dependency findings instead of leaving you to churn files you are about to delete anyway.
npx knip --fix-type exports,types
This removes the export keyword (and unused enum/namespace members), not
the implementation. What is left behind is now a module-private symbol —
which is exactly what C3 collects.
Knip does not remove unused variables inside files; that is a linter's job, and the official guidance is to run one and then re-run Knip.
remove-unused-vars reads linter JSON on stdin — run it bare and it
hangs waiting for input. Pipe it, and never combine it with a linter's own
--fix, or the reported positions stop matching:
npx eslint --rule 'no-unused-vars: error' --quiet -f json | npx remove-unused-vars
npx knip # rinse and repeat
It is version 0.0.x and self-describes as "highly experimental". Your linter's own unused-symbol autofix is the conservative substitute; either way, review the diff rather than trusting it.
Repeat C2–C3 until the count stops moving. Each round of unexporting creates a new round of unused locals, whose removal can orphan more exports.
npx knip --fix-type dependencies
Then a clean install (npm ci / pnpm install --frozen-lockfile) and
a full build. Dependencies referenced only from config strings, CLI
invocations, or peer requirements are the classic false positive here — a
broken build after C4 usually means one dep to restore plus one entry
pattern to add, not a rollback of the category.
Delete, never comment out. Commenting out is the dead code.
console.* / debugger outside intentional logging paths — if the repo
has a real logger, this is also a backend-observability handoff.only / .skip / .todo / xit / xdescribe / fdescribe — remove
the modifier, never the test. .only is the highest-priority item in
this whole skill, because it silently disables every other test in its
file. Removing it can turn the suite red: that is a bug it was
hiding, not a regression you caused. Fix or file it; never re-add
.only. A .skip/.todo is usually parked work — un-skip and fix it,
or leave it and register it in housekeep-backlog. Deleting a test to
lower this count is forbidden.utils/helpers dumping grounds — split by concernworkflow-refactornpx jscpd src --min-tokens 50 --reporters json
Near-identical components get parameterized into one; repeated try/catch
becomes a wrapper. That is behavior-preserving restructuring, not
deletion — so it belongs to workflow-refactor. Measure it here, ratchet
the percentage here, and hand the fixes over. If you do land them in this
pass, they are separate commits that never mix with C1–C4.
Delete only names with zero references in source, CSS, and HTML. Anything
reachable through a template string or hashed name stays until an owner
confirms — a name-based scan cannot see `/img/${slug}.png`.
Remove suppressions whose underlying error is gone (a stale
@ts-expect-error is itself dead code and the compiler will say so). For
the rest, fix the cause. Never blanket-delete @ts-ignore lines to
move a number; never widen a type to any to remove a suppression.
Full playbook: references/supabase-hygiene.md.
Ask the human to name the target database and wait for their reply. Do
not infer it from .env, a linked project ref, or anything said earlier in
the session. Then: retire in code → verify zero usage across a valid stats
window → author a reversible migration.
Three traps that make a "dead" object alive, all covered in the reference:
pg_cron job has zero client references and is load-bearing. Check
pg_policies, pg_trigger, and pg_proc.prosrc before believing a grep.functions.invoke() grep.pg_stat_* counts from stats_reset; a staging
database with no traffic makes everything look unused.Tables drop in two steps — rename to _deprecated_<name> first (errors
surface immediately, data intact, rollback is another rename), drop a release
later. Regenerate types in the same commit. Migrations go to db-migrator;
safety judgment stays with plan-data-integrity.
Never author or run a DROP against production in this skill. Index
drops are reversible; column and table drops destroy data.
Cleanup without a gate regrows within weeks. This is the deliverable that
makes the pass durable. Full config — scripts, tsconfig, ESLint flat config,
lint-staged, the job, aggregator wiring, and the Supabase types-drift guard —
is in references/ratchet-ci.md.
2a. Script it so humans, hooks, and CI run one definition — the same flags, or "passes locally" stops predicting CI:
{
"scripts": {
"knip": "knip --production --max-issues 0",
"knip:all": "knip --max-issues 3 --treat-config-hints-as-errors"
}
}
2b. CI job pinned to today's count. --max-issues defaults to 0;
set it to the post-cleanup number and only ever lower it. Run both
modes — Knip's CI guide suggests considering a default run alongside a
production run.
Put --treat-config-hints-as-errors on the default-mode run only:
production mode disables configuration hints entirely
(isDisableConfigHints = --no-config-hints || isProduction), so the flag
can never fire there. Exit codes: 0 clean, 1 issues found, 2 Knip
itself failed — treat 2 as a broken gate, never as a pass, or a crashed
run reads as green.
2c. Wire it into the single aggregator, don't add a fourth parallel
required check. The job joins the existing needs: list per
housekeep-gates. Ratchet policy also follows that skill: one baseline per
metric, and lowering the number is a separate reviewed PR from a code
change.
2d. Ratchet the other counts too — duplication %, suppression count,
console.* count. A number with no gate is a number that grows back.
2e. Local hooks — lint-staged runs ESLint per file; Knip is repo-wide
and belongs in pre-push, not pre-commit. Either way the hook calls the
same npm script CI calls, so "passes locally" predicts CI.
A gate nobody tested is a gate nobody has.
node_modules and fail on a clean install:
git clone . /tmp/dc-probe && cd /tmp/dc-probe && npm ci \
&& npm run typecheck && npm test && npm run build && npm run knip
export const zz = 1 to any file and push. The dead-code job and the
aggregator must go red. If the aggregator stays green, it is counting a
skipped job as success — that is housekeep-gates Phase 1. Delete the
branch after.knip --fix bare, or before config settles. Always --fix-type.
--allow-remove-files with no --fix-type counts as bare.ignore, ignoreFiles, ignoreIssues,
ignoreDependencies, --exclude, a narrowed --include, --workspace
scoping, --no-exit-code, switching off noUnusedLocals or a lint rule,
and — the quietest one — a rules entry downgraded to warn. Knip's
--max-issues total counts only issue types whose rule is error, so
rules: { files: "warn" } reports every dead file and still exits 0.
Use entry, paths, --production, or a @public/@internal tag to
fix a false positive; use deletion to fix a true one.npx shadcn add restores it in seconds. Ignoring it keeps
dead code and adds an exemption to maintain.--no-verify, never delete the failing test.@ts-ignore, any, skipped or
deleted test, or weakened assertion used to get greenignore* entry, --exclude, narrowed --include, --workspace scope,
--no-exit-code, rules: warn downgrade, or disabled compiler/lint
flag was used to move a numberDROP without a confirmed target and a reviewed
reversible migrationknip.json/tags so the next run does not re-report it--fix-type, one commit each, verified betweenworkflow-refactor
(or landed as separate refactor commits, never mixed with deletions)ignoreknip script + CI job with --max-issues at the new countworkflow-refactor; migrations →
db-migrator; leftover parked items → housekeep-backlog; gate
consolidation → housekeep-gatesApplied category-by-category. Pause for approval when a category's blast radius is large. Schema deletion always gets explicit confirmation.