Run vulture over the target, then sort each hit into truly-dead vs
reached-by-a-mechanism-vulture-can't-see. Vulture's static analysis only sees
direct calls in the code it reads — it cannot see a console_scripts
entrypoint, a pytest fixture injected by parameter name, a plugin registered
by decorator, or any other name-based dynamic dispatch. Reporting every
vulture hit as dead would tell you to delete load-bearing code; the judgment
pass here is what makes the report safe to act on.
Two passes. audit.py in this skill's directory is pass one — mechanical:
run vulture, parse its stable text-output format into findings rows. It never
judges dead vs dynamic; every row it emits starts bucket: unsure. The
judgment pass below reads those rows and reassigns the real bucket.
Buckets & categories
dead — no mechanism reaches it. Delete it.
dynamic — vulture flagged it, but something outside static call analysis
reaches it: an entrypoint (if __name__ == "__main__", a console_scripts
entry point, a CLI command function), a test fixture (@pytest.fixture,
setUp/tearDown, a fixture referenced only by name in a test's
parameter list), a dunder/protocol method (__enter__, __eq__,
__getattr__) invoked implicitly by the language, or a plugin/handler
registered by decorator or string lookup (@app.route(...),
getattr(self, f"handle_{kind}")).
unsure — vulture flagged it, and it isn't a clean fit for either bucket
above: a low-confidence hit (see below), or a name that could be reached
by something outside the repo (a public library export with unknown
external callers) but shows no concrete dynamic-dispatch evidence in this
codebase. Default here when the judgment call isn't clean — false "dead"
costs more than a false "unsure."
category is the mechanical kind vulture reported, slugged:
unused-function, unused-import, unused-class, unused-method,
unused-variable, unused-attribute, unused-property. extra.confidence
carries vulture's percentage (60% is its default floor; imports report at
90%, unused variables/attributes lower). Confidence is vulture's own signal
about how sure it is the name is unused, not the triage bucket — a 60%
unused function that turns out to be a console_scripts entrypoint is
still dynamic, not unsure, once the entrypoint evidence is concrete.
Run
Scope tight. Audit $ARGUMENTS if given; with no argument, scope
defaults per ~/.agents/skills/all-audits/SKILL.md's Scope section. Skip
vendored, generated, and dependency trees (node_modules, dist, .venv,
vendor, build output, lockfiles) and any .git/ or worktrees/ tree.
Pass one — run vulture, parse it.
uvx vulture <scope> \
--exclude "*/node_modules/*,*/.venv/*,*/dist/*,*/vendor/*,*/.git/*,*/build/*,*/worktrees/*" \
> /tmp/vulture-out.txt
python3 ~/.agents/skills/dead-code/audit.py /tmp/vulture-out.txt
audit.py's parse_vulture(text) -> list[dict] is the tested seam
(~/.agents/skills/dead-code/fixtures/ + answer-key.md back it, mirroring
~/.agents/skills/test-audit/fixtures/) — pure, no subprocess inside it, fed vulture's
captured text. main() wraps it: reads a file argument or stdin, prints
one JSON row per hit. This is a candidate list, not a verdict — every row
still needs the judgment pass.
Pass two — judge every row. For each row, read the flagged symbol
in context: its definition, its decorators, whether it's named or shaped
like an entrypoint, whether the module it lives in is a plugin/handler
registry. Reassign bucket per the rules above, and rewrite summary /
failure to say why — "reached via @app.route decorator registration
at line N" for a dynamic, "no caller, no decorator, no entrypoint shape"
for a dead.
Write the findings log and render the summary — the default
deliverable. See
~/.agents/skills/all-audits/harness/AUDIT-RUN.md for the shared
write-and-deliver step (tmpdir resolution, findings.jsonl +
report.html, opening, and the final print). This audit touches no
code — deleting dead code is a separate, opt-in step the user asks for by
name. This skill's own bucket names and metabar:
- Log — one JSONL line per vulture hit.
bucket is dead / dynamic
/ unsure. category is the vulture kind, slugged (see above).
extra.confidence carries vulture's percentage.
- Summary — the verdict, the
N flagged · D dead · Y dynamic · U unsure metabar, findings grouped by bucket then category with counts.
No per-hit cards. Call out the dynamic finds in a vt-callout — the
ones a naive vulture-only read would have wrongly told the user to
delete.
Verify against the fixture
~/.agents/skills/dead-code/fixtures/sample.py carries four dead symbols (an unused import,
an uncalled function, an uncalled class, and its uncalled method) and two
dynamically-reached ones (a main() entrypoint, a @pytest.fixture).
~/.agents/skills/dead-code/fixtures/answer-key.md has the captured vulture output and the
expected bucket for each. Running this skill over ~/.agents/skills/dead-code/fixtures/
should reproduce that table.
1---2name: dead-code3description: Find code nobody calls — dead functions, classes, unreachable branches, stray imports — and sort real dead code from code that only looks unused.4---56Run vulture over the target, then sort each hit into truly-dead vs7reached-by-a-mechanism-vulture-can't-see. Vulture's static analysis only sees8direct calls in the code it reads — it cannot see a `console_scripts`9entrypoint, a pytest fixture injected by parameter name, a plugin registered10by decorator, or any other name-based dynamic dispatch. Reporting every11vulture hit as dead would tell you to delete load-bearing code; the judgment12pass here is what makes the report safe to act on.1314**Two passes.** `audit.py` in this skill's directory is pass one — mechanical:15run vulture, parse its stable text-output format into findings rows. It never16judges dead vs dynamic; every row it emits starts `bucket: unsure`. The17judgment pass below reads those rows and reassigns the real bucket.1819## Buckets & categories2021- **`dead`** — no mechanism reaches it. Delete it.22- **`dynamic`** — vulture flagged it, but something outside static call analysis23 reaches it: an entrypoint (`if __name__ == "__main__"`, a `console_scripts`24 entry point, a CLI command function), a test fixture (`@pytest.fixture`,25 `setUp`/`tearDown`, a fixture referenced only by name in a test's26 parameter list), a dunder/protocol method (`__enter__`, `__eq__`,27 `__getattr__`) invoked implicitly by the language, or a plugin/handler28 registered by decorator or string lookup (`@app.route(...)`,29 `getattr(self, f"handle_{kind}")`).30- **`unsure`** — vulture flagged it, and it isn't a clean fit for either bucket31 above: a low-confidence hit (see below), or a name that *could* be reached32 by something outside the repo (a public library export with unknown33 external callers) but shows no concrete dynamic-dispatch evidence in this34 codebase. Default here when the judgment call isn't clean — false "dead"35 costs more than a false "unsure."3637`category` is the mechanical kind vulture reported, slugged:38`unused-function`, `unused-import`, `unused-class`, `unused-method`,39`unused-variable`, `unused-attribute`, `unused-property`. `extra.confidence`40carries vulture's percentage (60% is its default floor; imports report at4190%, unused variables/attributes lower). Confidence is vulture's own signal42about how sure *it* is the name is unused, not the triage bucket — a 60%43`unused function` that turns out to be a `console_scripts` entrypoint is44still `dynamic`, not `unsure`, once the entrypoint evidence is concrete.4546## Run47481. **Scope tight.** Audit `$ARGUMENTS` if given; with no argument, scope49 defaults per `~/.agents/skills/all-audits/SKILL.md`'s Scope section. Skip50 vendored, generated, and dependency trees (`node_modules`, `dist`, `.venv`,51 `vendor`, build output, lockfiles) and any `.git/` or `worktrees/` tree.52532. **Pass one — run vulture, parse it.**54 ```sh55 uvx vulture <scope> \56 --exclude "*/node_modules/*,*/.venv/*,*/dist/*,*/vendor/*,*/.git/*,*/build/*,*/worktrees/*" \57 > /tmp/vulture-out.txt58 python3 ~/.agents/skills/dead-code/audit.py /tmp/vulture-out.txt59 ```60 `audit.py`'s `parse_vulture(text) -> list[dict]` is the tested seam61 (`~/.agents/skills/dead-code/fixtures/` + `answer-key.md` back it, mirroring62 `~/.agents/skills/test-audit/fixtures/`) — pure, no subprocess inside it, fed vulture's63 captured text. `main()` wraps it: reads a file argument or stdin, prints64 one JSON row per hit. This is a candidate list, not a verdict — every row65 still needs the judgment pass.66673. **Pass two — judge every row.** For each row, read the flagged symbol68 in context: its definition, its decorators, whether it's named or shaped69 like an entrypoint, whether the module it lives in is a plugin/handler70 registry. Reassign `bucket` per the rules above, and rewrite `summary` /71 `failure` to say *why* — "reached via `@app.route` decorator registration72 at line N" for a `dynamic`, "no caller, no decorator, no entrypoint shape"73 for a `dead`.74754. **Write the findings log and render the summary — the default76 deliverable.** See77 `~/.agents/skills/all-audits/harness/AUDIT-RUN.md` for the shared78 write-and-deliver step (tmpdir resolution, `findings.jsonl` +79 `report.html`, opening, and the final print). This audit touches no80 code — deleting dead code is a separate, opt-in step the user asks for by81 name. This skill's own bucket names and metabar:8283 - **Log** — one JSONL line per vulture hit. `bucket` is `dead` / `dynamic`84 / `unsure`. `category` is the vulture kind, slugged (see above).85 `extra.confidence` carries vulture's percentage.86 - **Summary** — the verdict, the `N flagged · D dead · Y dynamic · U87 unsure` metabar, findings grouped by bucket then category with counts.88 No per-hit cards. Call out the `dynamic` finds in a `vt-callout` — the89 ones a naive vulture-only read would have wrongly told the user to90 delete.9192## Verify against the fixture9394`~/.agents/skills/dead-code/fixtures/sample.py` carries four dead symbols (an unused import,95an uncalled function, an uncalled class, and its uncalled method) and two96dynamically-reached ones (a `main()` entrypoint, a `@pytest.fixture`).97`~/.agents/skills/dead-code/fixtures/answer-key.md` has the captured vulture output and the98expected bucket for each. Running this skill over `~/.agents/skills/dead-code/fixtures/`99should reproduce that table.