内容
# CodeEraser
[](https://github.com/skymanbp/CodeEraser/actions/workflows/ci.yml) [](https://crates.io/crates/codeeraser) [](https://www.npmjs.com/package/codeeraser) [](LICENSE) [](https://codeeraser.dev) · English | **[中文](README.zh.md)**
> An eraser against LLM-induced code & document entropy.

## What it is
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 <!--ce:gate:size.file_lines_fail#digits-->750<!--/ce--> 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, C, C++ (a `.h` reads as C++), Java, Lua, R, Markdown and HTML (<!--ce:count:langs#word-->thirteen<!--/ce--> language codes over <!--ce:count:grammars#word-->twelve<!--/ce--> tree-sitter grammars). Size-only arm: js/mjs/cjs/jsx, css/scss/less, vue, svelte, sh/bash, yml/yaml. They enter the size gates, the hard budget and the ratchet, never a semantic verdict. Prose-only arm: `.txt`. Plain text enters the documentation-duplication corpus and nothing else — no size gate, no budget, no other verdict (`CMakeLists.txt`, `compile_flags.txt` and `robots.txt` are machine formats, not text). Faces: CLI · GUI (<!--ce:count:screens#word-->twelve<!--/ce--> screens) · Claude Code plugin (<!--ce:count:hooks#word-->four<!--/ce--> hooks, <!--ce:count:skills#word-->one<!--/ce--> skill, <!--ce:count:commands#word-->one<!--/ce--> command, <!--ce:count:mcp_tools#word-->twenty-one<!--/ce--> read-only MCP tools) · pre-commit · CI.
## How it works, and what is different about it

<sub>Zoom and pan this diagram at [codeeraser.dev/how/#verdict](https://codeeraser.dev/how/#verdict).</sub>
- **Interception at the instant of writing.** Every parsed code 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 <!--ce:restate:hook-probe:p50-ms:hook-probe#lead-->50<!--/ce--> ms p50 / <!--ce:restate:hook-probe:p95-ms:hook-probe#lead-->57<!--/ce--> 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](docs/FPR-REPLAY.md), which since 1.8.0 also carries one row per language plan v2.30 added (six corpora, 400 commits each, 0 false intercepts).
- **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. Since 1.8.0 the verified pairs enter `ce check`'s clone axis and `ce join`'s similarity leg as their own kind, and the core's verdict for a pair of trees is remembered in the index — asked once, replayed until either tree, the core or a knob changes.
- **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, includes, requires, 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.
- **Questions the index can answer, rules it can enforce.** A Datalog over the index's own facts (files, references, units, clones, mentions, path classes): `ce query` answers one question with every answer's derivation, and `ce rules` judges a rules file's assertions as a gate, a violation being a witness row with its proof. The core parses, checks (sorts, safety, stratification), evaluates semi-naively and proves; Rust lexes, assembles the fact tables and labels the ids back, so no name crosses the wire.
- **Dead code inside a function, read off its flow graph.** Rust lowers every unit of a language its `FlowSpec` table knows into four integer tables (units, statements, variables, accesses); the core builds the control-flow graph and answers unreachable statements, dead stores and unused locals and parameters. `ce flow`, the MCP tool `flow`, the GUI reports hub's flow family and the guard's flow class (`[flow] tier`, shipped at observe) read one document; 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.** For every T1/T2 family and T3 pair, Rust sends each member's tree in the clone family's postorder encoding with two more integer columns, a leaf's source hash and its position class (statement, expression, type, name, other); the core lines the members up, turns the places they differ into parameters (anti-unification) and answers the parameter count, the member kept (the most-referenced file), the lines saved, and feasible or why not. `ce merge`, the MCP tool `merge_suggestions` and the GUI reports hub's merge family read one document, every parameter labelled with each member's text; it is advice, never a gate.
- **How the directories depend on each other.** Rust sends the measured files with their directories and line counts, the directory tree, the file references and the package-grain references a file makes to a whole directory as five integer tables; the core folds them onto the directory graph, cuts its cycles at the cheapest feedback arc set (exact for a component of at most fourteen directories, a greedy order beyond, each cut arc marked with its road), layers what is left, clusters the file graph (a deterministic Louvain), names the files outside their cluster's directory, walks the impact of the `--impact` files and measures each directory's fan-in, fan-out and instability. `ce arch`, the MCP tool `architecture` and the GUI reports hub's arch family read one document; it is advice, never a gate.
- **Structure as a measured thing.** <!--ce:count:structure_axes#Word-->Eight<!--/ce--> 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.** <!--ce:count:erase_classes#Word-->Three<!--/ce--> classes (verbatim doc twin, whole-unit T1 twin whose copy is dead, confident non-public dead file), <!--ce:count:erase_reasons#word-->seven<!--/ce--> frozen reason codes, a <!--ce:gate:erase.row_cap#digits-->4,096<!--/ce-->-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](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.
## Evidence: the same task, run twice
<!-- scoreboard:begin -->
| | 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.
<!-- scoreboard:end -->
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`](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.
<!-- demo:begin -->
| | 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**) |
<!-- demo:end -->

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`](demo/README.md) and re-checked byte for byte in CI (`demo_replay`). The first real interception, recorded the day it happened: [T1-INTERCEPT](docs/T1-INTERCEPT.md).
Two moments from close up on the same seed; the second adds one `ce.toml` declaration.
<!-- vignettes:begin -->
**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.
```console
$ Write invoicer/discount.py
✗ ce: content for <work>/invoicer/discount.py duplicates 1 indexed region(s): invoicer/money.py:1-18 (89 tokens). Reuse the existing implementation instead of re-writing it. Moving it? Trim the source region first: the probe verifies against the current tree, and the same write then passes.
```
**One line, two mouths.** `ce.toml` puts `invoicer/**` on `file_lines_fail = 40`. The write-time guard refuses the write that would cross it, and `ce scan` grades the same tree against the same number — one declaration, read by the hook and by CI.
```console
$ Write invoicer/invoice.py
✗ ce: this write leaves <work>/invoicer/invoice.py at 93 lines, past the hard budget of 40 (plan §4.1). Split the file instead of growing it.
$ ce scan .
FAIL invoicer/invoice.py:1 file-lines = 51 (limit 40) [invoicer/invoice.py]
warn invoicer/report.py:1 file-lines = 35 (limit 30) [invoicer/report.py]
scanned 9 files / 19 functions — 1 warn, 1 fail -> FAIL (failed: hard_line)
```
<!-- vignettes:end -->
<!-- bench:begin -->
### Latency · v1.8.0
| percentile | `check_warm` | `deadcode_warm` | `dedup_cold` | `dedup_warm` | `docdup_warm` | `hook_probe` | `scan` |
|---|---:|---:|---:|---:|---:|---:|---:|
| p50 ms | 4643 | 1482 | 13929 | 650 | 2137 | 50 | 907 |
| p95 ms | 8340 | 4550 | 14144 | 655 | 2149 | 57 | 2896 |
Every value is generated from `contracts/bench/bench.json`; the test rejects hand edits to this block. [Full replay notes and per-version series](docs/BENCH.md) · [Complete website dashboard](https://codeeraser.dev/bench/)
<!-- bench:end -->
Latency rows are release-build replays on one fixed host, comparable version to version only. The precision and recall points are frozen with their evaluation ledgers ([EVAL-SET](docs/EVAL-SET.md); the per-language ladders' in [EVAL-SET-LANGS](docs/EVAL-SET-LANGS.md)), and the five-corpus points are rendered on [BENCH](docs/BENCH.md); comparators (jscpd, similarity-*) are named with the exact version measured.
## Install, run, update
**Installer.** Each [release](https://github.com/skymanbp/CodeEraser/releases) ships <!--ce:count:installers#word-->five<!--/ce--> GUI installers (NSIS `setup.exe` / AppImage / dmg) bundling the GUI, `ce` and the `ce-core` judgment core. The Windows installer puts the install dir on PATH and runs `ce setup`; AppImage and dmg users run `ce setup` once themselves; it finds Claude Code, wires the plugin below and says whether that `ce`'s directory is on PATH. The <!--ce:count:binaries#word-->fifteen<!--/ce--> artifacts (`ce` and `ce-core` per target plus the installers) and `SHA256SUMS` are unsigned by decision; verify with `sha256sum -c --ignore-missing SHA256SUMS`.
**Homebrew / winget.** The release generates a Homebrew formula and the three winget manifests under `packaging/` from the same pinned manifest it verified, never edited by hand, and publishes them when the repository's tap and winget tokens are configured: `brew install skymanbp/codeeraser/codeeraser` (macOS and Linux, `ce` + `ce-core` from the pinned assets) and `winget install skymanbp.CodeEraser` (the Windows installer, once winget-pkgs merges).
**Claude Code plugin.** `ce setup` registers the marketplace at this repository's `release` branch (fast-forwarded to every published tag, so an install follows releases, not `main`) and installs the plugin; by hand: `/plugin marketplace add skymanbp/CodeEraser@release`, then `/plugin install codeeraser@codeeraser`. The starter resolves `ce` and `ce-core` by pin: a matching local or PATH copy, then a pinned download, then an unverified PATH binary that says so.
**CLI only, or from source.** Download `ce-<ver>-<platform>` and `ce-core-<ver>-<platform>` (x86_64-windows / x86_64-linux / aarch64-macos / x86_64-macos / aarch64-linux), rename them `ce` / `ce-core` and put them side by side on PATH; or `cargo install codeeraser` and place a `ce-core` beside it; or build both with the pinned Rust toolchain (`rust-toolchain.toml`) and GHC <!--ce:tool:ghc#v-->9.14.1<!--/ce--> + cabal: `cd core && cabal build all && export CE_CORE_BIN=$(cabal list-bin ce-core)`, then `cd .. && cargo install --path cli`. Core resolution is one chain everywhere: `CE_CORE_BIN` → a `ce-core` sibling → PATH; `--core <path>` wins.
**Updating.** Release builds are two-phase: draft assets hashed, the pins committed to `plugin/bin/manifest.env`, and only then the tag verifies the same bytes ([RELEASE](docs/RELEASE.md)), which is what `ce update` and the tag job's installer check both read. `ce update` reads the latest tag and that tag's committed `manifest.env`; the verdict is the exit code, and `--yes` acts only where nothing else keeps a ledger of the binary. A copy the plugin bound is re-pinned by `/plugin update codeeraser`; a cargo install by `cargo install codeeraser`; the GUI app itself by the installer `--installer` saves. The plugin's SessionStart line announces a newer release once a day (`CE_UPDATE_CHECK=0` turns that off); the GUI has an update screen; `/codeeraser:update` runs the check from Claude Code.
### Commands
| Command | What it reports / judges |
|---|---|
| `ce scan` / `ce dedup` | size / complexity / readability metrics graded against the file's own lines; T1/T2 clone blocks, `--check` against the budget, a digest-keyed warm cache; both `--format sarif` |
| `ce clone` / `ce docdup` | T3 near-miss clones; documentation duplication |
| `ce graph` / `ce deadcode` | reference sites and the mention universe; liveness verdicts + the symbol advisory |
| `ce churn` / `ce join` / `ce trend` | git-window churn; the three-signal join; score trajectory (progress on stderr) |
| `ce similar` | the same-role advisor: the units most like one unit (`--at file:line`, `--unit`) or a text, in the core's order with its role bit; `--widen` adds the in-repo PPMI associative view. Advisory, never a verdict |
| `ce query` / `ce rules` | one question in CE Datalog over the index's facts, its derivations under `--why`; the rules file's assertions judged as a gate (exit 1 on a violation, 2 on a program error), the file named by `--file` or `[rules] file`, else `ce.rules` at the root |
| `ce flow` | dead code inside functions (unreachable statements, dead stores, unused locals and parameters), judged by the core over the control-flow graph; `--kind` narrows the listing, `--check` exits 1 only at `[flow] tier = "deny"` with a judged finding, 2 when the core cannot judge |
| `ce merge` | clone-merge suggestions: every clone group anti-unified by the core, its parameters, the member kept, the lines saved, feasible or why not; `--group n` prints one group. Advisory, never a verdict |
| `ce arch` | architecture analysis: the directory layers, the arcs to cut out of their cycles (exact or greedy), the file clusters and the misplaced files, per-directory fan-in / fan-out / instability; `--impact <path>` traces the files a change reaches. Advisory, never a verdict |
| `ce structure` | <!--ce:count:structure_axes#word-->eight<!--/ce--> axes; `--split-candidates` prices the best seam of every file past the soft line |
| `ce check` / `ce baseline` | the ADR-006 ratchet and score floor, <!--ce:count:fail_conditions#word-->six<!--/ce--> fail conditions each named on the console; `baseline` persists only at the root and under a named act |
| `ce erase` | the deterministic two-phase eraser; dry-run default, `--apply` behind clean-worktree preconditions, `--log` reads the applied-erase trail |
| `ce update` | latest release vs this build, exit 0 / 1 / 2; `--yes` replaces `ce` + `ce-core` after both pins verify, `--installer` saves the verified GUI installer |
| `ce doctor` / `ce setup` / `ce eject` / `ce mcp` | machine state; wire this machine's Claude Code to the plugin (exit 0 wired / 5 kept / 10 no Claude Code / 11–12 failed / 13 elevated account is not the logged-in user; `--unwire` removes only what it added); per-project uninstall; the read-only MCP server |
Console output, `--help` and the hooks' own refusal sentences are English by default and Chinese under `--lang zh`, `CE_LANG=zh` or the project's `[ui] lang = "zh"` in `ce.toml`, in that order of precedence, and `--help` reads only the first two; JSON schemas and the FAIL/pass vocabulary are never translated. `[[rules.class]]` in `ce.toml` gives one glob set its own size and complexity lines and ratchet tolerance (`0` = may not grow), and the same line is read by the score, the `ce scan` ladder and the PreToolUse budget ([ce.toml reference](docs/reference/ce-toml.md)).
## Three faces, one product
Every capability is claimed once in this table, the sets are derived from the code (clap's enum, the Tauri roster, the MCP catalog, `hooks.json`, `plugin/commands`, `plugin/skills`), and a CI gate (`face_parity`) refuses a face nobody wrote down or a claim nobody shipped. Deliberate omissions are rows, not silence.
<!-- parity:begin -->
| capability | CLI | GUI (screen · commands) | plugin (hooks · MCP · commands · skills) |
|---|---|---|---|
| size / complexity / readability metrics | `ce scan` | `reports`, `scan_report` | MCP `scan` |
| T1/T2 clone blocks | `ce dedup` | `reports`, `dedup_report` | MCP `check_duplication` |
| T3 near-miss clones | `ce clone` | `reports`, `clone_report` | MCP `clone` |
| documentation duplication | `ce docdup` | `reports`, `docdup_report` | MCP `docdup` |
| reference sites and the mention universe | `ce graph` | `reports`, `sites_report` | MCP `graph_sites` |
| liveness verdicts + symbol advisory | `ce deadcode` | `graph`, `graphscreen_report`, `reports`, `deadcode_report` | MCP `deadcode` |
| git-window churn | `ce churn` | `candidates`, `churn_report` | MCP `churn` |
| three-signal join | `ce join` | `candidates`, `join_report` | MCP `join` |
| tree-scale structure (split pricing) | `ce structure` | `structure`, `structure_report` | MCP `structure` |
| score trajectory | `ce trend` | `trend`, `trend_report` | MCP `trend` |
| score, ratchet and floor | `ce check` | `score`, `check_report` | MCP `check` |
| same-role advisor (similar units, associative view) | `ce similar` | `similar`, `similar_report` | MCP `similar_units` |
| code query and architecture rules | `ce query`, `ce rules` | `query`, `query_report`, `rules_report` | MCP `query`, MCP `rules` |
| intra-function dead code (unreachable, dead stores, unused locals and parameters) | `ce flow`, `ce flow --check` | `reports`, `flow_report` | MCP `flow` |
| clone merge suggestions (anti-unification) | `ce merge` | `reports`, `merge_report` | MCP `merge_suggestions` |
| architecture analysis (layers, cuts, clusters, impact) | `ce arch` | `reports`, `arch_report` | MCP `architecture` |
| baseline writes | `ce baseline` | — CLI only: a machine surface never writes a baseline | — |
| erase plan | `ce erase` | `erase`, `erase_preview` | MCP `erase`, skill `erase` |
| erase apply | `ce erase --apply` | `erase`, `erase_apply` | — no MCP face: applying is a human act |
| erase audit log | `ce erase --log` | `erase`, `erase_log_report` | MCP `erase_log` |
| machine state | `ce doctor` | `doctor`, `doctor_report` | MCP `doctor` |
| update check | `ce update` | `update`, `update_check` | MCP `update_check`, `/codeeraser:update`, hook `SessionStart` |
| update apply | `ce update --yes` | `update`, `update_apply` | — the plugin's copy is re-pinned by `/plugin update codeeraser` |
| write-time guard | `ce probe --hook` | — hooks are the plugin's face | hook `PreToolUse` |
| asked-write settlement | `ce settle --hook` | — hooks are the plugin's face | hook `PostToolUse` |
| stop audit / git hooks | `ce audit --hook`, `ce precommit`, `ce commitmsg` | — hooks are the plugin's face; precommit and commitmsg are git's | hook `Stop` |
| session health line | `ce health --hook` | — hooks are the plugin's face | hook `SessionStart` |
| project daemon | `ce daemon`, `ce ping` | — started lazily by every face | — |
| read-only report server | `ce mcp` | — the plugin registers it | `.mcp.json` |
| uninstall | `ce eject` | — CLI only | — |
| Claude Code wiring | `ce setup`, `ce setup --unwire` | — CLI only: the Windows installer calls it, AppImage / dmg users run it once | — |
| bench dashboard | — compiled-in series; README and site carry the same block | `bench`, `bench_doc` | — |
| root anchoring | — every command and hook anchors through `root` | `default_root`, `resolve_root` | — |
<!-- parity:end -->
## Tech stack, design and philosophy

<sub>Zoom and pan this diagram at [codeeraser.dev/#architecture](https://codeeraser.dev/#architecture).</sub>
- **Rust <!--ce:tool:rust#v-->1.94.1<!--/ce-->** (edition <!--ce:tool:edition#name-->2024<!--/ce-->). The `codeeraser` crate carries tree-sitter <!--ce:tool:tree_sitter#vminor-->0.27<!--/ce--> with <!--ce:count:grammars#word-->twelve<!--/ce--> wired grammars, rusqlite <!--ce:tool:rusqlite#vminor-->0.40<!--/ce--> (bundled SQLite, WAL, index schema <!--ce:ver:schema.index#digits-->17<!--/ce--> / GRAPH_REV <!--ce:ver:graph_rev#digits-->23<!--/ce--> / MENTION_REV <!--ce:ver:mention_rev#digits-->4<!--/ce-->), the `ignore` walker, `interprocess` named pipes / Unix sockets, clap, serde, sha2 for the updater's pins.
- **Haskell (GHC <!--ce:tool:ghc#v-->9.14.1<!--/ce-->, GHC2021, `-Wall -Werror`)**: `ce-core`, every judgment family, a frozen dependency graph.
- **Tauri <!--ce:tool:tauri#digits-->2<!--/ce-->** GUI over the same crate, vanilla JavaScript in the webview, no build step; **NSIS / AppImage / dmg** bundles with `ce` and `ce-core` as sidecars.
- **One wire.** ce ↔ core is NDJSON over stdio with SemVer negotiation (proto <!--ce:ver:proto#v-->7.10.0<!--/ce-->, <!--ce:count:families#word-->sixteen<!--/ce--> families); the per-project daemon speaks its own protocol (<!--ce:ver:daemon#v-->2.2.0<!--/ce-->) over `interprocess`; a protocol-major skew is a named refusal, never a guess.
- **Design rules.** ADR-001 Rust frontend · ADR-002 Haskell judges, never parses · ADR-003 lazy daemon, 30-minute idle exit, fail-open hooks · ADR-004 cheap PreToolUse, deep Stop, CI as backstop · ADR-005 two clone layers · ADR-006 shrink-only ratchet · ADR-007 pinned distribution · ADR-008 policy as Haskell data · ADR-009 documentation facts derived, never hand-typed. The plan is the contract: [DEVELOPMENT_PLAN](docs/DEVELOPMENT_PLAN.md).
- **Philosophy.** Measure in Rust, decide in Haskell, render everywhere else. Codes cross the wire; each face owns its sentences. Nothing on any surface asks a model anything. Hooks fail open and say so. A guard class reaches `deny` only with its own false-positive record in [CHANGELOG](CHANGELOG.md). Documentation is generated or gated: the CLI and config references, the <!--ce:count:booklets#word-->nineteen<!--/ce-->-booklet [methodology](docs/reference/methodology.md) with machine-checked citations, the numbers this page derives from the code, the two diagrams above, the bench block, the demo, the site's terminal block and its GUI screenshots, the parity table, the NOTICE. This repository is its own first user, and every push runs the <!--ce:count:gates#word-->seven<!--/ce--> product gates on this tree.
## Known limits
**1.8.0 scope.** The v2.29 plan re-opened, item by item, the deferred work the 2026-08-31 close-out had ruled not-done (plan v2.22, 45 items across three bundles), keeping only what earns its cost; the v2.30 plan then widened the judged set to C, C++, Java, Lua, R and HTML, each admitted with its own frozen precision exam and false-positive ledger ([language-expansion](docs/reference/language-expansion.md)). The limits below are the positions that survived.
**Limits.** PreToolUse shapes behaviour; it is not a security wall (shell writes bypass it; the Stop audit and CI are the backstops). Hooks fail open on internal errors and record the degradation. Binaries are unsigned.
- **Languages.** AST-based judgment uses the <!--ce:count:grammars#word-->twelve<!--/ce--> grammars above; Markdown is judged by the documentation and graph rules without a tree-sitter grammar, and HTML by the same rules over its grammar (an element's `id` is a section, its text is document text; no functions, no clone fingerprints), and plain text (`.txt`) by the documentation rule alone; JSDoc and Rust `///` are comments, not docstrings; T4 clones are not promised. `churn`, `join` and `trend` are minute-scale.
- **Advice, never a verdict.** Symbol-level liveness is an advisory and never a verdict; `ce deadcode` says so on its own last line. The same-role advisor is an advisor and never a verdict: `ce similar`, MCP `similar_units` and the GUI similar screen rank and rebut nothing. `ce similar` always exits 0, `ce check` never reads the family, and the Stop audit's advisory row only reaches the observe ledger. A guard class stays `observe` until it carries its own false-positive record.
- **Lines this product does not draw.** The complexity axis ships with no hard line at all. `cognitive_fail` defaults to 0, so a tangled function warns and nothing more until a repository declares one, and the write-time hook never judges complexity. `ce structure` has no score floor, so that family reports and gates nothing.
- **Distribution and setup.** A release builds <!--ce:count:platforms#word-->five<!--/ce--> targets since v1.7.0 (`x86_64-windows`, `x86_64-linux`, `aarch64-macos`, `x86_64-macos`, `aarch64-linux`); on the two newer ones a plugin starter reading a manifest older than that finds an empty pin and falls back to a PATH `ce` or a source install. `ce setup` wires nothing when the account running it is not the logged-in user (exit 13); run it unelevated from your own account. Judging this repository needs the `cli/tests` submodule seated (it is a reader of the tree, never a measured part).
- **Tombstone residue.** It counts single-word names, so a common word erased as an identifier can bind a sentence genuinely about that word; the repository's own `[tombstone] terms` list is the way out. It measures CJK names only at word boundaries: a wide Chinese name is seen where it stands alone as a heading, a list lead or an identifier, and is bound in prose by substring.
- **Score comparability.** Scores are not comparable across a `[[rules.class]]` switch, across the v0.7.3 → v1.0.0 density-charge rescoring, across the v1.2.0 → v1.3.0 test-submodule move, across the v1.3.x → v1.4.0 recursion increment, across the v1.6.0 → v1.7.0 re-denomination of the clone and docdup axes (files touched over each axis's own universe, with docdup pairs reaching `ce check` for the first time), or across the v1.7.4 → v1.8.0 widening (C, C++, Java, Lua, R, HTML and plain text enter the axes' universes, and verified near-miss pairs enter the clone axis). `ce structure`'s own 0–1000 is likewise not comparable across v1.6.0 → v1.7.0, where the modularity axis is new and every axis enters the equal-weight fold, nor across v1.7.4 → v1.8.0, where the new languages' edges enter its graph. Scores are also not comparable across a named re-establish that moves the baseline's `softLine`: the size axis charges against that repo-relative line rather than a constant, and this repository's has moved with every named re-establish since 304 at v0.7.3 (372 at v1.4.1, three points apart when both lines are applied to the v1.4.1 tree).
## Documentation
- [CLI reference](docs/reference/cli.md) · [ce.toml reference](docs/reference/ce-toml.md) (generated from the binary and the config schema; drift reddens CI) · [Methodology](docs/reference/methodology.md) (<!--ce:count:booklets#word-->nineteen<!--/ce--> booklets, cited to implementation lines) · [structure axes](docs/reference/structure-axes.md) · [size advisory](docs/reference/size-advisory.md) · [erase contract](docs/reference/erase.md) · [GUI reference](docs/reference/gui.md) · [plugin](plugin/README.md) · [demo](demo/README.md)
- [DEVELOPMENT_PLAN](docs/DEVELOPMENT_PLAN.md) · [EVAL-SET](docs/EVAL-SET.md) · [EVAL-SET-SIMILAR](docs/EVAL-SET-SIMILAR.md) · [EVAL-SET-LANGS](docs/EVAL-SET-LANGS.md) · [EVAL-SET-FLOW](docs/EVAL-SET-FLOW.md) · [language-expansion](docs/reference/language-expansion.md) · [FIELD-TEST](docs/FIELD-TEST.md) · [BENCH](docs/BENCH.md) · [PERF-BUDGET](docs/PERF-BUDGET.md) · [FPR-REPLAY](docs/FPR-REPLAY.md) · [FPR-TOMBSTONE](docs/FPR-TOMBSTONE.md) · [FPR-L2](docs/FPR-L2.md) · [T1-INTERCEPT](docs/T1-INTERCEPT.md) · [contracts/VERSIONING.md](contracts/VERSIONING.md) · [docs/RELEASE.md](docs/RELEASE.md) (wire SemVer and the two-phase release runbook)
- Website: [codeeraser.dev](https://codeeraser.dev) · [how it works](https://codeeraser.dev/how/) · [stack](https://codeeraser.dev/stack/) · [bench](https://codeeraser.dev/bench/) <!-- ce:allow(docdup) -- the documentation links are one set, listed in both languages -->
## License
Apache-2.0; see [LICENSE](LICENSE). Third-party inventory in [NOTICE](NOTICE) (regenerated and gated in CI). The test suite lives in [skymanbp/CodeEraser-tests](https://github.com/skymanbp/CodeEraser-tests); clone with `--recurse-submodules`. "CodeEraser"™ is a trademark of skymanbp; per Apache-2.0 §6 the license covers the code, not the name.
连接信息
猜你喜欢
暂无推荐服务器