Skip to content

Releases: skymanbp/CodeEraser

CodeEraser 1.7.1

Choose a tag to compare

@github-actions github-actions released this 08 Sep 04:39

CodeEraser 1.7.1

A single distribution-chain fix. On Windows the starter read its own SHA256
wrong, refused a binary that was byte-identical to the pin, and took the whole
plugin face down without printing anything. ce check, ce structure and
ce docdup scores are comparable with 1.7.0
: no judgment semantics, axis,
threshold or dimension moved, the index schema stays at 16, the wire stays at
7.1.0, the baseline stays at ce.baseline/2, and no default guard tier moved.

The starter refused its own verified copy

sha_of hashed by filename: sha256sum "$1" | cut -d' ' -f1. GNU coreutils
escapes a filename containing a backslash or a newline and marks that line by
prefixing it with a literal \, so the function returned \<64 hex> — one
character longer than any pin, and equal to none of them.

That is not a hypothetical on Windows. CLAUDE_PLUGIN_DATA is a native path,
so every candidate built from it carries backslashes, and the
ce-1.7.0-x86_64-windows.exe sitting in the data directory — byte-identical to
its pin — was rejected as tampered:

codeeraser: REFUSING on-disk ce — SHA256 mismatch, not running …\ce-1.7.0-x86_64-windows.exe

ce.sh then fail-opened at exit 0, per R3. So SessionStart, PreToolUse and Stop
and the MCP report face all went dead at once, and none of them said why: the
script that would have complained is the one that could not start. The symptom
users saw was an absence — no health line, no .ce/, no guard, and a report
server that would not connect.

The fix hashes through stdin (sha256sum < "$1", and shasum -a 256 the same
way). With no filename in the output there is nothing to escape: both tools
print <hash> -, and a backslash path reads exactly like a POSIX one. The
seventeen bootstrap states pass on Windows, including the tampered-artifact and
stale-core legs that must still refuse.

A second Windows leg, named but not fixed here

The three hooks and .mcp.json start with a bare sh, which Claude Code — a
native Windows process — resolves on the Windows PATH. Git for Windows puts
only Git\cmd there, which holds git.exe and no sh.exe; sh.exe lives in
Git\bin and Git\usr\bin. With no sh on PATH the hooks and the MCP server
never launch, and that failure is silent for the same structural reason as the
one above.

This release records it as a prerequisite in the plugin README rather than
changing the launcher contract. Appending Git\bin to your user PATH is
enough; it holds only bash.exe, git.exe and sh.exe, so it cannot shadow
the Windows find or sort (those are in Git\usr\bin), and appending rather
than prepending leaves bash resolving to the WSL one in system32. Hook
configuration is read at session start, so restart Claude Code afterwards.

Unsigned, as before

Binaries are unsigned; the SHA256SUMS asset and the plugin manifest's pins
are the trust anchor (ADR-007). ce update on a 1.7.0 install reads this tag
and the pins committed on it. A 1.7.0 plugin install picks the new pins up
through /plugin update codeeraser.

CodeEraser 1.7.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 22:14

CodeEraser 1.7.0

The same-role advisor lands on all three faces, sparse retrieval moves into the
index, and thirty-five revival items from the 2026-09-05 frontier ship with it.
ce check scores and ce structure scores are not comparable with 1.6.0
(the reasons are below); ce docdup rows are comparable with 1.6.0. The index
schema moves 15 → 16 (one full rebuild on first run), the wire moves
6.6.0 → 7.1.0 — one major break at 7.0.0 (fourclass/1 pair rows carry
dupSpans) between two additive minors (6.7.0 similar/1; 7.1.0 dirEdges,
declRem / declAdd) — the baseline file moves to ce.baseline/2 (a /1
file is refused by name; CE_ACCEPT_BASELINE=1 ce baseline . re-establishes it
once), the daemon protocol stays at 2.1.0, and the observe feed grows
additively to ce.observe/0.10.0. A release now builds for five targets,
sixteen assets
.

Why the scores moved

  • ce check: axes 2 (clone) and 3 (docdup) now score the distinct files a
    judged pair touches, over each axis's own universe (code files for clones,
    document files for docdup), and ce check sends the docdup pairs for the
    first time — that axis had been zero in the product. This repository
    949 → 943 (CI floor 946 → 939); the tests submodule 985 → 983 (983 → 979).
    No default guard tier moved.
  • ce structure: an eighth axis, modularity — a directory's normalised Newman
    contribution against a floor, judged as one integer inequality (modFloor
    1 ‰, modMassFloor 4 edges; a directory below the mass floor or without a
    complement is not judged). This repository 820 → 831; the same tree under the
    old seven-axis table reads 819, so 819 → 831 is the axis itself. Structure
    stays report-only.
  • Baseline membership is a container-chain anchor (fnv1a(outer unit keys) +
    the position among same-key units in that chain) instead of the nth same-key
    unit in the file: deleting an earlier sibling no longer moves a survivor's
    identity, and impl A { fn add } / impl B { fn add } are two members. The
    ledger legs hold the old → new map (45 pairs here, 37 in the tests submodule).

Same-role advisor, zero models

  • Every unit gets a term bag from six channels (name, shape, callee, doc,
    structure, literal) with Porter stemming; ranking is integer BM25 (k1 = 6/5,
    b = 3/4) over an inverted index in .ce/index.db; the same-role bit is a
    conjunction judged in the core — name, callee and shape channels must agree
    (CE.Similar, wire similar/1: a query bag plus nine integers per candidate,
    the core answers the order as exact rationals, the role bits and the counts;
    more than 65536 rows degrades by name). --widen adds in-repo PPMI
    association, opt-in; the co-occurrence pairs are derived at query time from
    the index rather than stored (a pair table measured 688k rows, 10.7 → 58 MB
    and a 5 → 35–50 s cold index, and was refused).
  • Three faces: ce similar --at <file:line> | --unit <key> | --text <words> [--widen] [--format json], the MCP tool similar_units, and the GUI's
    eleventh screen — one document, ce.similar-report/0.1.0. The Stop audit
    writes a similar object into the feed only when a unit added in the session
    has a same-role top-1 twin; it never blocks. ce similar always exits 0,
    ce check never reads this family.
  • Precision was measured before anything shipped, on two arbitrated oracles:
    generation 1 (118 queries) top-1 same-role 67/118, role = 1 subset 39/59;
    the held-out generation 2 (115 queries, no overlap) 46/115, 30/56 = 53.6 %,
    hit@5 60.0 %. 84 scoring configurations and 16 role predicates were re-ranked
    on the frozen pools; none cleared the pre-registered line, so the spec's
    form ships unchanged (SIMILAR_REV 1). Floors: 60 % / 40 %, never lowered.
  • Cost on this repository: ce similar 0.71–0.76 s, --widen 1.78–1.88 s;
    cold ce dedup 5.1–5.3 → 8.0–8.7 s for the bag tables (warm unchanged),
    index 10.7 → 18.0 MB.

