• v0.27.0 5cc15a61af

    code-index v0.27.0
    All checks were successful
    CI / cargo fmt (push) Successful in 47s
    CI / OSS corpus tier-3 scale (nightly) (push) Has been skipped
    CI / Grammar rebuild from source (nightly) (push) Has been skipped
    CI / cargo doc (intra-doc links) (push) Successful in 4m53s
    CI / cargo test (abi, 32-bit + wasm32) (push) Successful in 5m18s
    CI / cargo deny (push) Successful in 5m15s
    CI / cargo clippy (push) Successful in 5m47s
    CI / cargo check (windows-gnu) (push) Successful in 6m12s
    CI / cargo check (MSRV 1.98) (push) Successful in 5m51s
    CI / OSS corpus (tier 1) (push) Successful in 25m24s
    CI / cargo test (push) Successful in 30m14s
    CI (Windows) / fmt + clippy + build + test (windows) (push) Successful in 40m59s
    CI / cargo test (daemon transport) (push) Successful in 12m54s
    CI / Plugin path cost + pool throughput (nightly) (push) Has been skipped
    Release Build / Generate Version (push) Successful in 27s
    Release Build / Required CI green (push) Successful in 1m1s
    Release Build / Build linux-aarch64 (push) Successful in 12m25s
    Release Build / Build linux-x86_64 (push) Successful in 15m33s
    Release Build / Build linux-x86_64-musl (push) Successful in 15m42s
    Release Build / Pack the XAML reference package (push) Successful in 50s
    Release Build / Build windows-x86_64 (push) Successful in 20m23s
    Release Build / Windows archive smoke (msvc) (push) Successful in 8s
    Release Build / Create Forgejo Release (push) Successful in 2m54s
    Stable

    buildagent released this 2026-09-08 20:57:30 +02:00 | 330 commits to master since this release

    code-index v0.27.0

    Build: v0.27.0+679

    Four-binary release: code-index-mcp (MCP stdio bridge — the usual entry), code-index-daemon (long-lived watcher + RPC server, auto-spawned), code-index-plugin-host (bounded worker for plugin packages, spawned by the daemon), and code-index (CLI: init, index, watch, doctor, link, plugin).

    The runtime plugin architecture becomes usable end to end; the Windows archive is
    rebuilt on a linkage where a guest trap is actually intercepted rather than
    fatal; and a round of dogfooding against this release candidate fixed eleven
    findings — ten places where a tool reported a state it had not measured, and one
    operator-facing measurement that had gone stale.

    A second package, de.h-dv.timeline, exists in this tree and is graded by CI —
    its extractor.wasm is rebuilt from source and byte-compared on every run — but
    it is NOT published as a release asset here. The only .cip this release
    ships is the XAML reference package. An operator who wants the TimeLine package
    must pack it themselves from the source tree; a signed asset for it is planned
    for the next release.

    Two further findings from that round are filed and NOT fixed here — an index that
    cannot see a git worktree's own edits, and a corpus bless writer with no arm for a
    record whose measurement reproduced while its conditions moved.

    149 commits since v0.26.1.

    Upgrading: existing conformance verdicts must be re-run

    This release records the WASM engine identity with the execution knobs it pins
    (epoch, fuel, stack, rsimd, rsimd_det, multimem, backtrace), not the
    wasmtime version alone. Those knobs decide how a package actually runs, so a C1
    verdict recorded without them is not provably transferable to this host — and every
    verdict recorded by v0.26.1 or earlier was recorded without them.

    On any machine that already holds conformance verdicts, code-index plugin doctor
    reports them as not current and WARNs, and plugin status shows not run here.
    Nothing is broken and no package changed; the record is simply less precise than
    the check now requires. A fresh install is unaffected — it has no prior verdict.

    Remedy, once per installed package:

    code-index plugin check <digest>
    

    plugin doctor names this case explicitly ("recorded before this host began
    pinning engine knobs — same wasmtime <version>") rather than reporting it as
    another engine, so the wording does not send you looking for a wasmtime change
    that did not happen.

    Upgrading: the first run after this release re-parses your code files

    A schema migration widens the recorded annotation region so a position inside a
    symbol's documentation resolves to the symbol it documents. That lower bound comes
    out of the parser and cannot be derived from stored rows, so the migration
    invalidates every code row and the next index pass re-parses them.

    Nothing is lost and no action is needed. The daemon answers throughout, and
    project_overview discloses the window while it runs — state: "reconciling" with
    a SCHEMA-UPGRADE REBUILD detail naming how many files still carry the
    invalidation sentinel. Counters read during that window describe rows being
    replaced, and the payload says so rather than letting you read them as settled.

    Text and metadata rows (Markdown, TOML, SQL, and the rest) are deliberately NOT
    invalidated — they carry no extraction, so re-hashing them would cost time and
    change nothing.

    Cost is proportional to code files, not to repository size on disk. A full
    re-parse of a large index has been measured at about ninety minutes; a few hundred
    files is seconds.

    The Windows archive is built natively now, and a guest trap is actually caught

    Until this release the Windows archive was CROSS-BUILT on Linux for
    x86_64-pc-windows-gnu and linked against the legacy msvcrt C runtime. On
    that linkage a WASM guest trap was not intercepted: instead of
    host.worker_trapped in milliseconds, the worker took 33 seconds and then
    died with abi.frame_truncated, leaving the host with a dead worker and no
    usable diagnosis. Every other platform we ship intercepts the same trap
    immediately and keeps the worker alive.

    The archive is now built natively on Windows with the MSVC toolchain, on the
    same runner that already smoke-tests it. Measured on the shipping
    configuration, one variable at a time:

    linkage trap verdict worker
    MSVC, static CRT (shipped now) 67.6 ms host.worker_trapped survives
    gnu + msvcrt (shipped through v0.26.1) 33,293 ms abi.frame_truncated dead

    Those timings are first-request figures and include worker spawn; steady-state
    repeat traps on the surviving configurations measured around 10 ms. The claim
    worth taking from the table is not the millisecond count — it is that the trap
    is intercepted at all and the worker is still alive to answer the next request.

    The archive name does not change, and neither does anything else you may
    have pinned: release archives are named <os>-<arch>, never by target triple,
    so the Windows asset is code-index-<tag>-windows-x86_64 before and after.

    It depends on no redistributable. The C runtime is linked statically, so the
    binaries import only DLLs present on a stock Windows 10 or later install. That
    is checked rather than assumed: a plain MSVC build imports vcruntime140.dll,
    which ships with the Visual C++ Redistributable and is not on a clean machine,
    and the release now REFUSES to publish an archive importing anything outside the
    stock set. Trading a trap bug for "the program can't start because
    VCRUNTIME140.dll is missing" would have been the worse regression, on the one
    path where the user has no toolchain.

    What this does not claim. The root cause is the C runtime, not the
    toolchain. A gnu build against UCRT also intercepts the trap and keeps the
    worker, and the one wasmtime build flag that looked like a suspect
    (__USE_MINGW_SETJMP_NON_SEH, which wasmtime defines for every
    target_env = "gnu" Windows build) is present in the working configuration too,
    so it cannot be the cause on its own. With that held constant the CRT is the
    only variable that moved. We ship MSVC because it is a supported configuration
    on a runner we already have — not because the gnu toolchain is at fault. UCRT on
    the gnu target does work, but it needs a UCRT-CONFIGURED mingw toolchain,
    headers and libmingwex included; swapping only the CRT import library is not
    enough and fails at wasmtime's own setjmp. Measured, with every attempt and
    its exact failure, in #231.

    Upgrading: the XAML package is 0.2.0 and its digest has MOVED

    de.h-dv.xaml is now 0.2.0, published as de.h-dv.xaml-0.2.0.cip, and its
    digest has moved to
    sha256:7b572f5cc32ffcd5af550900a451bbef37e255804776d7e3e3e71d2dd7d0aa79.

    Re-pin it:

    code-index plugin install de.h-dv.xaml-0.2.0.cip \
      --sha256 sha256:7b572f5cc32ffcd5af550900a451bbef37e255804776d7e3e3e71d2dd7d0aa79
    

    An operator who does not re-pin gets a bare digest_mismatch and no other
    explanation. The extraction_identity moved too, so every project with this
    package enabled re-extracts its .xaml files once — which is the point: files
    that were reported extract.truncated_tree are now indexed whole.

    Why it moved. The extractor's walk budget cut real markup at roughly 64-80 KB
    of source, depending on density — measured on a 14,056-file production repository
    where about fifty files were affected, including every theme dictionary. Symbols
    past the cut were missing from file_outline, search_symbols, find_references
    and change_impact, disclosed per file but easy to miss.

    All three budgets are now derived from the largest file the indexer will hand any
    extractor (2 MiB) rather than from the corpus of the day: the walk budget is 16 MiB
    of serialized tree, the output budgets 48,000 facts and 2 MiB of fact bytes. At
    2 MiB of dense markup the extractor emits 29,845 facts and reports nothing. Both
    truncation codes still fire, still mean different things, and are still
    distinguishable through index_coverage.

    The instrument, not just the fix

    The bug itself is one line of linkage. What let it ship in every release
    carrying a Windows archive is that the tested binary and the shipped binary
    were different builds, and nothing compared them.
    CI built its own gnu
    binaries natively and ran the plugin smoke against those; the archive users
    downloaded was cross-built and, until this cycle, had never executed a wasm
    guest at all.

    So the durable part of this change is not the CRT:

    • the archive is smoked with its own shipped bytes before publication, and
      the release job waits on that;
    • the smoke refuses to grade an artifact whose linkage does not match what
      the build leg declared — the refusal that turned this investigation into a
      finding instead of a confident wrong number, and it has correctly blocked
      publication twice;
    • linkage is now reported on two axes, toolchain and C runtime, because
      after this change both toolchains can import the same api-ms-win-crt-* set
      and a two-valued verdict would start lying in the opposite direction;
    • CI's own Windows smoke builds the same linkage as the shipped archive, so
      the two cannot drift apart again.

    Plugins

    A package can now be located, verified, installed, conformance-checked, enabled
    with an explicit capability grant, rolled back, disabled, removed and garbage
    collected, with every mutating command disclosing what it is about to do first.

    • A package may displace a builtin's claim, with consent recorded in
      [[displaces]] and the operator told what it costs. An undeclared intersection
      is still refused.
    • The package lifecycle is a committed number, ratcheted one rung at a time
      rather than asserted.
    • A refused package reaches an agent as a reason code, not as silence.
    • Reproducible guest wasm, byte-comparable from any directory.
    • A migrating daemon is distinguishable from a wedged one, and a wedged
      project has an exit.

    The architecture is proven on a real language rather than on markup alone: the
    full-language migration parity gate (#84) is closed.

    Honesty fixes found by dogfooding this candidate

    Eleven findings, filed and fixed against the release candidate itself.

    • doctor no longer renders "could not measure" as a verdict. PRAGMA integrity_check returning "unable to validate … : database is locked" said the
      check did not run and was reported as corruption — hardest exactly when an
      operator was most likely to run it, mid-reconcile. The opposite direction was
      also wrong: an error mid-iteration reported an all-clear.
    • doctor's freshness check counted never-indexable files as staleness — 35 of 35
      on this repository — producing a permanent WARN that hid real drift. It now
      splits the disk side by the same classifier index_coverage uses, and asks the
      project's own extractor set, so the two surfaces give the same verdict.
    • context_pack shipped over-budget responses with budget_exceeded: false. Six
      of eleven budgets were over; the block attached above the router was outside the
      arithmetic. The flag and the number are now one measurement of the bytes shipped.
    • changed_symbols reported a bare ref_count: 0 for symbol kinds this index
      measures as having no use channel, where four other surfaces disclosed it. The
      counter and its basis are now a single value, so no surface can render one
      without the other. direct_callers gained a basis of its own.
    • review_diff's same-package counterevidence was unreachable in every workspace
      and monorepo layout, because the root segment was stripped only when leading.
    • A declaration's leading DOCUMENTATION block is now part of it, in the six
      languages that mark one. get_symbol on a /// line answered symbol_not_found
      while the same query on a #[test] line one file over resolved, and a commit
      whose entire deliverable in a file was a correction to a recorded measurement
      reported symbols: []. Python and Ruby are excluded with reasons and tests:
      Python's docstring is already inside the span, and Ruby marks no comment as
      documentation — RDoc reads the same # syntax as # frozen_string_literal: true.
    • partial_sources now answers the sentence that cites it. The list of partial
      files was suppressed whenever a reply named none of them, so every reply carried
      a disclosure pointing at a field that was not there. An empty list is now the
      measurement that this reply names none.
    • One helper decides AND words the engine-drift distinction, across the three
      surfaces that render it. A verdict recorded before this host began pinning
      engine knobs is no longer reported as another engine.
    • search_text(whole_word=true) graded a different query from the one the daemon
      ran. Boolean and prefix expressions were matched as literal source characters and
      the resulting total: 0 was described as exact; ASCII-only matching lost Unicode
      case folding, so café missed a standalone CAFÉ while fix matched inside
      préfixé; and the census restated the matching rules a second time, so total,
      matches_in_file and the returned line numbers could describe different
      predicates. One compiled matcher now serves admission, census and line numbers,
      and an expression the post-filter cannot honour is refused with a reason instead
      of answered with a confident zero. Whole-word pagination also minted unstamped
      cursors, bypassing the stale-generation rejection.
    • The promotion-lock ceiling plugin enable discloses to operators was wrong at
      the 100k shape. Re-measured, with the cause established by removal rather than by
      reading, and the rollup census it pays for is now graded.

    One reported defect was refuted rather than fixed, and the counterexample is
    pinned so it cannot be quietly re-implemented: a builtin's auto-generated
    exclusion is NOT package-independent, because a consented [[displaces]] covers
    exactly the files the builtin excluded.

    Honest limits

    The plugin ABI and the .cip package format are EXPERIMENTAL and may change
    incompatibly in any release. A package is pinned to one thing — the host fact-ABI
    major it brackets in [abi].host_min/host_max — and to nothing else. No
    compatibility is promised or implied by this project's semantic version, in either
    direction.

    Epic #75 is not closed by this release. #80's final end-to-end gate has steps
    1–11 implemented and green and step 13 done, and step 12 has moved: the Windows
    archive is now BUILT natively and executed by a job whose verdict gates
    publication, so the platform is no longer cross-built and graded elsewhere.
    aarch64 remains UNMET by decision — there is no runner — and that is
    declared rather than silently skipped. #41 and #45 remain open.

    The TimeLine package's ACROSS-DIRECTORY reproducibility is unmeasured.
    de.h-dv.timeline's extractor.wasm is rebuilt from source and byte-compared
    wherever the suite runs, CI included — an absent wasm32 target is a failure
    there, not a skip. What is NOT measured is the across-directory leg: cargo
    derives -C metadata from an absolute path, so proving it needs a comparison
    between two checkouts, which no single cargo test can perform. That state is
    "not measured", which is not the same as "measured and bad".

    Packing determinism is verified separately and independently: the same
    checked-in bytes produce
    sha256:150ceb22ed9757a660d5a2bf58b80c81b60043cc419e5da5dde559d5d6fc9c9f on two
    different machines, directories and host binary builds.

    Package conformance is bounded evidence, not proof of semantic correctness.
    C1 stages a package alone with no capability grant; passing it says the package
    runs and responds within budget on its own fixtures.

    The promotion-lock constant is a flat rate over a cost that climbs with scale.
    It is sized to over-predict, which is the direction it is allowed to be wrong in,
    and at small generations it over-predicts by more than twice. lock_estimate is
    labelled PREDICTED at every call site.

    Downloads

    Four platforms are built for every release. The table below says which ones THIS release published.

    Platform Archive
    Linux x86_64 (glibc) code-index-v0.27.0-linux-x86_64.tar.gz
    Linux x86_64 (static/musl) code-index-v0.27.0-linux-x86_64-musl.tar.gz
    Linux ARM64 code-index-v0.27.0-linux-aarch64.tar.gz
    Windows x64 code-index-v0.27.0-windows-x86_64.zip

    XAML plugin package

    de.h-dv.xaml-0.2.0.cip makes .xaml files carry symbols and references instead of being text-only: x:Class and Click= handlers bind into the paired C# code-behind, x:Name becomes a searchable declaration, and {Binding …} stays unresolved because no bridge can reach it.

    It is NOT installed by installing code-index. Packages are installed into a user-controlled store and approved per project, and installing grants nothing — enable is the grant.

    Download de.h-dv.xaml-0.2.0.cips too and keep the .cips beside the .cip. Packages are signed and an unsigned one is refused (signature_missing) with no flag that accepts it. You do NOT need to anchor us first: this release's publisher key is compiled into the binary, so the install below works on a machine that has anchored nobody. code-index plugin trust list shows it, marked [BUILTIN], and code-index plugin trust remove sha256:1cb03259a8c870b6db02360abd9351e17e67724d1f8c3509d85c4bf6b06fa72c withdraws it — see About that key.

    code-index plugin install de.h-dv.xaml-0.2.0.cip --sha256 sha256:7b572f5cc32ffcd5af550900a451bbef37e255804776d7e3e3e71d2dd7d0aa79
    code-index plugin check   sha256:7b572f5cc32ffcd5af550900a451bbef37e255804776d7e3e3e71d2dd7d0aa79
    code-index plugin enable  sha256:7b572f5cc32ffcd5af550900a451bbef37e255804776d7e3e3e71d2dd7d0aa79 \
        --capabilities bridge_source \
        --bridges 'de.h-dv.xaml/xaml:type->csharp:class,de.h-dv.xaml/xaml:call->csharp:method'
    

    The digest above is the one this release packed and it is what --sha256 pins; de.h-dv.xaml-0.2.0.cip.digest.txt carries it and the extraction identity. A digest that changes between releases means the package changed — re-pin, and read the notes.

    Only .xaml is claimed. .dataset and other markup remain text-only (searchable, no symbols).

    About that key

    sha256:1cb03259a8c870b6db02360abd9351e17e67724d1f8c3509d85c4bf6b06fa72c is a long-lived Ed25519 key held as a secret in this project's CI, used to sign released packages automatically, and it is compiled into the binaries in this release. That trusts this project's release pipeline, not only its maintainers: anyone who can run a release workflow here can produce a package that verifies under it. It adds nothing you had not already granted — the bytes are inside the program you are running, so forging that anchor means forging the binary — but you are entitled to know it is there before a package installs rather than after.

    There is no expiry — deliberately, because offline installations must not break on a timer — and withdrawal is on your side and works: code-index plugin trust remove sha256:1cb03259a8c870b6db02360abd9351e17e67724d1f8c3509d85c4bf6b06fa72c. A compiled-in key has no file to delete, so that writes one instead, carrying denied = true; the key stops verifying at the next load, plugin trust list shows it marked [DENIED], and deleting that file (the command prints its path) is the only undo. If the key is ever rotated, the new fingerprint is published here and in tests/packages/first-party.fingerprint, and a build that predates the rotation will not trust it.

    You may anchor it yourself instead — code-index plugin trust add code-index-publisher.pub --fingerprint sha256:1cb03259a8c870b6db02360abd9351e17e67724d1f8c3509d85c4bf6b06fa72c --name 'a label you choose', with code-index-publisher.pub from this release's assets. Your file replaces the compiled-in entry, and the --name is a label YOU choose: nothing in the package, the key file or the signature can name its own publisher.

    macOS is not currently built. No x86_64-apple-darwin or aarch64-apple-darwin archive is published — macOS users should build from source (cargo build --release). This is a deliberate deferral, tracked in #59; no date is promised.

    Wire into Claude Code

    { "mcpServers": { "code-index": { "command": "/usr/local/bin/code-index-mcp", "args": ["--root", "/path/to/your/project"] } } }
    

    SHA256 checksums (.sha256 files) available for every archive.

    Downloads