Inspiration
Dead code used to be a hygiene problem. Now it's a tax. Every coding agent that reads your repo pays tokens for functions nothing calls, and retrieval keeps surfacing "solutions" that were ripped out years ago. Deleting it makes every downstream AI tool cheaper and sharper. The catch is that nobody dares delete, because "what if something still uses it?" is expensive to disprove. A knowledge graph is the thing that disproves it.
Proven on real open-source repos (0 false deletions, every reap grep-audited)
| Repo | Size | Zero-ref candidates | Reaped | False deletions |
|---|---|---|---|---|
| excalidraw | 28,029 defs | 586 | 0 + 94 unused exports flagged (4,938 lines) | 0 |
| vite | ~1,400 files | 860 | 3 source fns + 3 unused exports | 0 |
| pinia | ~115 files | 101 | 1 ($dispose correctly kept) |
0 |
| got | ~78 files | 264 | 0 + 1 actionable unused export | 0 |
Reading the table: a low reap count on a healthy, already-linted repo (excalidraw is knip-clean) is the correct result, not a miss. Most of those candidates are exported or used dynamically, and the Reaper declines them with a reason instead of dumping them on you. The reap rate scales with neglect — an unmaintained internal monorepo is where it earns its keep, not a polished OSS library. One number stays the same across all of them: zero false deletions.
Headline novelty: the dead island. Every MR draws the deletion as a Mermaid graph — the dead code as an island, with a dashed "0 references" line showing nothing live reaches it. Behind that picture is transitive dead-cluster expansion: a graph fixpoint that pulls in a dead function, then the helper only it called, then the helper only that called, until the whole unreachable chain is accounted for. On the demo it catches the simple case. On a neglected codebase it walks several hops deep. Even a one-function reap ships the same island graph, so the proof is on every MR, not just the interesting ones. A linter can't do this — it has no reference graph to walk.
What it does
Dead Code Reaper finds provably-unreferenced definitions and turns them into small, evidence-backed deletion merge requests:
- Facts from Orbit. It indexes the repository with the GitLab Knowledge Graph and runs one SQL query for definitions with zero inbound CALLS / EXTENDS / IMPORTS edges — cross-language, whole-repo, something grep cannot do.
- Judgment, deterministic-first. Zero references doesn't mean safe to delete. Conservative rules do most of the filtering — exported APIs, entry points, framework-registered and string-dispatched symbols, test fixtures — and an optional local model (keyless Ollama) takes the ambiguous remainder. Every decline ships with its reason.
- Proof from CI. Survivors become one small MR per file with an evidence chain ("0 inbound references · not exported · last modified 2024-03 · 22 lines"). The pipeline runs the full build and tests on every reaper branch; a human merges. Nothing ever touches the default branch directly.
On our curated demo repo: 11 zero-reference candidates → 8 reaped (95 lines, 5 MRs, CI green) — including two transitive dead clusters (a helper whose only caller was itself dead, found by fixpoint expansion) — and 3 declined with reasons, including an exported SDK helper and two string-dispatched event handlers. Zero false deletions. Pointed at excalidraw (28k definitions, 5s): 586 zero-reference candidates, zero false deletions, and a code-health inventory: 94 exported functions (4,938 lines) nothing references internally.
A per-language linter (vulture, knip) hands you those 586 as a flat, unfiltered to-do list, and it can't see cross-language or string-dispatched references at all. The Reaper declined all 586, each with a specific reason — exported-and-referenced, dynamically dispatched, framework-registered. A reviewer reads judgments, not raw suspects. Graph facts, conservative judgment, and an evidence chain: that's the difference.
The human cost is small and familiar. Reviewing a week of reaps is the same as reviewing any small PR — roughly 10 to 15 minutes for about 5 MRs — against thousands of tokens of dead context removed from every future agent pass. Nothing merges without that look and a green pipeline.
How we built it
- Orbit (GitLab Knowledge Graph):
glab orbit localindexes the repo into DuckDB; the canonical query joinsgl_definition/gl_edge, counts only true usage edges (DEFINES/CONTAINS are declarations, not references), whitelists Function/Method, and scopes byproject_idfrom_orbit_manifest. - Triage: Python (stdlib only). Heuristic prefilter → fact-grounded prompt to a local Ollama model at temperature 0. The prompt states what the graph already proved and allows a skip only for three concrete risks; any doubt → skip.
- MR engine: branch per file-cluster, bottom-up deletions, evidence-chain descriptions,
glab mr create. - Duo Agent Platform: the operating procedure is published as a public AI Catalog agent, wired to GitLab's built-in tools (run_command, grep, edit_file, create_commit, create_merge_request, …) — enable it on a project to run the workflow in-platform. The same procedure also runs today as a CLI and a zero-install scheduled-CI template, so it's verifiable end-to-end regardless of Duo Agent Platform beta access.
Challenges we ran into (and measured our way out of)
- Reference-resolution quality varies by language. We built a planted-ground-truth polyglot fixture and measured: only TypeScript/JavaScript cross-file import→definition resolution is reliable in orbit local v0.74.0 (Python/Ruby/Go imports don't resolve to definitions). So the Reaper only auto-deletes TS/JS; everything else is report-only. We'd rather ship a tool that says what it can't do than one that quietly guesses.
- Orbit's TS pipeline stores identifier-only spans (end_line == start_line). Naive deletion would have removed one line of a multi-line function. We wrote a string/comment-aware brace matcher that recovers the full body plus its attached comment block — and drop any candidate whose span can't be resolved.
- LLMs are too scared to delete. A generic "is this safe?" prompt declined everything ("legacy code might be used somewhere"). Grounding the prompt in verified graph facts — everything is indexed, zero references include tests, exports are pre-excluded, non-exported TS functions are unreachable from other files — produced exact ground-truth verdicts while keeping the conservative default.
Accomplishments we're proud of
- Zero false deletions across planted ground truth, proven by tests passing post-reap.
- The declined list doubles as the safety story — reviewers see why each survivor lived.
- End-to-end on day one: working pipeline, real MRs, published catalog agent.
What we learned
A graph can prove there are no references, but not that something is unused. An LLM can reason about use but can't prove anything. Splitting the job — graph for facts, model for judgment, CI for proof — is the whole design. Recent work on codebase knowledge graphs (arXiv 2603.27277, 2601.10773) lands in the same place.
What's next
- Org-wide scheduled reaping: a drop-in
.gitlab-ci.ymltemplate + cron runs the Reaper weekly across every repo in a group and auto-merges deletions that stay green for 24h — single-repo today, organisation-wide on one schedule. - More languages as Orbit's resolvers mature (the language gate is one set, flipped per language).
- Token-savings telemetry: measure exactly how much context each reap saves AI agents.
Built With
- ai-catalog
- duckdb
- gitlab-ci/cd
- gitlab-duo-agent-platform
- gitlab-knowledge-graph
- gitlab-orbit
- glab-cli
- mermaid
- ollama
- python
Log in or sign up for Devpost to join the conversation.