Judgment correctness (revival batch A)

  • Stacking: fourclass/1 pair rows send dupSpans=[[hash,start,end]] and the
    core counts only novel lines that fall inside a new duplicate unit's span
    (the 7.0.0 major).
  • Erase: the class-2 death fact is a verdict code 0..4; codes 2 and 4 are
    refused with reason 6 public_surface, so RG10 finally reaches the twin
    path, and whole-file targets are chosen by licence.
  • Daemon: the core chain backs off exponentially (1 s · 2^(n−1), cap 60 s)
    instead of closing after three failures; the first report after recovery
    carries recovered: n.
  • A tests-submodule leg holds the PreToolUse hard budget and ce scan to the
    same verdict on the same bytes.

Declaration-level moves (7.1.0, additive)

fourclass/2 requests carry declRem / declAdd[keyHash, kind, start, end] for every (key, kind) that appears exactly once on each side — and the
core answers unitEdges (same name and kind, one destination, shared content
inside both spans; more than 65536 pairs drops the table by name). Report
information only: no score, line class or guard verdict changes. Edge coverage
on the frozen instruments: this repository 31/37 → 36/37, requests 1/1,
ripgrep 22/22.

Distribution

  • Five targets — x86_64-windows, x86_64-linux, aarch64-macos,
    x86_64-macos, aarch64-linux — spelled once (update::version::TARGETS),
    fifteen binaries plus SHA256SUMS, each target through one reusable
    workflow. The plugin manifest pins all fifteen; manifests before 1.7.0 carry
    empty pins for the two new targets and the starter falls back to a ce on
    PATH there.
  • ce setup wires Claude Code (marketplace skymanbp/CodeEraser@release,
    plugin install, a marker so --unwire only undoes what it did); exit codes
    0 wired / 5 kept / 10 no Claude Code / 11 add failed / 12 install failed /
    13 elevated account differs from the logged-on user (touches nothing). The
    Windows installer's hooks call it. The marketplace entry follows the
    release branch, fast-forwarded by the publish job.
  • The Homebrew formula and the winget manifests are projections of the pin
    manifest. Three optional tag legs publish to crates.io, the tap
    skymanbp/homebrew-codeeraser and a pull request to microsoft/winget-pkgs
    when the repository secrets are set, and skip by name otherwise.
    brew install skymanbp/codeeraser/codeeraser; winget install skymanbp.CodeEraser once the pull request is merged. The npm pointer package
    lives in-tree (npm/).
  • Trust chain: the draft is created on its build commit and the publish job
    compares twelve source paths between that commit and the tag commit; release
    notes must be written before the tag (the placeholder is refused); the pins
    are read from the draft's own SHA256SUMS by a program, not copied.

Smaller changes

  • ce erase --log, the MCP tool erase_log and the GUI erase screen read the
    erase trail as ce.erase-trail-report/0.1.0; erase suggestions name the
    family command (ce.erase-plan/0.3.0, additive families).
  • [ui] lang in ce.toml (--lang > CE_LANG > file); console faces only,
    not part of knobs_digest.
  • GUI tab bar: the breakpoint is measured (1150 px English, 844 px Chinese);
    eleven tabs fit the 860 px minimum window.
  • Evidence ledgers: the zone-tier false-positive rate is measured on history
    (this repository 28/5196 = 0.54 %, requests tail 11/448 = 2.46 %) and
    [guard] zone_tiers stays false; the changeset-level ledger for moves and
    stacking holds 821 events with zero intercepts and one missed copy positive,
    recorded as it is. min_distinct calibration is frozen; the guard FPR replay
    is a permanent --ignored leg.
  • Dependencies: getrandom 0.4, sha2 0.11, rusqlite 0.40.2, toml 1.1.5,
    tree-sitter 0.27.0. TOKENIZER_REV 2 → 3 with a second invalidation layer:
    a parser-revision mismatch clears the thirteen parse-derived tables and keeps
    trend. Shown invariant on one tree with both binaries — sixteen report
    families byte for byte, twelve index tables row for row.
  • Documentation: methodology booklet 15 (the advisor); generated Chinese
    surfaces render the frozen evaluation points from templates over the
    ledger's numbers; hand-written facts (the 750-line hard line, sixteen assets,
    130 golden requests) are chips with executors.
  • Bench: the one-date rule is retired (ruling 2026-09-06). Every row names the
    day it was measured; rows of one date are a series, rows of different dates
    only a bound. v1.6.0 and v1.7.0 were measured after this tag, one tag at a
    time on 2026-09-06, and the 2026-09-03 rows stand.

Latency

Same-day rows (2026-09-06, this machine, p50 — docs/BENCH.md), 1.6.0 → 1.7.0:
ce check warm 1352 → 2387 ms (the docdup pairs now travel with ce check;
that axis was zero in the product before); cold ce dedup 5327 → 10293 ms (the
bag tables of the retrieval index are built on the cold path), warm ce dedup
430 → 495 ms; ce docdup warm 1196 → 1408 ms; ce deadcode warm 1066 → 1166 ms;
ce scan 616 → 683 ms; the PreToolUse hook probe 47 → 48 ms. The 1.5.1 rows
were measured on 2026-09-03 and read against these only as a bound.

Unsigned, as before

Binaries are unsigned; the SHA256SUMS asset and the plugin manifest's pins
are the trust anchor (ADR-007). ce update on a 1.6.0 install reads this tag
and the pins committed on it.

CodeEraser 1.6.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 04:41

CodeEraser 1.6.0

The tombstone-residue class is now judged, not only measured, and Rust doc
comments are one paragraph to the documentation-duplication judge. Check
scores are comparable with 1.5.x
— no judged axis moved, no default guard tier
changed, the index schema stays at 15; the wire moves 6.5.0 → 6.6.0
additively
(an eleventh judgment family, tombstone/1), the daemon protocol
2.0.0 → 2.1.0 additively, and the observe feed takes a named break to
ce.observe/0.9.0
. ce docdup rows are not comparable with 1.5.x: every
Rust /// / //! block changes geometry (see below).

