@holmes-lab/holmes-kit 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,94 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
  <!-- @implements A-SPEC-209 -->
8
+ ## [0.25.0] - 2026-09-19
9
+
10
+ The semantic layer was dead on this repository and nothing said so. It is alive, it keeps itself
11
+ alive, it can now introduce a candidate the lexical pass never found, and a consumer learns it
12
+ exists without running `doctor`.
13
+
14
+ ### Added
15
+ - **`holmes-kit doctor` reports document-vector coverage** (A-SPEC-682). The tier line says what is
16
+ CONFIGURED; it never said whether one lookup would succeed. Measured here 2026-09-19: the resolved
17
+ tier was `cloud` and **0 of 602 scanned files** had a cached vector under it, so every lookup
18
+ returned nothing — reranking reordered nothing, alternates emitted nothing, and the output was
19
+ byte-identical to a layer that examined everything and agreed. A naive check would have counted
20
+ 532 cached vectors and called it healthy; those 532 keyed on symbol lists that no longer existed.
21
+ The count is therefore taken against the files scanned NOW, through the runtime's own accessor.
22
+ Four states: `covered`, `inert`, `not-adopted`, `unknown` — the two that cannot be judged say so
23
+ rather than passing quietly.
24
+ - **Vectors refresh on the channel that keeps the graph fresh** (A-SPEC-683). The graph self-heals
25
+ through the Stop hook's detached child; the vectors did not, because warming was reachable only
26
+ from an explicit `rtm_reindex`. The cache key is a hash of the document text and that text is the
27
+ file's path plus its symbol names, so every edit that renames a symbol invalidates that file's
28
+ vector: twelve days of work had taken coverage to zero. The detached refresh now warms too, the
29
+ Stop hook reports the verdict the child recorded (it never scans — tree-sitter wasm must not ride
30
+ into a gate process), and **an absent verdict reads as "not run", never as a pass**. Warming
31
+ under the cloud tier is egress, so it is automatic AND observed, with `HOLMES_NO_SEMANTIC_WARM`
32
+ to switch the transfer off — an owner who switches it off is shown a switch, not a fault. The
33
+ verdict records the commit it was taken at: work merged from another machine changes the symbols
34
+ the vectors key on, so a verdict from another head reports **needs re-measuring** rather than
35
+ yesterday's numbers as today's truth.
36
+ - **`issue_localize` admits what the lexical pass never returned** (A-SPEC-684). Its semantic
37
+ rerank could only reorder the array it was handed, so a question phrased in intent vocabulary
38
+ never reached the file that answers it. Measured with the tier live and the vectors warm: asking
39
+ *who opens a URL in the user's browser* ranked `LocalMarkdownRepository` first — the prose
40
+ contained the word "repository" — at 3.3x the second score, and never returned `open-url.ts`,
41
+ which the graph held throughout. The same need in mechanism vocabulary found its answer at rank 4.
42
+ `semanticAlternates` now carries the top cached-vector matches among files the emission missed;
43
+ `open-url.ts` comes back at cosine 0.754. **Pure addition on its own field**: the ranked `hits`
44
+ are untouched in set, order and score, because this repository measured that precision is lost by
45
+ admitting candidates INTO a ranked set. The mechanism is the one `maintenance_analyze` already
46
+ had (A-SPEC-494, measured ×1.25–×2.25 in weak windows across three corpora); it now exists once
47
+ and both surfaces call it.
48
+ - **A consumer meets the semantic ladder without running `doctor`** (A-SPEC-686). The layer is
49
+ measured — on 305 traceability cases here, recall 0.486 lexical → 0.667 local → **0.887 cloud**,
50
+ and on requests lexical search misses entirely, recovery 0% → 52% → **92%** — and a consumer
51
+ never learned it existed. Probed against a tarball install: `init` announced the guardrail mode,
52
+ the wired harness and the role policy and said nothing about the tier; the session banner said
53
+ the version and the governance rule. `init` now names the ladder with its measured numbers and
54
+ the command for each, and the session banner says it **once** per workspace on a machine and then
55
+ never again. `local` is named before `cloud` deliberately: `none` is the default because egress
56
+ needs consent, not because nobody got to it, and the cloud line states what leaves the machine.
57
+ A consumer who has already chosen a tier hears nothing at either moment.
58
+
59
+ ### Fixed
60
+ - **A project that never adopted the semantic layer hears nothing about it** (A-SPEC-683). Caught
61
+ by a consumer-shape probe before release, not by the suite: a tier-`none` workspace — the shipped
62
+ default, zero egress, never opted in — was told `semantic vectors: not run` on every turn. A
63
+ `null` verdict carries no tier, so "adopted but never refreshed" and "never adopted" looked
64
+ identical. The suite missed it because the not-adopted case was exercised through a verdict object
65
+ the consumer never has; the consumer reaches that code with no file at all.
66
+
67
+ ### Changed
68
+ - **One definition of the semantic document text, enforced by a census** (A-SPEC-685). The formula
69
+ existed in eight places; A-SPEC-682 gave it one definition and pinned it with a hand-written list
70
+ of two files — a note of which call sites that slice happened to touch, not a rule. The pin now
71
+ walks the source tree, and it immediately found what the list could not: two copies in the replay
72
+ benchmark. They were byte-identical, so live impact was zero — but a benchmark that keys
73
+ differently from the product scores a pipeline the product does not run, and this repository has
74
+ had an adoption verdict reversed by exactly that.
75
+
76
+ ## [0.24.1] - 2026-09-19
77
+
78
+ Documentation only. No code changed; `dist/` is byte-identical in behaviour to 0.24.0.
79
+
80
+ ### Changed
81
+ - **The README feature list stops growing without bound.** Accumulating one full paragraph per
82
+ shipped feature since 0.16.0 had taken the list to 30,905 characters, and the cost fell on the
83
+ reader who opens the page to find out what the CURRENT release is: the two 0.24.0 entries sat
84
+ above forty paragraphs of equal visual weight. The three most recent releases (0.23.2, 0.23.3,
85
+ 0.24.0) keep their full account; everything from 0.16.0 through 0.23.0 is condensed to one line
86
+ each under **Earlier releases**, and the older foundations are grouped under **Foundations** with
87
+ duplicated descriptions merged. Measured: 30,905 → 17,264 characters, a 44% reduction.
88
+ Each release's full account remains in this file, which the README now links to.
89
+ The unflattering measurements were kept rather than trimmed away — the wrong census number
90
+ 0.23.0 published and its correction, the 63.3% of candidate slots that were going to files that
91
+ could not be the answer, the 1,404 single-shot refusals that were burying a two-item inbox.
92
+ A summary that keeps only the favourable half is not a summary.
93
+ npm renders the README captured at publish time, so this release is what carries the shorter
94
+ page to the registry; the GitHub copy has been current since the commit itself.
95
+
8
96
  ## [0.24.0] - 2026-09-19
9
97
 
10
98
  A way for a consumer's defect to reach us, and two rules that existed only in prose.
package/README.md CHANGED
@@ -16,53 +16,58 @@
16
16
 
17
17
  ### 🛡️ Currently Supported Features (Production Features)
18
18
 
