Show HN: CodeEraser – a deterministic judge of LLM-induced code and doc entropy CodeEraser, a deterministic tool for judging LLM-induced code and documentation entropy, was released on Hacker News, claiming to stop codebase drift by denying writes that introduce exact clones or exceed line limits, with no model in the loop. The tool supports Python, TypeScript/TSX, Rust, Go, Haskell, and Markdown, and includes a Claude Code plugin, pre-commit hooks, and CI gates, with a measured 48 ms p50 / 58 ms p95 probe response time and zero misfires on 719 production probes. · English | https://codeeraser.dev 中文 /skymanbp/CodeEraser/blob/main/README.zh.md An eraser against LLM-induced code & document entropy. Long-lived LLM-assisted codebases drift the same way: the same function implemented twice, the same paragraph pasted into three files, updates that arrive as appends, files that only ever grow. CodeEraser stops that drift at the moment of writing and gates it in CI, with no model in the loop anywhere. Two refusals happen at write time, before the content reaches disk. A write that would introduce an exact T1/T2 clone duplication the replaced content did not already carry is denied at PreToolUse, with the region it duplicates named and the ordering that passes taught; a write leaving a file over 750 lines, or over the line its rules.class declares, is denied the same way. Everything else is a report or a gate: the Stop audit refuses the turn, ce precommit and ce commitmsg refuse the commit, and the CI exit codes refuse the merge. Scope. Judged languages: Python, TypeScript/TSX, Rust, Go, Haskell and Markdown seven language codes over six tree-sitter grammars . Size-only arm: js/mjs/cjs/jsx, css/scss/less, html/htm, vue, svelte, sh/bash, yml/yaml. They enter the size gates, the hard budget and the ratchet, never a semantic verdict. Faces: CLI · GUI eleven screens · Claude Code plugin three hooks, one skill, one command, sixteen read-only MCP tools · pre-commit · CI. - Interception at the instant of writing. Every file's normalized tokens identifiers → ID , literals → LIT , comments dropped are winnowed with k = 25, w = 26, so any shared run of 50+ tokens is guaranteed a shared fingerprint. The fingerprints live in a SQLite WAL index kept by a lazy per-project daemon; the PreToolUse probe answers in 48 ms p50 / 58 ms p95 on a two-file fixture, and the whole plugin chain in 0.50 s p95 as last measured on 2026-08-29, before the tombstone leg joined it. The guard charges only novel duplication: matches the replaced content already carried are subtracted, so on the live-stream reading it misfires on none of 719 production probes 0.00 per 500 ; the 2,761-event replay's full-file-write reading charges the 32 split-a-file intermediate states at 7.03 per 500; both readings are ledgered in FPR-REPLAY /skymanbp/CodeEraser/blob/main/docs/FPR-REPLAY.md . - Two clone layers, one verdict owner. T1/T2 is the hot path above. T3 is a cold path: structural fingerprints and MinHash/LSH 128 permutations, 32 bands × 4 rows generate candidates without dropping a pair that could pass, and the Haskell core computes Zhang–Shasha tree edit distance and accepts at TSED ≥ 0.85, in exact integer arithmetic. - Documentation duplication that survives rewording. NFC-normalized words, 5-word shingles, MinHash/LSH candidates, then an exact Jaccard ≥ 0.80 or a 50-word verbatim run, judged in the core with exact rationals. - Liveness that is named, not guessed. Per-language resolution ladders imports, re-exports, doc links, assets, package roots feed a rung-filtered graph; SCCs, reachability from entry roots and a four-way verdict unreferenced/unreachable × private/public come back with a confidence code derived from the unresolved-site ledger. Beside it, the mention universe every identifier in every text file, stored only as fnv1a64 hashes yields the unmentioned declaration advisory, which never turns a gate red. - Same-role advice from the repository itself, no model. The index's own facts become term bags names, shapes, callees, docs, structure, literals , scored with integer BM25 k1 = 6/5, b = 3/4 and judged in the core: candidates in one exact order, plus a role bit that holds only when the name, callee and shape channels agree. --widen adds the in-repo PPMI associative view. Advisory by construction: no exit code, no gate, no hook block. - Structure as a measured thing. Eight axes geometry, naming diversity, mixing, misplacement, documentation coverage, stale docs, redundancy, modularity , Tsallis-2 entropy per directory, chi-squared divergence from a declared layout, and split-ROI pricing with four cost legs crossing references, clone cuts, churn crossings, a new-file φ or a cohesion alibi. - A check score that cannot be gamed by moving lines. The gate's own axes size, complexity, clones, documentation duplication, dead code, churn, cycles each charge violation mass over opportunity as floor 1000·v/ v+n , and the weighted fold lands on 0–1000. The ADR-006 ratchet tightens every ceiling automatically; growth needs the tolerance max +2 %, +10 lines or a named re-establish CE ACCEPT BASELINE=1 , and a knob edit stops ce check by name instead of moving every line. - Time as a first-class signal. Theil–Sen slope over the last 512 score points a single wild point cannot drag a median ; churn = added − surviving lines by blame; the join lattice combines similarity, graph position and churn into merge / delete / churn-hotspot with reason bits and confidence. - Erase with a safety predicate, not a heuristic. Three classes verbatim doc twin, whole-unit T1 twin whose copy is dead, confident non-public dead file , seven frozen reason codes, a 4,096-row cap, and a convergence re-plan that fails if any applied verdict survives. - Tombstone residue, judged as a conjunction. Narrating a name the same change erased leaves a residue: a struck-through or removed label, or a sentence carrying a backward-looking mark no longer , used to , 曾经 … together with the erased name. Rust measures the changeset's surfaces the names it erased, its new labels, its new sentences ; the core judges them sentence by sentence a mark and an erased name together; a label needs the name alone against a floor and the class's tombstone budget . PreToolUse, the Stop audit and ce precommit / ce commitmsg speak it, each only at the class's tombstone tier and only when the core answers over . Changelog-role documents are exempt by path, by ledger shape, by a segment with three version witnesses or by a tombstone ledger declaration, and the false-positive rate is replayed on git history before any default moves FPR-TOMBSTONE /skymanbp/CodeEraser/blob/main/docs/FPR-TOMBSTONE.md . - Deterministic by construction. No RNG or clock in any judgment; golden fixtures compared byte for byte; configuration crosses as facts, never as names. | | 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 task add discounts, a compact report, CSV and JSON output, money formatting in the API is replayed by a scripted agent on demo/seed /skymanbp/CodeEraser/blob/main/demo/seed/README.md , a small invoicing service in Python and TypeScript. The seed is measured first, so every finding below was written by the task. Each loop then runs to its end; with nothing in the loop nothing refuses anything, so that one ends at the last write. Every verdict is the verbatim output of ce , and both trees are measured by the same six commands. | | Without CodeEraser | With CodeEraser | |---|---|---| | The seed, by the same six gates: clone blocks · doc twins · dead files | 0 · 0 · 0 | 0 · 0 · 0 | | Writes that landed | 7 of 7 | 5 of 7 | | Denied at PreToolUse | 0 | 2 | | Stop audit | not in the loop | blocked — this session's edits leave 2 duplicate block s touching changed files net +105 LOC … | | The repair the audit named | — | written, and the audit goes silent | | ce erase --apply | — | 1 row removed: the verbatim doc twin | | ce check score ratchet | 871/1000 — FAIL : ratchet over, discrete added | 979/1000 — FAIL : ratchet over | | T1/T2 clone blocks ce dedup --check , budget 0 | 4 FAIL | 0 pass | | near-miss clone pairs ce clone | 4 | 0 | | duplicated doc segments ce docdup --check | 1 FAIL | 0 pass | | dead files ce deadcode --check | 3 FAIL | 2 FAIL | | provably-safe removals still planned ce erase --check | 1 FAIL | 0 pass | The two denied writes are copies of an existing helper. The compact renderer that slipped past is the honest boundary, since a full-file rewrite copies its own blocks and nothing is novel at write time. It is what the Stop audit refuses the turn over, naming both blocks; the repair that answers it is the only write made because a gate asked rather than because the task did. What stays red is what a person has to settle: invoicer/invoice.py is 93 lines against a tolerated ceiling of 61, which the ratchet holds open for a named re-establish instead of absorbing silently, and two files are unreferenced: the new page nothing links to, and the renderer the CLI stopped importing on its way to JSON. Both transcripts, both SVGs and the JSON behind this table are generated by demo/run.js /skymanbp/CodeEraser/blob/main/demo/README.md and re-checked byte for byte in CI demo replay . The first real interception, recorded the day it happened: T1-INTERCEPT /skymanbp/CodeEraser/blob/main/docs/T1-INTERCEPT.md . Two moments from close up on the same seed; the second adds one ce.toml declaration. A copied helper, refused before the file exists. Move 1 of the run above, on its own. The reason names the region the content duplicates and the ordering that would pass, so the refusal is actionable rather than a veto. bash $ Write invoicer/discount.py ✗ ce: content for