LLMs write code that accretes: duplicated blocks, dead files, stale docs, structural sprawl. CodeEraser measures it, judges it, and gates it with a score that is only allowed to get better.
$ ce check --roast check score 923/1000 | axes 0:71 1:0 2:297 3:0 4:0 5:0 6:175 | 521 candidates ratchet: 0 added, 0 removed, 0 over, 0 tolerance drawn -> pass note: 10 blocks collapsed into existing members, 306 intra-file pairs off the sim table roast: suspiciously clean. who did you pay?
Real output: CodeEraser judging its own repository. The ratchet only shrinks: every violation is banked in a baseline, and new entropy fails the gate.
| Without CodeEraser | With CodeEraser | |
|---|---|---|
| writes refused before they reached disk | 0 | 2 |
| duplicate clone blocks left behind | 4 | 0 |
| duplicated doc segments | 1 | 0 |
| removals still owed | 1 | 0 |
| check score | 871 | 979 |
One seven-step task, two identical copies of the seed; the only variable is whether CodeEraser is in the loop — the write-time guard, the Stop audit, and, once the audit refuses, the eraser acting on its own plan. Both runs still end red — not on the same things.
The seed is demo/seed/, a small cent-exact invoicing service in Python and TypeScript, measured clean by the same gates before either run; the task adds discounts, a compact report, CSV and JSON output, and money formatting in the API.