19
+ - 🔦 **A semantic layer that is inert says so** *(new in 0.25.0)*: the tier line said what was CONFIGURED, never whether one lookup would succeed. Measured on this repository: the resolved tier was `cloud` and **0 of 602 scanned files** had a cached document vector under it, so every lookup returned nothing — reranking reordered nothing, alternates emitted nothing, and the output was byte-identical to a layer that examined everything and agreed. A naive check would have counted the 532 vectors sitting in the cache and called it healthy; they keyed on symbol lists that no longer existed. `doctor` now reports coverage against the files scanned NOW, through the runtime's own accessor, in four states — the two that cannot be judged say so rather than passing quietly.
20
+ - 🔁 **Vectors refresh on the channel that keeps the graph fresh** *(new in 0.25.0)*: the graph self-heals through the Stop hook's detached child; the vectors did not, because warming was reachable only from an explicit `rtm_reindex`. The cache key hashes the file's path plus its symbol names, so every edit that renames a symbol invalidates that file's vector — twelve days of work had taken coverage to zero. The refresh now warms too, and the Stop hook reports the verdict the child recorded rather than scanning (tree-sitter wasm must not ride into a gate process); **a missing verdict reads as "not run", never as a pass**. Warming under the cloud tier is egress, so it is automatic AND observed, with `HOLMES_NO_SEMANTIC_WARM` to switch the transfer off — an owner who switches it off is shown a switch, not a fault. The verdict records the commit it was taken at, so work merged from another machine reports **needs re-measuring** instead of yesterday's numbers as today's truth.
21
+ - 🎣 **The graph can introduce a candidate your words never named** *(new in 0.25.0)*: `issue_localize`'s semantic rerank could only reorder what lexical matching already found, so a question phrased in intent vocabulary never reached the file that answers it. Measured with the tier live and the vectors warm: asking *who opens a URL in the user's browser* ranked an unrelated file first on the shared prose word "repository", at 3.3× the second score, and never returned `open-url.ts` — which the graph held the whole time. The same need in mechanism vocabulary found its answer at rank 4. `semanticAlternates` now carries the top cached-vector matches among files the emission missed, and that file comes back at cosine 0.754. **Pure addition on its own field**: the ranked hits are untouched in set, order and score, because admitting candidates INTO a ranked set is where this project measured precision being lost.
22
+ - 🪧 **The semantic ladder reaches you without running `doctor`** *(new in 0.25.0)*: measured here on 305 traceability cases, recall goes 0.486 lexical → 0.667 local → **0.887 cloud**, and on requests lexical search misses entirely, recovery goes 0% → 52% → **92%** — and a consumer never learned any of it, because only `doctor` said so. `init` now names the ladder with those numbers and the command for each, and the session banner says it **once** per workspace on a machine and then never again. `local` is named before `cloud` on purpose: `none` is the default because egress needs consent, not because nobody got to it, and the cloud line states what leaves the machine. A consumer who has already chosen hears nothing.
19
23
  - 📮 **`holmes-kit report` — a defect can reach the maintainers** *(new in 0.24.0)*: until now a consumer's holmes-kit defect had no way back to us; the one we learned about arrived because someone pasted a transcript, and it had been reproducing for every consumer on every slice. The command writes a **redacted** report to `.ax/reports/<fingerprint>.md` and prints a prefilled GitHub issue link — title, assignee, body — plus a search link for the same fingerprint so you can see whether it is already known. `--open` opens it; on a headless box or over SSH the printed link is the whole of it. **No token, no API, nothing sent automatically**: you press Submit on GitHub's own page with the body in front of you and editable. Redaction is an allowlist rather than a scrubber — this project's own remote carries a token before the `@`, its replica ids carry a person's name, and its spec titles are unreleased product intent, so a path, a credential or a machine identifier withholds the field and the report says which. A spec id passes by shape; a spec title does not. And what holmes-kit cannot know, it says: no ledger keeps its own refusal text, so a report with no description states that rather than pretending.
20
24
  - 🧭 **A skipped graph analysis is visible** *(new in 0.24.0)*: `AGENTS.md` asks for `maintenance_analyze` before editing source, and nothing checked. Measured on this repository, the step had been skipped for seventeen consecutive commits — and the run that followed named child-process precedents a name search had missed completely, because the question was "who opens a browser" while the answer lived under "who spawns a child". The Stop hook now reports source changed with no analysis standing open: non-blocking, judged by commit rather than by clock, and silent in a workspace that never adopted the habit.
21
25
  - 🧱 **A stale build is told, not discovered** *(new in 0.23.3)*: thirty suites in this project load `dist/` while they run, and nothing asserted that it still represented the source — only the release gate compared the build id to HEAD, and only at publish time. A stale build does not go red; it verifies old code and returns green. The Stop hook now reports it on the non-blocking `tracked` channel, judged by the **build id and never by mtime**: measured here, `.build-id` had a newer mtime than every source file while naming a commit nine behind HEAD, with two changed sources missing from `dist` entirely. A workspace that does not build hears nothing, `fresh` says nothing, and the two states that cannot be judged say **that** rather than passing quietly.
22
26
  - 📐 **Declarations are read as written** *(new in 0.23.2)*: `Files to Touch` is where a spec declares the files it will touch, and three things read it — fulfilment advisories, the declaration census and the approval impact note. The parser took only the **first word of a list item**, so measured over 678 approved specs here, **88 specs and 172 paths were declared and never read**; 24 of them parsed to zero while naming files plainly. Several paths on one line, an indented continuation, a Korean first word, a prose paragraph — all invisible. They are read now, wherever they sit, and a bare `name.ext` keeps its old position rule so a property access like `module.exports` is still not a file. A declared path that does not exist but is the suffix of exactly one repository file is reported as an abbreviation rather than a defect; two candidates stays an ambiguity and nothing is guessed. Cost, measured across the whole corpus: **zero** new `missing` findings.
23
27
  - 📄 **The publish gate reads the docs** *(new in 0.23.2)*: the publish playbook has demanded "bring README and CHANGELOG up to this release" since 0.16.0 and only prose enforced it, so it failed four times — including 0.21.0, 0.22.0 and 0.23.0, which each shipped with a feature list frozen at 0.20.0. The release gate now refuses two things it can decide: a missing CHANGELOG entry for the version being published, and an entry with `### Added` while `README.md` has not changed since the previous release. Replayed over eight releases it refuses exactly the three that were stale and passes the other five. What needs judgement — is the old wording still true? — stays with the person and is **reported**, never faked; a check that could not run says so instead of reading as a pass.
