Use after deleting a feature, function, component, validation rule, config flag, enum value, status, or any symbol — frontend or backend — to find the downstream artifacts the deletion orphaned. Covers orphaned string keys in object/label/i18n maps, user-facing copy, OpenAPI enums, dead branches, now-unused imports/props, stale comments, and test fixtures that lint and code review miss. Trigger when you removed code and want a clean, zero-dead-code diff.
73
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
When you delete something, the thing you deleted usually had dependents — code and data elsewhere that only existed to serve it. Deleting the definition without sweeping its dependents leaves dead code: it compiles, it lints, all tests pass, and the reviewer often misses it too. It just sits there, quietly wrong, until it confuses the next person.
Core principle: A removal is not "the definition." A removal is "the definition and everything that only existed for it." Sweep for dependents on the same commit that deletes the definition.
bun run lint (ESLint no-unused-vars) and pipenv run nox -s lint (flake8, which flags unused imports) catch unused bindings — a variable, import, or parameter no longer referenced. They do not catch:
"team-members": "Team Members" in a label map is not a binding; nothing flags it when its only consumer disappears.errors["team-members"], labels[key], dispatch({ type: "X" }). The key is data, not a symbol.if (mode === "legacy") whose only caller you removed.backend/openapi.yml enum lists and descriptions, user-facing copy (UI text, API error messages), Cypress comments, seed data.This repo has no knip / ts-prune / depcheck on the frontend and no vulture-style detector on the backend. Lint is the only static safety net, so the orphan classes above must be swept manually. (This is exactly the class of miss that survives review: a reviewer verifies the binding-level cleanup lint already guarantees, and the data-level orphan slips through.)
Run this checklist on the same branch as the deletion, before you call the work done.
List every identifier and string literal tied to the deleted thing:
data-cy attributes, event names, feature-flag strings, enum string values.The string keys are the ones that bite. Write them down explicitly.
Also write down every spelling the concept appears under. One removed concept is usually spelled several ways: UPPER_SNAKE enum value, Title Case label, kebab-case key, camelCase identifier — and prose copy can use different separators again (Planned Mod, Planned - Mod, Planned-Mod). A grep for one spelling misses the rest. Grep also can't find prose that paraphrases the concept without using the token, so read the user-facing copy in the diff too.
# For each removed symbol AND each removed string key:
grep -rn "team-members" src/ --include="*.js" --include="*.jsx" | grep -v "\.test\."
# For a removed enum value / status / label, one case-insensitive pattern that
# tolerates the separator variants (zero separators too, so camelCase matches),
# across frontend, backend, and docs:
git grep -n -i -E "planned[ _-]*mod" -- . ':!*.lock' ':!*package-lock.json'Exclude lockfiles by suffix or filename, not by a broad pattern. A pathspec with no wildcard (':!package-lock.json') only matches at the repo root, so use the leading * (':!*package-lock.json') to also skip nested copies. A broad pathspec like ':!*lock*' hides unrelated files whose path merely contains "lock" (e.g. procurement-shop-lock-parity).
For every hit, classify it:
For a removed producer (e.g. a validation rule that emitted an error key), find who consumed its output:
# Who reads this key / renders this error / maps this label?
grep -rn 'getErrors("team-members")\|convertCodeForDisplay("validation"' src/For a removed consumer (e.g. a component), find data that existed only to feed it — fixtures, selector maps, mock builders.
For a removed stored value (an enum member, status, or column), also check data at rest: existing rows may still hold the value, and removing it from code alone leaves them behind. Those rows need a data migration — see the db-migrations skill. Decide what they convert to; if the right replacement isn't obvious, that is a product question, so ask.
bun run lint # frontend (from frontend/): unused import/var/param
pipenv run nox -s lint # backend (from backend/ops_api): unused imports
git diff main # read every hunk: any comment, fixture field, or
# branch that referenced the removed thing?Lint clearing is necessary, not sufficient. The git diff read is where you catch the data-level orphans by eye.
Before deleting an orphaned key/label, grep tests for it. If a test asserts its presence, that test is testing the dead thing — update or remove it in the same sweep.
| Orphan class | Caught by lint? | How to find |
|---|---|---|
| Unused import / var / param | ✅ Yes | bun run lint / nox -s lint |
| Object-map / label / i18n key | ❌ No | grep the string literal |
String-keyed lookup (errors[k], action type) | ❌ No | grep the string literal |
| Unreachable branch after caller removed | ❌ No | trace callers, read diff |
| Stale comment / JSDoc | ❌ No | read the diff |
| Fixture field for a deleted rule | ❌ No | grep field name in fixtures/tests |
Enum value in openapi.yml / schema | ❌ No | grep every spelling of the value |
| User-facing copy naming the removed thing | ❌ No | grep every spelling, incl. separator variants |
| Existing rows holding a removed stored value | ❌ No | check data at rest; add a data migration |
PLANNED_MOD, Planned Mod, and Planned - Mod are three different strings. Use a separator-tolerant, case-insensitive pattern (step 2).helpers/ module, fixtures in a test dir, renderers in a page component. Grep repo-wide.On OPS-5882 (removing the team-member Vest validation), ESLint and a first code review both passed. A second review caught an orphaned "team-members": "Team Members" entry in the validation label map in src/helpers/utils.js — dead the moment the Vest test that produced the team-members error key was deleted, because its only consumer was convertCodeForDisplay("validation", key) in ReviewAgreement.jsx. Grepping the removed string key repo-wide (step 2) would have surfaced it immediately.
On OPS-6356 (removing the PLANNED_MOD budget line status), the plan's grep was planned_mod|planned mod|plannedmod. It returned nothing for the frontend, but an Award approval page paragraph still read "...Planned Status to Planned - Mod Status." — the hyphenated spelling matched none of those patterns. Review caught it. The separator-tolerant pattern planned[ _-]*mod would have found it. The same removal also had to reach openapi.yml, API error messages, and the existing database rows, none of which lint covers.
HHS/OPRE-OPS
f2b8bfc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.