ce join prints.GitHub Releases: GUI + CLI + core, five platformsbrew install skymanbp/codeeraser/codeerasercargo install codeeraserce setup # or /plugin marketplace add skymanbp/CodeEraser@releasece update --yesce update swaps ce and ce-core only after both SHA256 pins verify; the GUI has its own update screen.
ce setup, AppImage / dmg users run it oncece update compares the latest tag against this build (exit 0 / 1 / 2); --yes replaces ce and ce-core after both SHA256 pins verify (never a copy the plugin or cargo owns, which name their own update), and --installer saves the verified GUI installer. The GUI has an update screen; in Claude Code, /codeeraser:update.
ce check . --fail-under 911 # ratchet + floor; exit 1 turns the pipeline redce precommit # staged net LOC + touched duplicatesce mcp # every report family as a read-only tool for any agentThe gate. One 0–1000 score over seven axes, a shrink-only ratchet against the banked baseline, and a hard floor for CI.
T1/T2 clone detection via winnowing fingerprints, with a clone-block budget your repo can only ratchet down.
T3 near-clones judged by tree edit distance in the Haskell core, renamed and reshaped copies included.
Liveness verdicts on the cached reference graph: no kept in-edge and no entry flag means dead, and the gate says so.
Beside it, the unmentioned-declaration advisory, which never reddens a gate.
Duplicated prose across docs and comments, judged by exact Jaccard over live segments.
Tree-scale entropy: layout divergence, doc staleness, redundancy and modularity axes over the whole repository.
Size, complexity and readability per file and function, the classic metrics core-graded against the global table or the file's own [[rules.class]] lines.
Your score across mainline history, cached per commit, rebuildable, honest about direction.
The deterministic two-phase eraser: dead files, verbatim document twins and whole-unit exact clones.
Planned first, applied only from a clean worktree, never by asking a model to rewrite code.
Change heat over a git window, attributed per unit, so you see how much of a file is being rewritten rather than added to.
The three-signal join: churn, duplication and liveness folded into one verdict, so a hot duplicated dead corner names itself.
The same-role advisor: integer BM25 over term bags the index already holds, with the core deciding which candidates play the query's role.
A deterministic offline retrieval, advice only, never a gate.
A Datalog over the index’s own facts: ask a question and every answer comes with its derivation; write architecture constraints as assertions and ce rules is a gate that exits on the first violation.
Judged in the core, labelled back by the CLI.
Dead code inside one function: statements no path reaches, stores no path reads, locals nothing reads.
The core walks the control-flow graph; a finding is a verdict only in a language whose blind-reviewed precision exam passed, and an unused parameter is always advice.
How a clone group would fold into one function: the places its members differ become parameters, and the core answers the parameter count, the member worth keeping, the lines saved, and feasible or why not.
Advice only, never a gate.
How the directories depend on each other: the layers, the cheapest arcs to cut out of their cycles, the file clusters and the files outside their cluster's directory, each directory's fan-in, fan-out and instability, and what a change to a named file reaches.
Advice only, never a gate.
As a Claude Code plugin: duplicate writes intercepted at the moment of writing, a write you let through at ask recorded once it lands, audits at stop, health at session start.
Every verdict is integer or exact-rational arithmetic over measured facts, and every rule is cited to the line that implements it. One rule per row, each linked to its derivation.
t = window + kgram - 1 = 26 + 25 - 1 = 50 tokens
Any common run of at least t normalized tokens yields at least one shared fingerprint: the Schleimer et al. SIGMOD'03 no-miss bound, held as a correctness contract.
TSED(a, b) = (max(n1, n2) - ted(a, b)) / max(n1, n2)
cloneDecidesWith (num, den) t n1 n2 = (mx - t) * den >= num * mx where mx = max n1 n2Zhang-Shasha edit distance is always integral, so a near-miss clone at TSED ≥ 0.85 is decided by exact cross-multiplication.
dupDecidesWith num den inter union = inter * den >= num * unionTwo documentation blocks are duplicates at Jaccard ≥ 0.80 over five-word shingles, or on a verbatim run of 50 words; MinHash/LSH only proposes the pairs.
tsallis2Norm cs = tsallis2 cs / (1 - 1/n) -- n = nonzero bins, n > 1
chi2 pairs = Σ_{r > 0} (p - q)^2 / q -- p = o/Σo, q = r/ΣrDirectory diversity and layout divergence in exact rationals: the log-free members of the entropy and KL families, so every boundary is decidable.
rho = q / qMax = (e*m − o*i) / (mu*(m − mu))
Each directory's modularity contribution as a share of the most it could earn: 1000‰ when it never reaches out, 0 at the null model's expectation.
score = max 0 (scoreScale - raw `div` (violCostNeutral * wTotal))
tolerated(c) = max (c * tolNum `div` tolDen) (c + tolAbs)
added = current \ baseline -- non-empty => failThe axes fold into one 0–1000 score under a convex size penalty; each file's ceiling only shrinks, and growth past max(+2 %, +10) fails the gate.
+1 for each method in a recursion cycle, whether direct or indirectSonarSource's cognitive complexity with the recursion increment of S3776 Appendix B1, which SonarSource's own analysers leave out: the core finds the call cycles and every member pays once.
reach = ⋃ { reachable(G, s) | s ∈ entries(entryMask, flags) }
judged = i ∉ reachA file nothing live reaches is dead; cycles are found once, by Tarjan's strongly connected components, and reported, never judged.
benefitMilli(u) = max 0 (floor (1000 * (p(total) - p(end_u) - p(total - end_u)))) viable ⇔ b >= c -- ROI >= 1, evaluated without division
The penalty curve is convex with p(0) = 0, hence superadditive: a seam's benefit is never negative, and it is priced against what the cut costs.
slope = median{ (y_j - y_i) / (x_j - x_i) : x_i ≠ x_j } -- Theil-SenThe trend is the median of pairwise slopes: one wild commit cannot move it past its neighbours.
score = Σ w · idf · 22·tf·avg / (10·tf·avg + 3·avg + 9·len) (k1 = 6/5, b = 3/4; integer fixed point) PPMI(a, b) = max(0, log2(n_ab · N / (n_a · n_b)))
Integer BM25 with k1 = 6/5 and b = 3/4 ranks the units most like one unit; in-repository PPMI may widen a query, as advice only.
dead(F) :- file(F), not reach(F).
strata : a head sits above every negated or aggregated body predicate; a negative edge inside an SCC is a program error
eval : semi-naive per stratum over lazy bitmask indexes; the first derivation of a tuple is its provenanceDatalog evaluated semi-naively, one stratum at a time, over the index's own facts; every answer carries the derivation that produced it.
kind 0 : unreachable = no path from the entry, one finding per maximal run of seqs kind 1 : dead_store = a write no path reads before the next write or the exit (backward liveness)
Reachability over each function's control-flow graph, and backward liveness over its variables.
align : T1/T2 isomorphic, holes = differing leaves; T3 the tree-edit mapping narrowed top-down, relabels and unequal gaps are holes params : one per distinct value vector; feasible iff every hole is an expression or a name, params ≤ 6 and savings > 0
Where the members of a clone group differ becomes a hole, and each distinct value vector a parameter: at most six, and only when a line is saved.
cuts : per SCC, a subset programme when it has ≤ 14 directories (exact 1), else Eades–Lin–Smyth + redundancy pass (exact 0)
layers : level(d) = 0 when nothing leaves d, else 1 + max level of what d points at, the cut arcs removed
metrics: fanIn, fanOut, instability = ⌊1000 · out ÷ (in + out)⌋, −1 when nothing touches the directoryDirectories ordered into layers once the cheapest arcs are cut out of their cycles, with each directory's instability in integer per-mille.
50 ms907 ms650 ms0.00 per 500 edits17/17 scoped (100%)Every number is produced by replay (cli/tests/it/bench.rs and bench_backfill.rs) from one source, contracts/bench/bench.json — never hand-filled.
Complete evaluation dashboard →
docs/diagrams/architecture.en.json; every component cites its source files. Open the full-size SVG.Open the component-level stack and its enforcement map →
All fifteen release binaries (five platforms × ce / ce-core, plus the five
GUI installers) are pinned by SHA256 in plugin/bin/manifest.env;
the tag phase verifies every pin, waits for a green CI on the tagged commit, and refuses
an unexpected asset before anything publishes. SHA256SUMS covers the set for
offline checking. Code signing is deliberately out of scope; the hash chain is the permanent
anchor. This repository gates itself with its own tool on every commit.