24
- - 🧩 **Your config files survive a re-wire** *(new in 0.23.0)*: `init --agent antigravity` and `init --agent codex` used to replace `.agents/mcp_config.json`, `.agents/hooks.json` and `marketplace.json` **whole**. Measured with a real `--dry-run` before the fix: a neighbour MCP server, a neighbour hook namespace, a neighbour plugin, a marketplace's own name and an operator's `disabled` flag all survived a re-wire at a rate of **zero**. They now survive — only the holmes-kit entry is refreshed, and `init` names what it kept. A `disabled` you set stays set (and `init` says the gate will not run while it stands, rather than switching it back on silently); an existing file that is not readable JSON is refused with a reason instead of being replaced. The Claude wiring already merged; the other two harnesses now have the same discipline.
25
- - 🫀 **The MCP supervisor notices a child that died** *(new in 0.23.0)*: under `HOLMES_MCP_AUTORELOAD` the supervisor had no exit handler, so a crashed child left it writing to a dead pipe its in-flight count never returned to zero, the swap that would have replaced the child never fired, and the server went **permanently deaf** (observed twice in one session, then reproduced on demand against the real class). It now answers every outstanding request with a JSON-RPC error **first** (a client must never wait for ever), then resets, respawns and replays the opening exchange. A child that dies having never answered spends a restart budget, so a broken build stops quickly while one crash under load restarts freely; an intentional swap or shutdown is not counted as an accident.
26
- - 🧮 **Coverage you can explain** *(new in 0.23.0, corrected in 0.23.2)*: the RTM census already said *where* an unlinked spec is anchored; it now also says *what it declared* — `scanned-source`, `file-anchor-target`, `test-target`, `unreachable-target`, `no-declaration` — plus how many specs declare a path no anchor mechanism can read. Bucket names state the fact; whether one is a defect stays `traceGaps`'s answer. **0.23.0 published a wrong number here**: it read "of 52 unlinked specs, zero declare a file the scanner parses, so the remaining 8.1% is not a missing anchor". That zero was the Files-to-Touch parser's, not the corpus's — see the 0.23.2 entry. The same census now reads three, and five real trace gaps were behind it.
27
- - 🤖 **A CI matrix that judges every commit, and never reads silence as green** *(new in 0.22.0)*: a maintainer-side runner takes one commit onto a Linux VM (clone install → build → full suite) and appends exactly one row to a `ci-runs` ledgerfor every outcome, including the ones where the run could not judge (`clone-failed`, `install-failed`, `vm-unreachable`). A launchd agent triggers it per commit. The Stop hook reports the matrix's last word and `doctor` gains a `ci matrix` check; a missing row reads as **"not run"**, never as a pass. Workspaces that never adopted the matrix hear nothing about it.
28
- - 🔁 **Advisories learn what happened next** *(new in 0.22.0)*: every finding impact, anchor density, Files-to-Touch fulfilment, trace gap, `kills`-unapplicable now carries a deterministic id. Sealing records it as `issued`; the next `approval_status` re-runs the **same** functions and records `resolved` when the cause is gone or `persisted` when it is not, at most one row per finding per day. An author who judges a finding unhelpful passes `dismiss: [id]`; an unknown id comes back in `dismissUnknown` rather than inventing a row. `rtm_dashboard`'s census gains the per-kind tallies. This is the numerator every "promote to a hard gate once we know the false-positive rate" sentence was missing.
29
- - 🧪 **`kills` that cannot apply say so** *(new in 0.22.0)*: `test_run --mutate` reports mutations that never applied as `unapplied`, separately from `survivors`. Measured here, all 22 `kills` entries in this repository wrote `where` as a file path and `mutate` as prose, so the literal-replacement engine applied **none** of them while the response still read `survivors: []` — the shape of a clean run. Sealing a T-SPEC now reports entries whose `where` is absent from the A-SPEC's anchored source, and the authoring playbook shows the grammar.
30
- - 🔎 **The RTM stops claiming coverage it cannot see** *(new in 0.21.0)*: `rtm_dashboard` and `/api/rtm` report `codeLinkedPct`, `unlinkedCount` and `unlinkedByReason` beside the old `coveragePct`, which read 100 while 11.4% of approved specs carried no `implements` edge. The scanner now also reads every anchor the injector can write (`.sh`/`.yml`/`.toml` anchors were planted and never indexed). Sealing gained a **Files-to-Touch fulfilment advisory** (declared paths that do not exist, as `missing`/`moved`/`alternatives`, ledgered, never blocking) and `rtm_impact` gained **trace gaps** — approved specs that declare a changed production file yet anchor only tests, with `impact_gate_check` answering `trace-gap` instead of silently widening.
31
- - 🩹 **`@known-defect(reason, expires=YYYY-MM-DD)` and constitution article ART-9** *(new in 0.21.0)*: a test that pins a known defect as its expected value carries a machine-readable marker. Unexpired markers are listed on the Stop hook's `tracked` channel as debt; the gate blocks only when a marker has **expired** or cannot be read (`no-reason`, `no-expires`, `bad-date`). String literals are stripped first, the expiry day itself counts as expired (UTC), and a walk that cannot start is no signal rather than a clean bill. A bypass is sometimes the right call — the marker is there so the next person can see it.
32
- - 🧑‍🤝‍🧑 **Concurrent Multi-Agent Workspace** *(new in 0.20.0)*: several agents, machines and clones work on one spec store and converge through Git. A Git-shareable **workspace identity** with per-checkout replica registration (`workspace_identity`) stamps every governed write with run provenance; specs become **UUID-keyed entities** (`entity_store`) whose display ids can be **renumbered without losing identity, anchors or approval closure** (`entity_renumber`); independent work from a common ancestor is **integrated** with stable identity, per-side conflict evidence, automatic renumbering of colliding new ids and explicit derived-state discharge (`entity_integrate`); single-use approvals are spent on the writer's own ledger chain so offline clones merge cleanly, and a nonce spent twice is a structured **double-spend** that freezes every authority-spending act until `ledger_reconcile`. Evidence (RED→GREEN outcomes, timelines) follows the entity across renames. Reproduced end to end in a project outside this repository on macOS and Linux.
33
- - 🗂️ **Approval decisions you can actually see** *(new in 0.20.0)*: `holmes-kit approve --status` (and the `--watch` header) shows the workspace, the delegation posture, and per request the risk grade, who asked (run · replica · workspace), the subject digest and exactly what a grant would open — plus recent decisions with their outcome (live until / consumed / expired / revoked / denied). A grant is now **bound to the workspace it was minted in and to the content the human read**: a copied grant is refused as `foreign-workspace`, a seal of changed content as `stale-subject`, and `approve --revoke` withdraws a live grant. Grants are git-ignored by `init`.
34
- - 🔁 **Import cycles are governed, at the three moments that can each do something** *(new in 0.19.0)*: the spec graph has been required to be acyclic since early on; the code graph now is too. **Guidance** reaches the agent before it designs (the authoring playbooks say "share types through a third module", pinned by test). **A design-time advisory**`approval_status`'s `graphPreview` gains `cycles` — names the cycles your declared files are *already* in, with each edge classified as `type-erasable` (TypeScript deletes it, so it is not a runtime cycle), `lazy-require` (a workaround someone already paid for) or `eager-value`; the note says in words that this is **not** a prediction, because at approval time the code that would create a cycle does not exist yet. **A ratchet** in the Stop hook's constitution catches new cycles after the code is written shipped in `track` (records, never blocks), and the escape is a **named exception**, never a threshold, so a project carrying legacy cycles can still adopt the harness. This repository went from three cycles to zero, and the two lazy `require()` workarounds they forced are gone.
35
- - 📐 **Size and fan-in, shown but never judged** *(new in 0.19.0)*: the same design-time advisory carries `architecture` — lines, symbols, longest function, fan-in and fan-out for each declared file. Numbers only: no grade, no threshold, no participation in any verdict. A test pins the *absence* of a severity field, because one would grow into the gate the evidence does not support (the size/defect correlation in the literature is largely "there is more code"). Practitioners still reject inspecting more than five things, so the number is worth seeing — the person decides.
36
- - 🎯 **Candidates you could actually act on** *(new in 0.19.0)*: the commit-history channel used to admit whatever git named, so ledger files and build baselines took emission slots — measured, **63.3% of candidate slots** went to files that cannot be the answer, one at the head of the list. Now history-derived candidates must be able to be source, vendored trees are demoted, and inside a file the search already found, def-use ranking puts the functions the request reaches through data flow first (symbol Top-10 recall 0.087 0.287 on this repository; unchanged on two others, and reported as conditional rather than general).
37
- - 🧭 **The graph speaks BEFORE you commit to a scope** *(new in 0.18.0)*: the read-only `approval_status` now also answers `graphPreview` `impact` (files that call INTO your declared Files-to-Touch from outside it, each anchor carrying its spec's intent sentence) and `density` (anchor-dense files inside the scope) — computed by the **same functions the sealing advisory uses**, so the preview can never disagree with the seal. Read the impact, then widen the declaration, narrow the design, or leave it knowingly; the authoring playbooks carry the step (pinned by test) and it stays a discipline, not a gate. Root-cause work gets the other half: `maintenance_analyze` candidates ride with `decisionContext` — the ADRs constraining that file and each decision's own sentence — which is the order a person diagnoses in (what broke, then why it was left this way). Both are information only: value tests pin that no ranking, score or gate reads them.
38
- - 📜 **ADR as a first-class governed document** *(new in 0.18.0)*: decisions stop leaking into agent memory outside the gate (a measured incident on a consuming project drove this). `spec_create(type: "ADR")` scaffolds a root decision document (Context / Decision / Consequences / Alternatives, `decided`/`decider`) under the store's full authoring governance — validate, `spec_approve` seal, ledger, tamper-block with its **own number space** (your existing `ADR-0001` just works) and a **hitl-only seal** (autonomy never self-approves a decision). Store ADRs join the existing decision surface with zero new edge kinds: `ADR-XXXX` citations in specs/code become `constrained_by` edges, `supersedes` chains link, the graph's SPEC:ADR node carries the Decision line as its intent summary, and legacy `.ax/decisions/` entries coexist (store wins on id collision). A migration guide ships at `docs/adr-migration.md`.
39
-
40
- - 📣 **Impact Advisory at sealing time** *(new in 0.16.0)*: approving an A-SPEC now returns what your Files-to-Touch declaration *missed* — files whose symbols **call into** the declared scope from outside it (1-hop, capped, repo-relative allow-list), computed from the persisted RTM graph at the moment of sealing. Advisory, never verdict: it rides the response *after* the seal commits, degrades to absence on any failure, and every emission lands in an observation ledger so its false-positive rate is **measured before** anyone proposes a hard gate. The graph keeps itself fresh — `rtm_impact` rebuilds on basis drift and the Stop hook spawns a TTL-gated detached reindex (staleness was measured as the advisory's quality factor: 7 findings on an 8-day-old graph, 17 after a fresh one). *(0.17.0 hardening)*: the advisory/impact graph is **approved-only** (a draft needs no approval to exist, so it can no longer reach these agent-visible channels), summary prose can't forge graph rows (structural characters fold at both the extraction and storage boundaries), and annotations are capped with explicit omission counts (a hub-grade response shrank 104.7KB 18.7KB, −82%). Sealing also gains an **anchor-density advisory** (observation-only): an A-SPEC whose Files-to-Touch contains an anchor-dense file (live anchors ≥ max(8, p90)) is annotated with `anchorDensity: [{path, anchors, p90}]` and ledgered — grounded in the measured precision tax of anchor accumulation; a count *gate* was considered and refused.
41
- - 🗣️ **The graph speaks intent** *(new in 0.16.0)*: every SPEC node stores a one-sentence intent summary (`"<title> <first sentence of its intent section>"`, schema `rtm-graph/3`, old stores rebuild automatically) — extracted deterministically, **never generated** (same store, byte-identical graph; measured cost +6.4% build time / +4.2% file size). Advisory anchors arrive as `{id, summary}` and `rtm_impact` adds `impactedSummaries`, so the reader sees *which intent* is at risk without a spec-store round trip. Information only: value tests pin that no verdict, ranking or gate reads the prose.
42
- - 📇 **Session-context observability** *(new in 0.16.0)*: the ledger records which agent/model drove a session and what the governance overhead cost, per replica (`session-context.<replica>.jsonl`), grounding field reports in machine attribution instead of guesswork.
43
-
44
- - 📋 **Requirements & Specification Governance**: Strict **"No Spec, No Code"** enforcement with 4-tier spec chain traceability (`REQ H-SPEC A-SPEC T-SPEC`) and `// @implements A-SPEC-XXX` code anchors (comma-lists and every anchor in a file participate in the gate).
45
- - 🔴 **Inbuilt TDD — RED-first, enforced not asked** *(new in 0.9.0)*: the test-first discipline is a holmes-installed `holmes-tdd-slice` skill **and** a new constitution article **ART-8**. A changed A-SPEC must show a recorded `red-assertion → green` sequence in the ledger; a `red-error` (a test that could not run) is not a valid RED, so "the covering test failed *correctly*" is judged mechanically, not on trust. `test_run` classifies each covered file (`red-assertion`/`red-error`/`green`) and records per-A-SPEC outcomes the Stop hook reads. Ships at `redFirstEvidence: track` (observe-first, non-blocking; `strict`/`off` per repo), evidence-gated and jest-only for now. A T-SPEC may also declare `kills:` mutations and `test_run --mutate` reports which SURVIVED (a coverage gap). Where superpowers *asks* for RED-first and discriminating power, holmes-kit *proves* them.
46
- - 🧰 **Governance UX tools** *(new in 0.10.0)*: `spec_unseal` (the inverse of `spec_approve` return a sealed spec to editable `draft` in one act, out-of-band approval required, refuses approved dependents), `approval_status` and `ledger_timeline` (read-only observability into a spec's seal state and the governance history), and a structured `conflict` on `spec_approve`'s optimistic-concurrency refusal (read vs. current version + retry). See CHANGELOG for details.
47
- - ⬆️ **Zero-config upgrades** *(new in 0.11.0)*: `holmes-kit upgrade` moves **every** wired workspace to the latest in one command — plan → confirm → install → re-pin all recorded workspaces (`--dry-run`/`--yes` supported). Preparation is automatic (each `init` records the workspace; a session whose pin is behind nudges you to upgrade); the re-pin **write** stays your explicit choice, never a silent auto-install. Opt out of the nudge with `HOLMES_NO_AUTO_REPIN`.
48
- - 📊 **World-top-tier RTM dashboard** *(new in 0.12.0–0.12.1)*: `holmes-kit serve` — ask to *see* the RTM heatmap and the `rtm_dashboard` MCP tool launches the server idempotently and hands back the URL plus an honesty **census** (requirement/pipeline counts, coverage %, what's excluded). The heatmap is a real **2D coverage matrix** (requirements × pipeline stages, rows seriated by completeness, sequential-ramp cells with the percent printed in each). Drilling into a symbol renders that function's **CFG as a layered DAG** with **PDG (data/control-dependence) colour overlays**, served by `/api/cfg?file=&symbol=` from the same engine the taint lane uses — a non-CFG language is named, never faked. Tokenised palette (sequential ramp, status colours, UI/mono pairing) with light/dark.
49
- - 🔢 **Sensible spec numbering** *(new in 0.12.1)*: a brand-new project's first slice is now **REQ-100**, not REQ-201 `spec_slice_init` shares the same id allocator as the reverse-draft path (`nextIdBase`, floor 100). Existing projects are untouched: the next id is always `max(existing)+1`, so a repo already numbering from 201 keeps the exact same sequence. Numbering past 999 yields 4-digit ids cleanly, and ADR references now recognise 4-digit ADRs (`ADR-1000+`).
50
- - 🤖 **Autonomous Approval — three layers, always bounded** *(reworked in 0.13.0; foundation 0.8.0)*: for teams that want the agent to self-drive the SDLC, autonomy is a posture the agent holds at two scopes — a **project default** you opt into at `holmes-kit init --autonomy` (persisted as the `HOLMES_AUTONOMOUS_APPROVAL` env in `.mcp.json`), and a **per-session envelope** you grant on the spot with `holmes-kit autonomy on --for 2h` (an expiring marker under the agent-write-protected `.ax/state/`). Under either, the agent seals **low-risk** specs itself (ledgered under an `autonomous:<client>` actor); every **governance-critical, high-risk, or irreversible** decision — `gate-behavior`/breaking A-SPECs, architecture/gate/taint files, and every upstream `REQ`/`H-SPEC`/`C-SPEC` is instead **refused and routed to the out-of-band `holmes-kit approve` queue** for a human, never silently self-approved. The active posture is **surfaced at every session start** so it can't be forgotten *(0.14.0: the hook side now reads the project default out of `.mcp.json` directly, so an `init --autonomy` project sees its banner and escalations without an env round-trip)*, and an agent can never grant it to itself: the env is env-only (blocked like `HOLMES_ROLE`), the session command needs a real TTY or an out-of-band `HOLMES_APPROVAL`, and the marker lives where agents can't write. Off = byte-identical to a fully human-gated project. *(new in 0.10.0)* `HOLMES_ELICIT=off` routes every decision straight to the same queue.
51
- - 🧭 **Spec-Evolution Trigger** *(new in 0.14.0)*: the gate used to judge only *where* a change lands (file ∈ Files-to-Touch, anchored, approved) never *what kind* of change it is, so a real architecture swap inside an approved scope passed unreviewed. Now, when a changed in-scope source **newly introduces an external dependency** (a swapped engine, a new runtime), Holmes-Kit raises a **spec-reappraisal**: manual mode warns at the turn boundary, and under autonomy it also files the drift in the out-of-band `holmes-kit approve` queue so the owner sees it — a decided reappraisal is never re-raised for the same drift. Observe-first by design: it never blocks a turn. Detection is TS/JS + Python, string- and comment-safe (prettier multiline imports, CRLF files, docstrings and template literals all judged correctly).
52
- - 🚢 **Release Autonomy + Docs-Currency Gate** *(new in 0.13.0)*: publishing is irreversible and outward, so `npm publish` stays **human-approved by default** — but a deterministic classifier (`releaseAutonomy`, reusing the same per-spec risk grade) lets a **low-risk** release (patch/minor, every spec auto-grade, autonomy on) self-publish under the ledger, while a **major** bump, any `gate-behavior`/security/architecture spec, or an upstream `REQ`/`H-SPEC` forces HITL. The `holmes-publish` playbook also gains a **docs-currency gate**: before any release it diffs the specs since the last tag and blocks if a user-facing change never reached `README`/`CHANGELOG` — a stale doc is a false claim.
53
- - 🧭 **Compatibility declaration gate** *(new in 0.15.0)*: Holmes-Kit runs on three agent harnesses (Claude Code, Codex, Antigravity) and three OSes (Windows/macOS/Linux) and now the **sealing act itself asks whether you considered them**. A new A-SPEC approves only with `harness_impact:` and `os_impact:` declared (`'none: <reason>'` or a full 3-cell mapping with `supported|unavailable|n-a` verdicts); a `none` claim is machine-cross-checked against Files-to-Touch (harness-surface paths, OS-signal file contents), the slice scaffold plants both fields as TODO the gate refuses untouched, and already-sealed specs are untouchedthe duty arrives with the next re-approval, exactly like `breaking_change`.
54
- - 🌐 **English CLI & hook surface** *(new in 0.13.0)*: the operator-facing CLI and hook messages — `doctor` output, the CLI usage/errors, the hook `deny` reasons and ART citations, and the interactive `approve`/`init`/`upgrade`/`semantic-key` prompts — are now English, guarded by a hangul-absence test over the **rendered runtime output** (not just a source scan, which misses `\u`-escaped strings). The MCP tool responses (`spec_create`/`spec_approve`/ledger/review) are still being migrated and are next.
55
- - 🪧 **Session Banner + Update Notice** *(new in 0.8.0; refresh implemented + made uniform in 0.12.2)*: every session start emits an English intro (version + governance rule + npm URL) to both the human transcript and the agent context (SessionStart hook + MCP `instructions`); when a newer published version is on npm, an install-mode-aware `holmes-kit upgrade` command is appended. The registry refresh (dist-tags query cached in `~/.holmes/update-check.json`) is detached, TTL-gated, and fail-silent, and now fires from **every harness's MCP-server startup** not just Claude's SessionStart hook — so Claude / Antigravity / Codex are notified alike. Opts out via `HOLMES_NO_UPDATE_CHECK`/`CI`. Upgrade execution stays your explicit choice (`holmes-kit upgrade`), never a silent auto-install.
56
- - 🧱 **Deterministic Gate, Hardened** *(new in 0.8.0; further hardened in 0.13.0)*: shell writes are judged at the segment's **effective working directory** (`cd sub && cat > ../src/x.ts` is sealed, legitimate out-of-tree scratch writes are freed); the governing anchor is the **whole set**, not the first match. *(0.13.0)* Two more bypasses are closed: the gate treats a project as **governed when any spec exists** (a fresh project holding only unapproved drafts is no longer an ungoverned free-for-all), and it classifies `cp`/`mv` by their **destination** (a copy/move landing on a source path is sealed even when the source file isn't code). Every gate change ships with two consecutive clean adversarial rounds.
57
- - 🧠 **3-Tier Semantic Layer** *(new in 0.3.0)*: knowledge-graph semantic search with an explicit consent ladder — `none` (default, **zero egress**), `local` (bge-m3, no egress, optional module), `cloud` (gemini-embedding-001, opt-in via `GEMINI_API_KEY`). Measured on 305 traceability cases: recall 0.486 (lexical) 0.667 (local) **0.887 (cloud)**; on lexical-zero requests: 0% 52% **92%**. Surfaced only additively rerank, evidence (`semCos`), and `semanticAlternates` never as a hard filter.
58
- - 🎯 **Graded Impact Surface** *(new in 0.3.0)*: `rankedImpact` (personalized-PageRank over the spec/code graph) beat its pre-registered naive baseline on **both recall and precision across 3 corpora (×1.6–×17)** the necessary condition for any better-than-a-person phrasing, measured before claimed.
59
- - 🐞 **Causal Defect Localization & CPG** *(equalized in 0.5–0.7)*: AST Code Property Graph (CFG/DDG/CDG) & Dataflow Taint reachability across 7 languages (TS/JS, Python, Go, Rust, Java, C/C++, C#) **42 language×layer cells graded on measured evidence** (11 corpora, 39,344 functions, zero invariant violations; C++ conditional on 67.9% parse coverage, disclosed in the matrix).
60
- - 📏 **Measured, Not Claimed** *(new in 0.3.x)*: performance is judged against a pre-registered modeled-human band (R 0.67–0.78 / P ≈0.9±). Current official grade: **band entry on recall; division-of-labor precision 0.727 = 81% of the modeled human reproduced by an independent context-free judge on a fresh blind window.** No superhuman claims until both metrics exceed the band.
61
- - 🧪 **Self-Healing & Diagnostic Doctor**: Automated integrity checks and self-healing auto-fix remediation (`holmes-kit doctor --fix` & `spec_remediate`) wiring-handshake checks run on Windows natively as of 0.3.2. As of 0.9.0, doctor also reports holmes-kit's own advertised **MCP schema token cost** (computed live) and warns when `HOLMES_MCP_PROFILE=full` needlessly re-advertises the hook-enforced gate-duplicate tools.
62
- - 🔔 **Approval UX** *(new in 0.3.1; inbox split 0.15.0)*: in-session approval dialogs forewarn their 120s deadline and, on expiry, the refusal says exactly where the decision went (`npx holmes-kit approve` out-of-band queue) no more silently dead dialogs. Since 0.15.0 the tracked queue holds **decision-seeking requests only**; plain gate refusals live in a local per-machine refusal log (raw commands never leave the machine), browsable with `approve --refusals` and still decidable by id — measured before the split, 1,404 single-shot refusals were burying a 2-item inbox.
63
- - 🚦 **Push & Server-Side Re-Validation** *(hardened in 0.8.0)*: a local `pre-push` evidence gate (test-run ledger head == push HEAD, green, executed > 0) plus a **server-side CI workflow** that re-runs `npm ci build full suite → tarball install probe`, so a `--no-verify` push or a hook-less clone is still caught.
64
- - 📊 **Automated RTM & Taint Heatmap**: Interactive standalone HTML/SVG report generation (`generateRtmHeatmap`) for spec coverage and security dataflow reachability.
65
- - 🤖 **CLI-First AI Harness Matrix**: Native process hook gating for Claude Code, Antigravity CLI (AGY), Codex CLI, and Google Antigravity SDK.
28
+
29
+ **Earlier releases** — condensed to one line each; every release's full account lives in [CHANGELOG.md](CHANGELOG.md).
30
+
31
+ - 🧩 **Your config files survive a re-wire** *(0.23.0)*: `init --agent antigravity|codex` refreshes only the holmes-kit entry instead of replacing `.agents/mcp_config.json`, `hooks.json` and `marketplace.json` wholea neighbour server, hook, plugin and your own `disabled` flag all survive, and an unreadable JSON file is refused with a reason rather than overwritten.
32
+ - 🫀 **The MCP supervisor notices a child that died** *(0.23.0)*: a crashed child used to leave the server **permanently deaf**; outstanding requests now get a JSON-RPC error first, then the supervisor resets, respawns and replays the opening exchange with a restart budget spent only by a child that never answered.
33
+ - 🧮 **Coverage you can explain** *(0.23.0, corrected in 0.23.2)*: the RTM census says *what each unlinked spec declared* (`scanned-source`, `file-anchor-target`, `test-target`, `unreachable-target`, `no-declaration`). The "zero" 0.23.0 published here was the Files-to-Touch parser's, not the corpus's — the corrected census reads three, with five real trace gaps behind it.
34
+ - 🤖 **A CI matrix that judges every commit** *(0.22.0)*: a maintainer-side Linux runner appends exactly one `ci-runs` row per commit for **every** outcome, including the ones it could not judge; a missing row reads as "not run", never as a pass. Workspaces that never adopted it hear nothing about it.
35
+ - 🔁 **Advisories learn what happened next** *(0.22.0)*: every finding carries a deterministic id, and the next `approval_status` re-runs the same functions to record `resolved` or `persisted` the numerator every "promote to a hard gate once we know the false-positive rate" sentence was missing. `dismiss: [id]` retires one an author judges unhelpful.
36
+ - 🧪 **`kills` that cannot apply say so** *(0.22.0)*: `test_run --mutate` reports `unapplied` separately from `survivors` measured here, all 22 `kills` entries in this repository applied **none** while the response still read `survivors: []`, the shape of a clean run.
37
+ - 🔎 **The RTM stops claiming coverage it cannot see** *(0.21.0)*: `codeLinkedPct`, `unlinkedCount` and `unlinkedByReason` join the old `coveragePct`, which read 100 while 11.4% of approved specs carried no `implements` edge — plus a Files-to-Touch fulfilment advisory and `rtm_impact` **trace gaps** (a spec that declares a changed production file yet anchors only tests).
38
+ - 🩹 **`@known-defect(reason, expires=YYYY-MM-DD)` and article ART-9** *(0.21.0)*: a test that pins a known defect as its expected value carries a machine-readable markerlisted as debt while live, blocking only once it has **expired** or cannot be read. A bypass is sometimes right; the marker is there so the next person can see it.
39
+ - 🧑‍🤝‍🧑 **Concurrent multi-agent workspace** *(0.20.0)*: several agents, machines and clones converge on one spec store through Git replica-stamped provenance, UUID-keyed entities that renumber without losing identity or approval closure, `entity_integrate` with per-side conflict evidence, and single-use approvals whose double-spend freezes every authority-spending act until `ledger_reconcile`. Reproduced end to end outside this repository on macOS and Linux.
40
+ - 🗂️ **Approval decisions you can actually see** *(0.20.0)*: `approve --status` shows who asked (run · replica · workspace), the risk grade, the subject digest and exactly what a grant would open. A grant is bound to the workspace it was minted in and to the content the human read a copied one is `foreign-workspace`, changed content is `stale-subject` and `--revoke` withdraws it.
41
+ - 🔁 **Import cycles are governed** *(0.19.0)*: guidance reaches the agent before it designs, a `graphPreview.cycles` advisory names the cycles your declared files are already in (each edge classified `type-erasable` / `lazy-require` / `eager-value`), and a Stop-hook ratchet in `track` catches new ones — the escape is a **named exception**, never a threshold, so a project carrying legacy cycles can still adopt the harness. This repository went three cycles zero.
42
+ - 📐 **Size and fan-in, shown but never judged** *(0.19.0)*: lines, symbols, longest function, fan-in and fan-out per declared file. Numbers only a test pins the **absence** of a severity field, because one would grow into the gate the evidence does not support.
43
+ - 🎯 **Candidates you could actually act on** *(0.19.0)*: history-derived candidates must be able to be source (**63.3%** of emission slots were going to files that cannot be the answer), vendored trees are demoted, and def-use ranking orders symbols inside a file the search already found.
44
+ - 🧭 **The graph speaks BEFORE you commit to a scope** *(0.18.0)*: `approval_status` answers `graphPreview` `impact` (what calls into your declared scope from outside it) and `density` computed by the **same functions the sealing advisory uses**, so the preview can never disagree with the seal. `maintenance_analyze` candidates ride with the ADRs constraining each file. Information only: tests pin that no ranking or gate reads them.
45
+ - 📜 **ADR as a first-class governed document** *(0.18.0)*: `spec_create(type: "ADR")` scaffolds a decision document under full authoring governance, with its **own number space** and a **hitl-only seal** (autonomy never self-approves a decision). `ADR-XXXX` citations become `constrained_by` edges; legacy `.ax/decisions/` entries coexist. Migration guide at `docs/adr-migration.md`.
46
+ - 📣 **Impact advisory at sealing time** *(0.16.0, hardened in 0.17.0)*: approving an A-SPEC returns what your Files-to-Touch declaration **missed** — files whose symbols call into the declared scope from outside it. Advisory, never verdict: it rides the response after the seal commits, degrades to absence on failure, and every emission is ledgered so its false-positive rate is **measured before** anyone proposes a hard gate. Approved-only, capped (a hub-grade response shrank −82%), with an anchor-density advisory alongside.
47
+ - 🗣️ **The graph speaks intent** *(0.16.0)*: every SPEC node stores a one-sentence intent summary, extracted deterministically and **never generated**, so an advisory shows *which intent* is at risk without a spec-store round trip. Information only.
48
+ - 📇 **Session-context observability** *(0.16.0)*: the ledger records which agent/model drove a session and what the governance overhead cost, per replica field reports grounded in machine attribution instead of guesswork.
49
+
50
+ **Foundations** — in place since the early releases, still load-bearing.
51
+
52
+ - 📋 **Requirements & specification governance**: strict **"No Spec, No Code"** across a 4-tier chain (`REQ H-SPEC A-SPEC T-SPEC`) with `// @implements A-SPEC-XXX` code anchors comma-lists and every anchor in a file participate in the gate.
53
+ - 🔴 **Inbuilt TDD — RED-first, enforced not asked** *(0.9.0)*: constitution article **ART-8** requires a recorded `red-assertion green` sequence in the ledger, and a `red-error` (a test that could not run) is not a valid RED so "the covering test failed *correctly*" is judged mechanically, not on trust. Ships observe-first (`redFirstEvidence: track`). Where superpowers *asks* for RED-first, holmes-kit *proves* it.
54
+ - 🧱 **Deterministic gate, hardened** *(0.8.0, 0.13.0)*: shell writes are judged at the segment's **effective working directory**, the governing anchor is the whole set rather than the first match, a project is governed when **any** spec exists, and `cp`/`mv` are classified by **destination**. Every gate change ships with two consecutive clean adversarial rounds.
55
+ - 🤖 **Autonomous approval — three layers, always bounded** *(0.8.0, reworked 0.13.0)*: a project default (`init --autonomy`) and an expiring per-session envelope let the agent seal **low-risk** specs itself under an `autonomous:<client>` actor; every governance-critical, high-risk or irreversible decision is refused and routed to the out-of-band `holmes-kit approve` queue. The agent can never grant it to itself, the posture is surfaced at every session start, and off is byte-identical to a fully human-gated project.
56
+ - 🧭 **Compatibility and evolution gates** *(0.14.0, 0.15.0)*: a new A-SPEC seals only with `harness_impact:` and `os_impact:` declared and machine-cross-checked against Files-to-Touch; separately, a changed in-scope source that **newly introduces an external dependency** raises a spec-reappraisal a warning when manual, a queued item under autonomy, never a blocked turn.
57
+ - 🚢 **Release autonomy + docs-currency gate** *(0.13.0, machine-checked in 0.23.2)*: `npm publish` stays **human-approved by default** while a deterministic classifier lets a low-risk release self-publish under the ledger; a major bump or any gate-behavior/security/architecture spec forces HITL. The publish gate refuses a release whose docs never caught up a stale doc is a false claim.
58
+ - 🧠 **3-tier semantic layer** *(0.3.0)*: an explicit consent ladder — `none` (default, **zero egress**), `local` (bge-m3, no egress), `cloud` (gemini-embedding-001, opt-in). Measured on 305 traceability cases: recall 0.486 0.667 **0.887**; on lexical-zero requests 0% 52% **92%**. Surfaced additively, never as a hard filter.
59
+ - 🎯 **Graded impact surface** *(0.3.0)*: `rankedImpact` (personalized PageRank over the spec/code graph) beat its pre-registered naive baseline on **both** recall and precision across 3 corpora (×1.6–×17) measured before claimed.
60
+ - 🐞 **Causal defect localization & CPG** *(equalized in 0.5–0.7)*: AST code property graph (CFG/DDG/CDG) and dataflow taint reachability across 7 languages **42 language×layer cells graded on measured evidence** (11 corpora, 39,344 functions, zero invariant violations; C++ conditional on 67.9% parse coverage, disclosed in the matrix).
61
+ - 📏 **Measured, not claimed** *(0.3.x)*: performance is judged against a pre-registered modeled-human band (R 0.67–0.78 / P ≈0.9±). Current official grade: **band entry on recall; division-of-labor precision 0.727 = 81% of the modeled human**, reproduced by an independent context-free judge on a fresh blind window. No superhuman claims until both metrics exceed the band.
62
+ - 📊 **RTM dashboard & heatmaps** *(0.12.0–0.12.1)*: `holmes-kit serve` renders a real 2D coverage matrix (requirements × pipeline stages) with an honesty census, drills into a symbol's **CFG as a layered DAG with PDG colour overlays**, and a non-CFG language is named rather than faked. Standalone HTML/SVG reports (`generateRtmHeatmap`) cover spec coverage and taint reachability.
63
+ - 🔔 **Approval UX** *(0.3.1; inbox split 0.15.0)*: dialogs forewarn their 120s deadline and, on expiry, say exactly where the decision went. The tracked queue holds **decision-seeking requests only** plain gate refusals live in a local per-machine log (raw commands never leave the machine), after 1,404 single-shot refusals were measured burying a 2-item inbox.
64
+ - ⬆️ **Zero-config upgrades & session banner** *(0.8.0–0.12.2)*: `holmes-kit upgrade` re-pins **every** recorded workspace in one command (`--dry-run`/`--yes`), and every session start states the version, the governance rule and any newer published versionfrom every harness's MCP startup, not just Claude's. The write stays your explicit choice, never a silent auto-install.
65
+ - 🧰 **Governance UX tools** *(0.10.0)*: `spec_unseal` (return a sealed spec to editable `draft` in one act, out-of-band approval required), `approval_status` and `ledger_timeline` for read-only observability, and a structured `conflict` on optimistic-concurrency refusal.
66
+ - 🚦 **Push & server-side re-validation** *(hardened in 0.8.0)*: a local `pre-push` evidence gate (test-run ledger head == push HEAD, green, executed > 0) plus a server-side workflow that re-runs `npm ci build full suite tarball install probe`, so a `--no-verify` push or a hook-less clone is still caught.
67
+ - 🧪 **Self-healing & diagnostic doctor**: integrity checks and auto-fix remediation (`doctor --fix`, `spec_remediate`), plus a live report of holmes-kit's own advertised MCP schema token cost.
68
+ - 🔢 **Sensible spec numbering** *(0.12.1)*: a brand-new project's first slice is **REQ-100**; existing projects keep `max(existing)+1` exactly, and 4-digit ids (including `ADR-1000+`) work cleanly.
69
+ - 🌐 **English CLI & hook surface** *(0.13.0)*: the operator-facing CLI, `doctor` output, hook `deny` reasons and interactive prompts are English, guarded by a hangul-absence test over the **rendered runtime output** rather than a source scan.
70
+ - 🤖 **CLI-first AI harness matrix**: native process hook gating for Claude Code, Antigravity CLI (AGY), Codex CLI, and the Google Antigravity SDK.
66
71
 
67
72
  ---
68
73
 
package/dist/.build-id CHANGED
@@ -1 +1 @@
1
- d23fd983-mu76a9wy
1
+ 30c9f274-mu7u760l
@@ -200,6 +200,7 @@ export declare function wiringHandshakeChecks(target: string): Promise<Check[]>;
200
200
  * quotes the measured stakes (lexical-zero recovery 0% -> 52% local / 92% cloud, S-491) so the
201
201
  * user decides the trade with numbers rather than adjectives.
202
202
  */
203
+ export declare function semanticCoverageCheck(root: string, tier: SemanticTier): Check | null;
203
204
  export declare function semanticTierVerdict(t: SemanticTier): {
204
205
  level: 'PASS' | 'WARN';
205
206
  detail: string;
@@ -50,6 +50,7 @@ exports.pushGateCheck = pushGateCheck;
50
50
  exports.ciMatrixCheck = ciMatrixCheck;
51
51
  exports.formatChecks = formatChecks;
52
52
  exports.wiringHandshakeChecks = wiringHandshakeChecks;
53
+ exports.semanticCoverageCheck = semanticCoverageCheck;
53
54
  exports.semanticTierVerdict = semanticTierVerdict;
54
55
  exports.detectTreeKeyTemporary = detectTreeKeyTemporary;
55
56
  // @implements A-SPEC-264, A-SPEC-423, A-SPEC-549.1, A-SPEC-590
@@ -68,6 +69,7 @@ const tier_1 = require("../semantic/tier");
68
69
  const probe_process_1 = require("./probe-process");
69
70
  const npx_cache_check_1 = require("./npx-cache-check");
70
71
  const ci_runs_1 = require("../project/ci-runs");
72
+ const vector_coverage_1 = require("../semantic/vector-coverage");
71
73
  const path = __importStar(require("node:path"));
72
74
  const role_policy_1 = require("../governance/role-policy");
73
75
  const blind_spots_1 = require("../guardrail/blind-spots");
@@ -1248,6 +1250,16 @@ async function runDoctor(packageRoot, target, opts, extraChecks) {
1248
1250
  const t = (0, tier_1.resolveSemanticTier)();
1249
1251
  const v = semanticTierVerdict(t);
1250
1252
  add('semantic tier', v.level, v.detail, v.fix);
1253
+ // @implements A-SPEC-682 — the tier line says what is CONFIGURED. Measured 2026-09-19 on this
1254
+ // repository, the configured tier was cloud while 0 of 602 scanned files had a cached document
1255
+ // vector under it, so every lookup returned nothing and both semantic surfaces were inert —
1256
+ // output-identical to a layer that examined everything and agreed. doctor renders the verdict;
1257
+ // it does not compute it (this file already carries 51 anchors against a p90 of 10).
1258
+ {
1259
+ const cov = semanticCoverageCheck(target ?? process.cwd(), t);
1260
+ if (cov)
1261
+ checks.push(cov);
1262
+ }
1251
1263
  // @implements A-SPEC-477 — the ".env stopgap" detection: a key in a project-tree file is a
1252
1264
  // commit-accident surface, world-readable at 644, and readable by every in-session tool. The
1253
1265
  // value is read to detect the pattern and never printed.
@@ -1689,6 +1701,50 @@ function entryOf(value) {
1689
1701
  * quotes the measured stakes (lexical-zero recovery 0% -> 52% local / 92% cloud, S-491) so the
1690
1702
  * user decides the trade with numbers rather than adjectives.
1691
1703
  */
1704
+ // @implements A-SPEC-682 — the coverage CHECK: the I/O half, kept out of the pure judgement.
1705
+ //
1706
+ // The count is taken against the files scanned NOW, through the runtime's own `cachedDocVector` —
1707
+ // the exact accessor the ranking surfaces call. Rebuilding the cache tag here would duplicate a
1708
+ // rule that differs by tier (local carries a revision and a pooling mode, cloud does not), and a
1709
+ // duplicated rule is the drift this whole slice exists to remove.
1710
+ //
1711
+ // Counting cache ENTRIES instead would have read 532 vectors on this repository on 2026-09-19 and
1712
+ // called it healthy, while 0 of them answered a lookup for a current file: the key is
1713
+ // sha256(document text), and that text changes whenever a file's symbols do. The reassuring
1714
+ // number is the wrong number.
1715
+ function semanticCoverageCheck(root, tier) {
1716
+ if (tier.tier === 'none')
1717
+ return null; // never opted in; it hears nothing about this
1718
+ const unjudgeable = () => ({
1719
+ name: 'semantic coverage', level: 'WARN',
1720
+ detail: (0, vector_coverage_1.coverageLine)((0, vector_coverage_1.vectorCoverage)({ tier: tier.tier, modelTag: null, total: 1, covered: 0 })),
1721
+ });
1722
+ try {
1723
+ const { CpgScanner } = require('../cpg/cpg-scanner');
1724
+ const { ScanFileCache } = require('../cpg/scan-cache');
1725
+ const { VectorCache } = require('../semantic/vector-cache');
1726
+ const { makeTierEmbedder } = require('../semantic/embedder');
1727
+ const crypto = require('node:crypto');
1728
+ const runtime = makeTierEmbedder(tier, new VectorCache(root));
1729
+ if (runtime === null)
1730
+ return unjudgeable();
1731
+ const dir = path.join(os.tmpdir(), 'holmes-cpg-cache-' + crypto.createHash('sha256').update(root).digest('hex').slice(0, 16));
1732
+ const scanned = new CpgScanner(undefined, new ScanFileCache(dir)).scan(root, root);
1733
+ const covered = (0, vector_coverage_1.countCovered)(scanned, (t) => runtime.cachedDocVector(t) !== null);
1734
+ const v = (0, vector_coverage_1.vectorCoverage)({ tier: tier.tier, modelTag: runtime.label, total: scanned.length, covered });
1735
+ const line = (0, vector_coverage_1.coverageLine)(v);
1736
+ if (line === '')
1737
+ return null;
1738
+ return v.state === 'inert'
1739
+ ? { name: 'semantic coverage', level: 'WARN', detail: line,
1740
+ fix: 'Re-warm the vectors for the resolved model — `rtm_reindex` carries the only warm pass.' }
1741
+ : { name: 'semantic coverage', level: 'PASS', detail: line };
1742
+ }
1743
+ catch {
1744
+ // A count that could not be taken says so — it must not read as 0 (inert) or as full.
1745
+ return unjudgeable();
1746
+ }
1747
+ }
1692
1748
  function semanticTierVerdict(t) {
1693
1749
  if (t.tier === 'cloud') {
1694
1750
  // @implements A-SPEC-477 — the SOURCE of the consent is shown (env/keychain/file); the value
@@ -606,5 +606,18 @@ function runInit(opts) {
606
606
  (0, roles_readme_1.installRolesReadme)(opts.target);
607
607
  messages.push('Role policy is OPTIONAL and currently off — see .ax/roles/README.md to enable it.');
608
608
  }
609
+ // @implements A-SPEC-686 — the tier, said where the consumer is already reading posture.
610
+ // Probed 2026-09-19: init announced the guardrail mode, the harness, the skills and the role
611
+ // policy, and said nothing about the semantic layer; only `doctor` did, and a consumer who never
612
+ // runs doctor never met it. One-time by the nature of the act, so no marker is needed here.
613
+ // Fail-open: an invitation is not worth failing a wiring over.
614
+ try {
615
+ const { resolveSemanticTier } = require('../semantic/tier');
616
+ const { tierAdviceLines } = require('../semantic/tier-advice');
617
+ for (const line of tierAdviceLines(resolveSemanticTier()))
618
+ messages.push(line);
619
+ }
620
+ catch { /* the wiring is the point; the invitation is not */
621
+ }
609
622
  return { ok: true, exitCode: 0, messages, changes, removals };
610
623
  }
@@ -49,7 +49,18 @@ if (require.main === module) {
49
49
  const { makeHandlers } = require('../mcp/handlers');
50
50
  const { LocalMarkdownRepository } = require('../spec/spec-store');
51
51
  const h = makeHandlers(new LocalMarkdownRepository(path.join(root, '.ax', 'specs')));
52
- void h.rtm_impact({ root, changed: [] }).catch(() => undefined);
52
+ void h.rtm_impact({ root, changed: [] }).then(() => {
53
+ // @implements A-SPEC-683 — the vectors ride the channel that already refreshes the graph.
54
+ // Detached, TTL-gated and fail-soft are properties this child already has; warming inherits
55
+ // all three rather than needing a schedule of its own. Measured 2026-09-19: without this,
56
+ // coverage reached 0 of 602 in twelve days while the graph stayed current.
57
+ const { refreshSemanticVectors } = require('../mcp/handlers/graph-operations');
58
+ const { CpgScanner } = require('../cpg/cpg-scanner');
59
+ const { ScanFileCache } = require('../cpg/scan-cache');
60
+ const crypto = require('node:crypto');
61
+ const dir = path.join(require('node:os').tmpdir(), 'holmes-cpg-cache-' + crypto.createHash('sha256').update(root).digest('hex').slice(0, 16));
62
+ return refreshSemanticVectors(root, process.env, undefined, () => new CpgScanner(undefined, new ScanFileCache(dir)).scan(root, root));
63
+ }).catch(() => undefined);
53
64
  }
54
65
  }
55
66
  catch { /* fail-soft: a failed refresh leaves the old graph, which the advisory tolerates */ }
@@ -175,6 +175,28 @@ if (require.main === module) {
175
175
  }
176
176
  }
177
177
  catch { /* the banner is never a gate */ }
178
+ // @implements A-SPEC-686 — invite ONCE. The measured layer reached consumers only through
179
+ // the README and doctor; a consumer who runs neither never learns it exists. This is an
180
+ // every-session surface, so it says it once per workspace on this machine and then never
181
+ // again — a recurring advertisement for an opt-in feature is the noise class this project
182
+ // removed the same day. Fail-open, exactly like the autonomy banner above it.
183
+ try {
184
+ const { resolveSemanticTier } = require('../semantic/tier');
185
+ const { tierAdviceLines, shouldInvite, markInvited } = require('../semantic/tier-advice');
186
+ const cwd = process.cwd();
187
+ const tier = resolveSemanticTier();
188
+ if (shouldInvite(tier, cwd, (p) => fs.existsSync(p))) {
189
+ const lines = tierAdviceLines(tier);
190
+ if (lines.length > 0) {
191
+ out.hookSpecificOutput.additionalContext += '\n[Holmes-Kit] ' + lines.join('\n');
192
+ markInvited(cwd, (p, data) => {
193
+ fs.mkdirSync(path.dirname(p), { recursive: true });
194
+ fs.writeFileSync(p, data);
195
+ });
196
+ }
197
+ }
198
+ }
199
+ catch { /* the banner is never a gate */ }
178
200
  process.stdout.write(JSON.stringify({ hookSpecificOutput: out.hookSpecificOutput }));
179
201
  if (out.shouldRefresh) {
180
202
  // Detached, unref'd child so the session start does not wait on the network. The refresh
@@ -6,6 +6,9 @@ import { type CiVerdict } from '../project/ci-runs';
6
6
  import { type DistVerdict } from '../project/dist-freshness';
7
7
  import { type AnalysisVerdict } from '../project/analysis-currency';
8
8
  export declare function collectKnownDefects(root: string, now: Date): KnownDefectJudgement | undefined;
9
+ export declare function collectSemanticCoverage(root: string, tier?: {
10
+ tier: string;
11
+ }): string;
9
12
  export declare function collectAnalysisCurrency(root: string, changedSources: number): AnalysisVerdict | undefined;
10
13
  export declare function collectDistFreshness(root: string): DistVerdict | undefined;
11
14
  export declare function collectCiVerdicts(root: string, now?: Date): CiVerdict[];
@@ -52,6 +55,8 @@ export interface StopEvidence {
52
55
  * turn's source edits. Absent when the workspace never analysed anything.
53
56
  */
54
57
  analysis?: AnalysisVerdict;
58
+ /** @implements A-SPEC-683 — the rendered coverage line the refresh child recorded; already judged. */
59
+ semantic?: string;
55
60
  /** Provenance-chain verification result (CLI-supplied). A broken chain blocks the stop. */
56
61
  provenance?: {
57
62
  ok: boolean;