Tombstone residue, judged as a conjunction

A change that erases a name and then narrates it — a struck-through or
(removed) label, or a sentence carrying a backward-looking mark together with
the erased name — is measured in Rust as the changeset's surfaces and judged in
the core sentence by sentence against a floor and the class's [tombstone] budget. Three legs speak — PreToolUse, the Stop audit, ce precommit /
ce commitmsg — only at the class's [tombstone] tier (default observe)
and only when the core answers over; no core, an older core, or an
incomplete measurement is recorded by name and never enforced.

  • [tombstone] table in ce.toml: tier, budget (absent = the condition is
    not evaluated), ledger (globs of changelog-role documents), terms (words
    of the repository's own vocabulary that are never names). All four enter
    knobs_digest, so a repository that declares them re-establishes its
    baseline once (CE_ACCEPT_BASELINE=1 ce baseline).
  • Changelog-role documents are exempt four ways: by path, by ledger shape, by a
    segment carrying three version witnesses, or by a [tombstone] ledger
    declaration; each exemption is written to the feed by kind.
  • ce commitmsg <file> — a git commit-msg hook face: the staged changes
    and the commit message (as one Markdown prose surface, COMMIT_EDITMSG:line)
    under the same judgment as ce precommit; exit 1 on a deny, 2 when the file
    cannot be read. The parity table names it beside ce precommit.
  • The false-positive rate was replayed on git history before anything moved:
    nine rounds on the requests tail (0 / 400 commits) and this repository's
    whole history (strict reading 1 / 537 events = 0.19 %, inside the 1 % gate;
    the conservative reading that counts every true positive as a cost is
    6 / 537 = 1.12 %, over the gate by one commit — the two extra hits are true
    positives this batch wrote itself, recorded as they are, the line not moved),
    with each round's fix made in the definition, never in a threshold —
    docs/FPR-TOMBSTONE.md, methodology booklet 14.

A Rust /// block is one paragraph to docdup

tree-sitter-rust's doc-comment node ends at column 0 of the next row, so the
merge rule never saw consecutive /// lines as adjacent and every line was its
own segment ending a row late. Adjacency and end_line now read the node's
last content row (DOCDUP_REV 4 → 5, the cache is wiped once). Measured on the
same trees with both binaries: ripgrep 251 → 568 segments and 7 → 50 pairs,
this repository 845 → 1462 segments; cobra, zod and requests unchanged. The five
frozen docdup instruments were regenerated by the EVAL-SET.md revival
protocol; the oracle for ripgrep grew from 8 to 91 pairs, all of them
comment_block.

Smaller changes

  • The codex review of the tombstone class (20 findings, 19 in code): unread
    and bounded pairs are counted and stand the leg down, the session union
    forgets a name written back, the prose sentence is cut on the whole paragraph
    and claimed by the added lines, version strings are exactly three parts,
    terms match whole normalized words, consume refuses a reply whose counts
    disagree with its sites.
  • The Stop leg's cost with judgment in the core, measured A/B on one tree in
    two worktrees (docs/PERF-BUDGET.md): parity on a clean tree; +0.46 s on a
    16-file dirty tree for the measurement itself; the tombstone/1 round trip
    is within noise (≤ 0.12 s).
  • CHANGELOG.md reached its own 750-line hard line again; v1.3.1 and v1.3.2
    moved byte for byte into docs/CHANGELOG-ARCHIVE.md.
  • Bench: v1.6.0 changes cli/src and core/app, so it earns its own seven
    rows — measured after this tag in a quiet window and committed separately;
    until then the bench pages say so.

Unsigned, as before

Binaries are unsigned; the SHA256SUMS asset and the plugin manifest's pins are
the trust anchor (ADR-007). ce update on a 1.5.x install reads this tag and
the pins committed on it.

CodeEraser 1.5.1

Choose a tag to compare

@github-actions github-actions released this 02 Sep 00:03

CodeEraser 1.5.1

A maintenance release: the close-out sweep after 1.5.0 (a 43-agent "could have,
didn't" pass and a 13-agent i18n audit, every confirmed item landed) plus the
website tidied end to end. Scores are fully comparable with 1.5.0: no judged
axis moved, no default guard tier changed, the wire stays at 6.5.0 and the index
schema at 15; every change to the judged tree is either a comment, a rendering
of an existing fact in the reader's language, or an additive report field.

Dead-code rows say why in the reader's language

ce deadcode used to mint its reason as an English sentence in the measurement
layer and print it into Chinese consoles and the GUI in both languages. The
measurement side now emits a code (0 = no kept in-edge, 1 = referenced only
by dead code); ce.deadcode-report 0.4.0 and ce.graph-canvas 0.4.0
carry whyCode beside the English why, additively, and the console and the
GUI render it from their own language tables. Older reports fall back to the
English sentence they already carry, never to undefined.

Smaller fixes from the same sweep

  • The GUI health screen's OK / FAILED verdict was hard-coded English; it now
    reads the handshakeOk / handshakeFailed keys, the same words the CLI uses.
  • ce erase's advice rows for a language with unresolved reference sites now
    say how many sites remain; ce.erase-plan 0.2.0 carries sites additively
    and the wire's reason bits are untouched.
  • Six source comments and one methodology booklet still said "until a score
    floor lands"; the close-out ruled that floor out, and the comments now say so
    instead of promising it.
  • The nine frozen evaluation points in bench.json cite docs/X.md:A-B in
    prose that no gate ever parsed; a new gate (bench_frozen_sources) resolves
    every citation and requires each number in the value to appear on the cited
    lines. Six more docs-constant chips are bound to the source constants they
    name (allowlist 22 → 16).

The website

All eight pages of https://codeeraser.dev were reworked from screenshots rather
than from the stylesheet: one shared stylesheet (the per-page <style> blocks
were byte-for-byte copies the scan-only gate cannot see), unified tables,
footers and metadata, pictures that reserve their space before loading, a jump
bar and stable anchors on the methodology page, two new Chinese diagrams
(methodology.zh.svg, stack.zh.svg), the archify diagrams stamped dark with
translated descriptions — and a handful of real errors found by looking: a
diagram that said "five named conditions" where there are six, a chip backing
the wrong fact, an overflowing text line, straight quotes on the Chinese page.

Benchmarks

1.5.1 changes cli/src, so it earns a row; per the series' own rule the whole
latency table is replayed in one sitting after the tag, and the rows for 1.5.0
and 1.5.1 land together with that replay. Until then every version-bearing
surface says exactly that.

Full details in the CHANGELOG. Channels: GitHub Release (ten pinned assets),
crates.io, npm, https://codeeraser.dev.

CodeEraser 1.5.0

Choose a tag to compare

@github-actions github-actions released this 01 Sep 20:48

CodeEraser 1.5.0

The complexity axis gets the one thing it never had: an absolute line — opt-in,
off by default, and deliberately so. Scores are fully comparable with 1.4.x by
construction
: the new knob never reaches the score, no judged axis moved, no
default guard tier changed, the wire stays at 6.5.0 and the index schema at 15.

[thresholds] cognitive_fail

Until now no complexity refusal existed anywhere in the product. The scan grade
table's fail column is literally 0 for every complexity metric, the PreToolUse
hook judges no complexity at all (write-time checks are AST-free by design), and
the ADR-006 ratchet only stops growth — a brand-new function bootstraps at
whatever value it is born with, by documented design. A tangled function warned,
and nothing more, ever.

cognitive_fail completes the fail tier. Declared, it arms ce scan's exit
code (named condition: hard_line, incoherent ladders refused at load) and the
per-class channel — [[rules.class]] knobs.cognitive_fail, so one tree can hold
two different walls and only the classed path answers to the stricter one.

The default is 0 — the published "no hard line" — and that is a stance, not a
caution. The plan's own evidence row records that cognitive complexity has no
support on the correctness axis (r = −0.13, CI crossing zero); a metric this
project declines to over-claim does not get a wall nobody asked for. Arming one
is a declaration, fenced by the knob digest like every other declaration.

What it deliberately is not: a curve. Applying the size axis's graded-zone
pricing to cognitive complexity was measured before being declined — it charges
0‰ on this repository, because 2732 of 3900 measured functions sit at complexity
0 and the opportunity count dilutes everything; the shape of the numerator was
never the problem, and a curve would have cost a score migration for nothing.

Nothing else moves. The grade rows always carried [code, warn, fail] and the
core always judged a nonzero fail line generically, so the Haskell core is
unchanged
— every golden fixture byte-identical — and the knob fingerprint of
every repository that does not declare the key, classed or not, is untouched by
the canonical rules that drop default-valued and undeclared leaves.

The maintenance underneath

  • An observation had been written as an invariant. The soft-line exponent's
    comment (and its methodology booklet) claimed k=2 lands S "within ±6% of the
    historical 300". That was a three-tree calibration note from v0.6; measured
    today this repository's S is 372 — +24% — and the citation gate re-signs
    anchors without reading truth. Both sentences now say what they were.
  • The hard-line chips had no executor. The docs-constants gate matches chip
    display names against source constant names, so sizeHard H, seamHard H
    and file_lines_fail H — labels carrying the role letter the docs use —
    could never resolve and sat on the allowlist: 750 printed on four surfaces,
    checked by nothing. The gate now maps labels to the constants they name,
    checks both halves of a pair chip ([softMin, softMax]), and harvests
    impl Default field values, where every ce.toml threshold default actually
    lives. Eight negative probes verified one by one that moving any bound
    constant reddens the gate. The allowlist shrank from 30 entries to 22, each
    annotated with the property that keeps it unbindable.
  • The moving soft line joins the comparability list. S is repo-relative and
    re-derived at a named re-establish; this repository's walked from 304
    (v0.7.3) to 372 (v1.4.1) — three points apart replayed on the same tree —
    and no document had ever named that as a comparability cause. Both READMEs
    now do, and the Limits paragraph states that the complexity axis ships with
    no hard line, so the absence reads as the stance it is.
  • One audit claim was refuted and is recorded as such: ce join does not
    price the size axis under different rules — it does not price it at all (its
    fact tables ride empty and are ignored; the join consumes verdicts alone).

Benchmarks

1.5.0 changes cli/src, so it earns a row; per the series' own rule the whole
latency table is replayed in one sitting after the tag, and the row lands with
the replay. Until then every version-bearing surface says exactly that.

Full details in the CHANGELOG. Channels: GitHub Release (ten pinned assets),
crates.io, npm, https://codeeraser.dev.

CodeEraser 1.4.1

Choose a tag to compare

@github-actions github-actions released this 01 Sep 16:29

CodeEraser 1.4.1

Five defects, every one of them invisible to the gates that were supposed to see
it. Scores are fully comparable with 1.4.0 — no judged axis moved, no default
guard tier changed, the wire stays at 6.5.0 and the index schema at 15.

The shape they share

Most documentation in this repository is generated, and a byte gate compares each
file with its own generator. That catches a hand edit instantly and catches
nothing else. A generator that states a rule nobody executes, a form applied to
the wrong kind of value, a hand-written sentence with no chip on it — all of these
pass every gate in the repository while being wrong on the page. Four of the five
below were found by an adversarial audit reading for exactly that; the fifth the
benchmark replay found in itself.

The one that could break a run

CE.Scan.overCap sums six request dimensions against the scan row cap. The Rust
side priced five. The sixth, rowClasses, travels one entry per row and rides at
full chunk length, so it was sent and never charged.

The consequence is not a wrong number, it is a refusal: a large repository that
declares a [[rules.class]] gets its request answered degraded, and
scan/wire.rs turns that into cap mirror drift (scan/wire.rs vs Scan/Cost.hs)
an error blaming a mismatch only we can fix. ce scan and ce check abort.

The module's own head comment claimed the caller paid for the class column by
reserving the override rows. That arithmetic never held: overrides is at most a
few rows per declared class, rowClasses is as long as the chunk. A row now costs
2 on a classed run and 1 otherwise, priced where the split is decided, with a leg
that pins both the halved capacity and the by-name refusal.

Three that a reader would have hit

  • edition 2,024. Both READMEs rendered the Rust edition through #digits, a
    form built for the 4,096-row erase cap, which groups from 1,000. Editions are
    named 2015 / 2018 / 2021 / 2024; with a comma it is not a valid value, and a
    reader grepping the page for 2024 found nothing. There is now a #name form —
    an integer that names rather than counts — and the edition fact reads it. The
    comma shipped in three releases.
  • The comparability sentence. 1.4.0's release notes, and two lines of the
    CHANGELOG, all declared that scores are not comparable with 1.3.x. The sentence
    in both READMEs that enumerates when scores stop being comparable listed only
    a [[rules.class]] switch and the v1.2.0 → v1.3.0 submodule move. Someone
    upgrading from 1.3.x and reading that page would have taken the recursion
    increment for a regression in their own code. It is hand-written prose with no
    chip on it, so no gate could have caught the omission.
  • Fourteen spaces. The refusal a file too large for a chunk receives lost its
    line continuation when the block moved out of wire.rs, so a deliberately named
    refusal read like a broken build. The existing leg now asserts the text, not just
    a keyphrase.

The benchmark series disobeyed its own page

docs/BENCH.md has said since 1.3.0 that a release joins the latency series only
when there is something new to measure — one that ships the same cli/src and
core/app as its predecessor gets no row, because replaying the whole series to
add a duplicate measurement publishes machine drift under a new version number.
Nothing executed that sentence. The backfill driver measured every tag.

So v0.7.1 and v1.0.1 had been in the published table all along, and the v1.4.0
replay was about to add v1.3.1 and v1.3.2. The rule now lives in the producer:
series_tags applies it before any subset filter and names every tag it turns
away. The series is 14 versions × 7 metrics = 98 rows, one sitting, one machine.

The same replay exposed a second defect in itself. Only recent tags were really
measured against their own core: v0.1.0's ce takes the core as a --core flag
defaulting to the bare name and reads CE_CORE_BIN only on its daemon path, so it
resolved whatever ce-core was installed on the machine. It now gets the tag's
core two ways, which is what makes the page's promise true for every row rather
than the recent ones.

Score comparability

Fully comparable with 1.4.0. git diff v1.4.0..v1.4.1 -- core/app is empty and
the only cli/src change is the chunk budget, which decides how a request is
split, not what it is worth. Comparability with 1.3.x and earlier is unchanged
from what 1.4.0 said: not comparable, on any repository that recurses.

Unsigned, on purpose

Code signing and notarisation stay out of scope (ADR-007/R1). The SHA-256 chain is
the permanent trust anchor: every asset is pinned in plugin/bin/manifest.env at
the release commit, the tag job verifies each pin before publishing, and
SHA256SUMS covers the set for offline checking. ce update reads the latest tag
and that tag's committed pins.

CodeEraser 1.4.0

Choose a tag to compare

@github-actions github-actions released this 01 Sep 02:49

CodeEraser 1.4.0

Cognitive complexity now charges the recursion increment. Every function inside
a recursion cycle costs one more point than it did in 1.3.x, so scores from
this release are not comparable with scores from 1.3.x
on any repository that
recurses.

The rule, and why it was missing

The S3776 whitepaper (v1.7, p.8 and Appendix B1) says the increment applies to
each method in a recursion cycle, whether direct or indirect. This
implementation had carried that as a known gap since M1, with the head note of
cognitive.rs saying so.

SonarSource's own analyzers do not implement it either — java, python and
javascript were read at source on 2026-08-31 and recurs matches nothing in any
of the three. So this release takes the side of the specification and records the
consequence: on a function inside a recursion cycle, CodeEraser now scores one
point higher than a real SonarQube would. That is not a defect and will not
be "fixed" on some corpus later; it is a difference of reading between two
implementations of one spec, and it lives in the attribution column of
contracts/fixtures/crosscheck/DIVERGENCES.md.

Where the work lives

ADR-008's fourth instalment splits the two halves along the language line this
project has held from the start.

  • Call arcs are a measurement-side fact — lexical, per parse unit, in Rust
    (cli/src/scan/calls.rs).
  • The cycle is a verdict — Haskell, core/app/CE/Scan/Cycles.hs, computing
    its own SCCs. It deliberately does not reuse CE.Graph.Cycles, whose first
    line says cycles there are REPORTED and never judged (RG9); the two faces must
    not be crossed. What it does copy, verbatim, is that module's reading of a
    single vertex — so direct recursion needs no special case at all: it is a cycle
    of length one.

The policy constant +1 appears in exactly one place in the repository.

Scope is within a file. Cross-file cycles are a named non-goal, and the
position is kept as an assertion rather than a sentence, so it cannot rot.

Wire: scan/1 6.5.0, additive

Request gains callEdges = [[from, to], …] — both ends are row indices into the
existing rows table, strictly ascending, so no name and no path crosses the
wire. Reply gains cocBumped = [[rowIndex, effectiveValue]].

It sends the value, not the increment. The measurement side then renders the
number the core judged and never re-derives a cycle or an increment of its own;
cli/src/scan/coc.rs does index arithmetic and one monotonicity check. All four
readers — findings, the pinned evaluate mirror, the ADR-006 ratchet's
complexity column, and the JSON report — therefore read the same integer, because
scan::settle is the only road it travels.

Chunking gained a testable invariant along the way: a file's rows may not span
two chunks
. Arcs are stated as row indices, so a boundary inside a file cuts an
arc in half, and losing one arc is losing one cycle. Chunking now splits on files;
a single file too large to fit is refused by name rather than split.

What it measured here

27 functions in this repository each gained exactly one point — walk, go,
loop, visit, flatten, pairs, the recursive shapes, found by running the
old and the new binary over the same tree and comparing function by function.
ce check still reads 953, and the complexity axis still contributes 0. That
is this repository's arithmetic, not a general result: a repository whose
recursive functions sit near the ceiling will score lower under 1.4.0 than it did
under 1.3.x.

Two real defects surfaced from measuring rather than from reading:

  • impl Drop { fn drop(&mut self) { drop(...) } } was mis-charged. The bare
    drop inside is the prelude's free function, and a bare name cannot reach a
    method. LangSpec::call_member_scopes now keeps bare names out of member
    scope, probed syntax-first for all five languages.
  • A bare call whose name is bound by an import inside the same unit was
    mis-charged the same way — found while recalibrating against the four
    crosscheck corpora, on ignore's fn symlink { use std::os::unix::fs::symlink; … }. LangSpec::call_import_kinds now refuses those.

Both carry a paired probe: remove the rule and the exact mis-charge returns;
change only the name being imported and the recursion is still charged.

After those fixes the four crosscheck corpora (go 52, python 118, rust 319,
typescript 25 units) moved not one unit, and every published divergence row
against gocyclo, lizard, RCA and gocognit stands unchanged.

How it is anchored

None of the whitepaper's six worked examples recurses, so there is no scored
example to copy. The anchor is derived instead, and derived so that both sides
are checkable: take p.10's sumOfPrimes, whose margin score of 7 is already
pinned by an existing test against that same page, and add one self-call whose
result is discarded — a call is not itself a structural increment. The same
source must therefore still read 7 before the cycle settles and 8 after. The
difference is the rule under test.

core/test/ScanCyclesProps.hs carries seven legs, the main one comparing all
65536 four-vertex graphs
against an independent oracle that never touches
Data.Graph.

Also in this release

Both READMEs now open with the product doing its visible thing rather than with a
diagram of it: a terminal card in which an agent asks to create a file and ce
answers with the indexed region that content already duplicates, and the ordering
that would pass. The card is generated, not photographed — the same demo run that
produces the console block further down the page is also drawn to SVG, so there
is no second artefact that could drift from the first, and node demo/run.js --check compares both cards byte for byte. The architecture diagram moves down to
the tech-stack section, which is the section about the machine.

Score comparability

Not comparable with 1.3.x. Cognitive complexity is a judged axis and its
value changed for every function inside a recursion cycle. Repositories with no
recursion are unaffected, but there is no way to tell from a score alone which
case a repository is in, so treat 1.3.x numbers as a different series.

No default guard tier changed, so the false-positive figures stand where 1.2.0
left them. The index schema stays at 15 and no other request family changed
shape; the shared wire version goes to 6.5.0 for scan/1's two additive
keys. A pre-6.5.0 core is refused by name when its reply arrives without
cocBumped — it would otherwise have judged the call edges silently.

Latency

v1.4.0 changes cli/src and core/app, so it joins the benchmark series. The
series is replayed WHOLE, in one sitting, from each tag's own tree and each tag's
own binaries — that replay runs against this tag and lands on
the dashboard and in
docs/BENCH.md
shortly after publication. Until it does, those surfaces say so in generated text
rather than quoting a stale row under a new version number.

Unsigned, on purpose

Code signing and notarisation stay out of scope (ADR-007/R1). The SHA-256 chain
is the permanent trust anchor: every asset is pinned in plugin/bin/manifest.env
at the release commit, the tag job verifies each pin before publishing, and
SHA256SUMS covers the set for offline checking. ce update reads the latest tag
and that tag's committed pins, so it needs nothing published beyond this release.

CodeEraser 1.3.2

Choose a tag to compare

@github-actions github-actions released this 31 Aug 06:32

CodeEraser 1.3.2

The closeout release. The roadmap is gone — not deleted, ruled on — and what
took its place is a list of positions this product holds permanently.

The project is finished

The plan's K–L row named three deferred bundles: M (scoring and evaluation
items, plus product small items), N (distribution) and the four evidence
gates
that would decide which guard classes may be promoted — 45 items in all.
All 45 are ruled not done. Not forgotten, not deferred indefinitely: the
1.3 shape is the finished shape, and everything after this is maintenance —
defect fixes, dependency and measured-figure updates, and the documentation that
follows them. Reviving any one of them takes a fresh plan, not an inherited one.

Clearing a deferred bundle in this repository could not mean deleting the
sentence that named it. Every deferral here was a named disposition with a
reason, so its end had to be one too.

What that means for a reader

Both READMEs' Roadmap and known limits is now Known limits, and the
reader-facing half of those 45 items is stated as permanent positions rather
than plans. Each one was re-derived from the tree the day it was written, not
copied from the frontier ledger — which is already wrong in at least one place:
the ledger says there is no in-app update channel, and ce update shipped in
1.3.0.

  • Symbol-level liveness is an advisory and never a verdict — ce deadcode says
    so on its own last line.
  • A guard class stays observe until it carries its own false-positive record.
  • ce structure has no score floor, so that family reports and gates nothing.
  • A release builds three targets — x86_64-windows, x86_64-linux,
    aarch64-macos. The plugin starter can resolve five: x86_64-macos and
    aarch64-linux have no pinned asset and fall back to a PATH ce or a source
    install. The site's install line used to say "Windows · Linux · macOS", which
    is true about operating systems and misleading about architectures; it now
    names the three keys that actually have assets.
  • The Claude Code marketplace entry tracks main, not a release.

One gate that could not have caught what it was watching

After 1.3.1 published, four surfaces — both READMEs and both homepages — still
called 1.3.0 the latest version, and every byte gate over them was green. The
sentence is generated: it reads the newest version in the benchmark series, and
1.3.1 never joined that series because it changed no measured code. The gate
compares each file against its own generator, so a generator stating something
false is invisible to it.

The fix is not a corrected number. The heading now says only what was measured,
a derived sentence adds "the current release, vX, has no row of its own", and a
new assertion reads CARGO_PKG_VERSION — the only check in the repository that
consults the crate rather than the contract, so the two can disagree out loud.
docs/BENCH.md now also states the rule that was never written down: when a
release joins the series, and what the page owes a reader when it does not.

The demo leads with the finding

The in-repo demo runs one task twice, differing only by the hook. Its result was
twelve rows of prose labels; the five numbers that carry the point now open the
page — writes refused before the file existed 0 → 2, clone blocks left behind
4 → 0, duplicated doc segments 1 → 0, removals still owed 1 → 0, check score
952 → 979 — with the table kept below as detail. The homepage had never shown a
before/after at all. Both surfaces render from one row list through the existing
generator, so the site cannot quote a figure the README's table does not have.

ce deadcode judged the two new files dead the moment they existed, and it was
right: nothing linked them. The fix was to write the paragraph in
demo/README.md that explains them, not to add a link that would quiet the gate.

A performance proposal, measured and struck

A mention-universe fast path — an (mtime,size) prefilter with the content hash
as fallback — was ruled in, then measured before being built. A prefilter does
not turn the read into nothing; it turns the read into a stat. On this
repository's universe of 832 files and 10.29 MiB that is 155 ms of reading
against 32 ms of stat-ing, so the whole ceiling is 123 ms — roughly a tenth
of one warm command, and smaller than the two git ls-files questions that
precede it. It also lands on almost nothing: Advisory::Yes occurs exactly once
in the tree, so the gate, ce scan and the write-time hook never pay for the
pass at all. Against an ADR amendment, a per-file (mtime,size) column, and a
class of correctness risk the content hash does not have, it is struck.

Score comparability

Fully comparable with 1.3.0 and 1.3.1, and checkably so: git diff v1.3.1..v1.3.2 -- cli/src core/app is empty. The measurement side and the
judgment core did not move a line, the wire is untouched (proto 6.4.0, graph/1
6.4.0, index schema 15), and no default guard tier changed, so the FPR figures
stand where 1.2.0 left them. That is also why this is a patch and not a minor —
the version number describes the artifact, not the occasion.

Latency

No row was added, for the same reason as 1.3.1: the measured program is the same
program. A row per unchanged binary would publish machine drift under a version
number. The dashboard and
docs/BENCH.md
now also say when a release joins the series and what the page owes a reader when
it does not.

Unsigned, on purpose

Code signing and notarisation stay out of scope (ADR-007/R1). The SHA-256 chain
is the permanent trust anchor: every asset is pinned in plugin/bin/manifest.env
at the release commit, the tag job verifies each pin before publishing, and
SHA256SUMS covers the set for offline checking. ce update reads the latest
tag and that tag's committed pins, so it needs nothing published beyond this
release.

CodeEraser 1.3.1

Choose a tag to compare

@github-actions github-actions released this 31 Aug 00:58

CodeEraser 1.3.1

A documentation and site release. The shipped program is bit-for-bit the same measurement side and judgment core as 1.3.0 — see Score comparability below for the check that says so.

Highlights

  • The homepage's three GUI screenshots are now generated by the product. They were posed by hand in August and had gone stale four ways at once: an eight-tab strip after the strip grew to ten, ce.join-report/0.1.0 after the schema reached 0.3.0, a tree measuring cli/tests inline after it became a submodule that is read but never judged, and alt text quoting a structure score of 854 after it had moved to 832. Every one of those facts is derived elsewhere on the same site; the pictures were the last surface that could disagree with the product and not be caught. scripts/shoot_gui.js now shoots them by running the real gui/ui in headless Edge — the engine the shipped app draws through, since Tauri on Windows is WebView2, so these are the product's own pixels rather than a lookalike. The one thing a browser has no version of is the webview's invoke bridge; it is fed report documents the CLI produced, and those are the documents the webview itself would have received (ce … --format json and the Tauri command call the same report_json over the same judge::run).
  • Five gates hold the pictures (it/site_screenshots.rs): no picture may be older than the gui/ui it shows (git ancestry, and a shallow clone with no history is refused by name rather than passing vacuously); each is a whole 1424×892 app window; neither homepage may hand-type a number beside a picture; the receipt contracts/gui-shots.json must name the three SCHEMA_IDs the code declares today — the path the first gate cannot see, where the interface holds still but a report shape moves — plus a sha256 per picture; and every file under site/assets/ must be claimed by some gate, derived rather than listed.
  • The shoot is reproducible, which is the part that makes it a check. Caught by re-running the generator before cutting this release: three shoots off one saved report set gave gui-tree.png three different digests and gui-candidates.png two. Every shot opens by clicking a button, and the app gives buttons a 0.12 s background transition, so a capture two frames later landed at an arbitrary point along the interpolation. That is not cosmetic — if a re-shoot always differs, "is the committed picture still current?" has no answer. The fix uses the app's own switch: it already answers prefers-reduced-motion, so the harness declares it and the transition never starts. Three consecutive shoots are now byte-identical, and gui-candidates.png was re-taken because the old one was shot mid-transition. A sixth gate holds the coupling: the CSS rule and the CDP call live in two files nobody edits together, and removing either reddens it.
  • The two scores say which they are. ce structure and ce check both give a number out of 1000 and they are not the same measure: one is tree-scale entropy, the other the gate's own seven axes plus the ratchet and the floor. This repository sits at 832 on one and 952 on the other, and neither is "the" score. The console had always distinguished them; the two GUI screens printed a bare number, so a reader moving between tabs — or between the homepage's terminal block and its screenshot — had no way to tell which was which. Both screens now carry the console's own words in both languages, and docs/reference/gui.md records why the two cannot be reconciled: they are not measuring the same thing.
  • The judgment diagram fits its frame. Both language twins were drawn on a 1000-wide canvas that the layout never filled, leaving the right third of the picture empty in the README and on the site. The canvas is ours rather than the renderer's — meta.viewBox in our diagram IR is a hand-written number — so the fix is one line per language file and a re-render: same geometry, a frame that matches it.
  • The Chinese diagram speaks one language. The renderer writes a little chrome of its own that no key in our IR can reach, so a zh diagram was still labelled "Legend". scripts/diagram_svg.mjs now carries a per-language map, and a gate reads that map back out of the renderer and refuses any zh SVG still carrying a term it covers — so the check holds no second copy of the list whose whole job is to be applied.
  • One derived fact stopped answering for two different rosters. count:axes is linked to the check score's seven axes, and both READMEs were rendering it inside sentences about the structure family's seven, which live in a different file. They are equal today, so the prose read true and nothing would have noticed the day either family grew. The structure sentences now render count:structure_axes, scraped from the judge that owns them.
  • README, both languages, aligned and slimmed to 168 lines each: the score bullet says which score and names its axes, the design rules run to ADR-009, the generated-or-gated inventory names the three members it had grown since it was written, and five restatements are gone — each said elsewhere in full, none of them a fact the page states only once.

Score comparability

Fully comparable with 1.3.0, and checkably so: git diff v1.3.0..v1.3.1 -- cli/src core/app is empty. Not a line of the measurement side or the judgment core moved; the two version literals in cli/Cargo.toml and core/ce-core.cabal are the whole of it. The wire is untouched (proto 6.4.0, graph/1 6.4.0, index schema 15) and no default guard tier changed, so the FPR figures stand where 1.3.0 left them. That is also why this is a patch and not a minor: what shipped is a build-time screenshot generator, two GUI caption strings, the documentation and the site.

Latency

No row was added, for the reason above: the measured program is the same program. The dashboard's rule is that the whole series is replayed in one sitting whenever a release joins it, because every row must share one machine state — replaying a single unchanged tag four days later moved every one of its seven metrics, from 11 % faster to 12 % slower. Replaying sixteen tags to add a row for identical binaries would publish that drift under a version number, which is worse than publishing nothing.

What this release does carry is the series 1.3.0's notes promised but could not ship: the whole-series replay landed on main after that tag was cut, so the dashboard and docs/BENCH.md reach the site with this one.

Unsigned, on purpose

Code signing and notarisation stay out of scope (ADR-007/R1). The SHA-256 chain is the permanent trust anchor: every asset is pinned in plugin/bin/manifest.env at the release commit, the tag job verifies each pin before publishing, and SHA256SUMS covers the set for offline checking. ce update reads the latest tag and that tag's committed pins, so it needs nothing published beyond this release.

CodeEraser 1.3.0

Choose a tag to compare

@github-actions github-actions released this 30 Aug 15:23

CodeEraser 1.3.0

Highlights

  • ce update — a self-update on every face. The check reads the latest release tag and that tag's committed plugin/bin/manifest.env; the verdict is the exit code (0 current / 1 available / 2 unknown — no network is never read as "current"). ce update --yes downloads ce and ce-core, verifies both SHA-256 pins, then places them by rename (--installer also saves the verified GUI installer and prints its path). A copy the plugin bound is re-pinned by /plugin update codeeraser; a cargo install by cargo install codeeraser. GUI: new Update screen. Plugin: SessionStart announces a newer release once a day (CE_UPDATE_CHECK=0 turns it off), /codeeraser:update runs the check, MCP update_check only checks. Report schema ce.update-report/0.1.0.
  • Three faces, one product — gated. A capability × face table (CLI / GUI / plugin) is derived from clap's enum, the Tauri handler roster and tab strip, the MCP catalog, hooks.json and the plugin's commands and skills; every shipped face must be claimed by a row and every claim must ship. The table is embedded in both READMEs.
  • README rewritten in both languages (English README.md, Chinese README.zh.md) around one structure: what it is, functions and scope, how it works, before/after on a real in-repo demo (demo/ — the same seven-step task run twice, only the hook differs; outputs are byte-gated), benchmark dashboard, install/run/update, tech stack and philosophy, roadmap and limits.
  • Symbol-layer advisory (ADR-008 step 3b). ce deadcode reports unmentioned declarations (public / private / restricted / re-exported) as an advisory beside the liveness verdicts, computed from a mention universe of every file git sees; ce graph --mentions prints the universe and rates. Wire graph/1 grew additively to 6.4.0 (unmentioned/mounts tables, foreign-owner role bit, knobs fence); ce.deadcode-report 0.3.0; ce.mentions-report 0.2.0.
  • Tests moved to a public submoduleskymanbp/CodeEraser-tests mounted at cli/tests, with its own gates. Foreign-owned files are read but never judged (files.owner, role bit 7).
  • Documentation facts are derived, not typed. One registry in the test suite resolves every fact that moves with a version — versions, schema ids, counts and their spelled-out number words, gate figures — and four renderers place it: inline chips on nineteen surfaces, generated blocks, derived tables, and citation labels. contracts/docs-facts.json is the projection, byte-verified in CI; CE_BLESS=1 rewrites it locally and CI refuses the flag. Citations now key on the anchor's text rather than a line number, so an edit above a cited line re-renders the label instead of silently rotting it.
  • Two architecture diagrams, one per language, rendered from committed JSON IR by a pinned archify. The SVGs under docs/assets and site/assets are byte-identical twins and regenerate deterministically under a gate.
  • The hooks refuse in the reader's language. Every sentence the PreToolUse guard speaks — duplicate content, over budget, the graded zone, and the three notes beside them — now goes through the i18n table, so CE_LANG=zh no longer answers a Chinese session in English. The English wording is byte-for-byte what it was, so FPR replay is unaffected.
  • Two measured exhibits in the READMEs, replayed at build time on a scratch tree: a copied helper refused at the moment of writing, and one [[rules.class]] line read by two mouths (the guard refuses the write, ce scan reports the same threshold). The site's homepage terminal block is likewise generated from a real run rather than typed.

Score comparability

The repository's own score is not comparable with 1.2.0: test files left the judged tree for the submodule (step #12), and the mention universe, secrets exclusions and ce:allow grammar changed the judged population. No default guard tier changed; FPR replay stands at the 1.2.0 figures.

Latency

The whole series was re-measured in one sitting for this release — all fifteen
tags, each built and measured from its own worktree on one machine state, so
version-over-version is finally a like-for-like comparison. The replay landed
after this tag was cut, so it is on main, not on v1.3.0:
docs/BENCH.md.

Against 1.2.0, one metric moved for a reason in the code: deadcode_warm
413 → 923 ms p50
, because ce deadcode now also computes the
unmentioned-declaration advisory. On one warm index, ce graph costs 26 ms,
ce graph --mentions 811 ms and ce deadcode 947 ms — the mention pass is
almost all of it. That pass reads every file in the universe and hashes its
bytes on every run (content hash, not mtime), which is what makes it correct
and what makes it cost a second read of the tree.

The other movements are not the code. Running 1.3.0's binaries over 1.2.0's own
tree leaves scan, dedup_cold, dedup_warm, docdup_warm and check_warm
flat or faster than 1.2.0's binaries on that same tree; what changed is the tree
— the indexed population went from 361 to 542 files (the tests submodule is
indexed though never judged, and the release added demo/, the booklets and the
split-out unit tests). Read across the whole series, docdup_warm 801 ms sits
with 1.0.0's 777 and 1.1.0's 787 on a tree half again as large, dedup_warm
381 ms stays well under 1.1.0's 494, and scan is faster than 1.2.0.

One caveat the series now states itself, and it is checkable from the
repository: 1.2.0's row was first taken on 2026-08-26; replaying that same
tag four days later — its own tree, its own binaries — moved all seven of its
metrics, from 11 % faster to 12 % slower, five of them faster. A
cross-version delta of roughly a tenth or less is below this instrument's
resolution, which is why the series is replayed whole rather than appended
to.

Upgrade notes

  • A non-default ce.toml trips knobs_digest on the first run after upgrading (the baseline fingerprint covers the whole parsed config): re-establish by name with CE_ACCEPT_BASELINE=1 ce baseline <root>.
  • Index schema is rebuilt automatically (v15); the graph store re-derives (GRAPH_REV 15).
  • ce update exists from this release on; a 1.2.0 install upgrades by the usual channel once, then self-updates.
  • Wire: ce↔core 6.4.0, daemon 2.0.0. The hello reply echoes 1.3.0.

Assets

Nine platform binaries and installers plus SHA256SUMS, all built by the release workflow's three-OS matrix (nothing is built locally). Binaries are unsigned; the SHA-256 chain — SHA256SUMS, the manifest pins committed before the tag, and ce update's verification — is the trust anchor.

Also on crates.io (cargo install codeeraser), npm (name-reservation pointer), and codeeraser.dev.