enquire-mcp
oomkapwn/enquire-mcp/CLAUDE.md
This file is read by Claude Code sessions on this repo. It defines the current sprint goal, scope, quality bar, and anti-patterns so any session (continuation or new) shares the same North Star. Current state (as of 2026-09-02): tracked source is v4.0.0-rc.7. PR #565 completed AH-4: configuration-changing build-embeddings and setup now prepare and audit a same-parent replacement generation before promotion, so model, corpus, lease, stale-generation, HNSW, and promotion failures cannot publish a partial replacement over a usable index. The PR…
CLAUDE.md33 starsChanged 4 months ago
- Reads credentials
- Deletes or force-pushes
- Commits and pushes
# Project goal — v4 MCPB Basic candidate + audit-driven hardening line
<!-- current header; `@rc`=4.0.0-rc.7 — pinned to package.json by scripts/check-version-consistency.mjs; advance this marker on every version bump -->
This file is read by Claude Code sessions on this repo. It defines the current sprint goal, scope, quality bar, and anti-patterns so any session (continuation or new) shares the same North Star.
**Current state (as of 2026-09-02):** tracked source is **v4.0.0-rc.7**. PR #565 completed AH-4: configuration-changing `build-embeddings` and `setup` now prepare and audit a same-parent replacement generation before promotion, so model, corpus, lease, stale-generation, HNSW, and promotion failures cannot publish a partial replacement over a usable index. The PR candidate and its squash-main product boundary passed all 13 direct release contexts, the auxiliary operating-system lanes, CodeQL, and Pages. Release work is explicitly deferred: there is no `v4.0.0-rc.7` tag, GitHub Release, npm publication, dist-tag change, or MCP Registry action. The last known public package state remains npm `@latest` **v3.11.6** and npm `@rc` **v4.0.0-rc.3**; annotated `v4.0.0-rc.6` remains tagged but unpublished. A7 remains HOLD until a published `@modelcontextprotocol/node` admits 2.x. Tool/prompt/resource, CLI, privacy, and write-gate behavior remain compatible; the intentional v4 break remains the nominal SDK type returned by `buildMcpServer()`, alongside the documented persistence-path and HNSW-generation changes. The bounded T-Search protocol remains retained without the 36B checkpoint or server-side LLM orchestration: **47 tools · 19 prompts · 2272 tests · 11 languages.**
**Active work (2026-09-02):** the read-only sibling sweep of AH-4 on the exact PR #565 product boundary is **complete and returned ALLOW** — no path can publish a partial embedding generation over a usable one. That sweep also re-audited the whole 40-commit rc.7 line and found two integrity regressions, now in flight as one bounded product PR: embed-vector retention was gated on `schema_version >= 2` where schema 4 exists to invalidate FP32-derived vectors, and the rename-rollback reverse still passed `overwrite: true` and so destroyed a concurrent occupant of the vacated source path. After that PR lands, re-triage permits exactly one bounded card; AH-5 truthful erasure is the leading candidate, followed by AH-6 bounded `resources/list`. The mechanical release-mutation campaign remains frozen pending a separate value decision, and npm/tag/GitHub Release/MCP Registry work remains deferred until the maintainer explicitly reopens distribution.
- **v4.0.0-rc.7 full-audit safety closure plus AH-4 staged replacement; rc.6 remains tagged but unpublished:** ambiguous and late-occupant rename rollback fails closed with append-only recovery; query/eval recognize shared FTS aliases by physical identity; tag/link evidence is complete but bounded; graph claims match the equal-RRF candidate in-degree tie-break; HNSW cleanup removes same-or-older invalidated DB authorities under the publisher lease, including no-receipt failures. Configuration-changing embedding builds now validate a complete private generation before atomic promotion and preserve the prior usable generation on pre-promotion failure. **2228 → 2272 source tests.**
- **v4.0.0-rc.6 A6 recovery, tagged but unpublished:** version surfaces and public acquisition strings name 4.0.0-rc.6. Release handoff copies the body to `release-notes.md` instead of a same-file `mv`; the annotated tag exists, but OIDC publication failed with `ENEEDAUTH`, so npm `@rc` and GitHub Releases remain at rc.3. A7 remains HOLD on unpublished typescript-sdk#2574. **2228 → 2228 source tests.**
- **v4.0.0-rc.5 tagged but unpublished predecessor:** squash-main `5438622`; annotated tag exists; GitHub Release and npm `@rc` were not published. **2228 → 2228 source tests.**
- **v4.0.0-rc.4 unpublished full-audit source predecessor:** read-only verification, three consequence-separated publication jobs, exact CI-built artifact handoff, reproducible Docker, and the audited runtime/persistence closures were staged but never tagged. **1807 → 2228 source tests.**
- **v4.0.0-rc.3 published predecessor** — shipped the post-rc.2 hardening line through the tag-push-only protected controller; exact npm/GitHub release source `3edcba4c6b1de813686601c36c1c2b487e20e382`. **1807 → 1807 source tests.**
- **v4.0.0-rc.2 published Basic MCPB predecessor** — packages the bounded 13-tool read-only profile, adds exact-byte three-OS consumer evidence, transparency sidecars, and a fail-closed twelfth release context while preserving the full edition defaults. **1807 → 1807 source tests.**
- **v4.0.0-rc.1 shipped SDK-v2 foundation** — exact v2 packages, dual-era transports, write-safe shutdown, official-client conformance, and packed-consumer matrices; exact tag/npm `gitHead` `4af28086d30ab08fefce49d94107c18c14eac18f`. **1795 → 1807 source tests.**
**Active goal (post-2026-07-15 Codex external audit):** shift from the audit-hardening treadmill toward **activation + user-visible product improvements + user learning**; retrieval benchmark and evaluation work is outside the active improvement lane and requires a new explicit maintainer instruction, while hardening remains **risk-triggered** (a change touching a parser / FS-write / transport / persistence-format / model-network / supply-chain surface). The audit graded v3.11.6-rc.2 at 4.0 technical, 0 CRIT/HIGH/MED, 5 LOW.
**Open deferrals (v3.11.x+):** structure-accessor phases 2-7 (now **risk-triggered**, not scheduled — ship a phase only when a concrete duplicate walker/defect triggers it), namespaced multi-vault (**RFC-gated** — the "no multi-vault" non-goal is under reversal), the maintainer-cancelled LongMemEval headline work (**outside the active lane; explicit reauthorization required**), embed-db migrations, HNSW filter-during-search.
**External-audit gate (v3.6.1 rule — STANDING):** every pre-stable `@rc → @latest` promotion needs ≥2 independent external auditors with DIFFERENT methodologies (change-driven + state-driven + adversarial/behavioral), documented under `docs/audits/`. Substantively met across the v3.9→v3.11 line. _(The cache-miss zero-outbound e2e probe the 2026-07-15 audit left INCONCLUSIVE is now CLOSED in v3.11.6-rc.3 — `tests/serve-offline-network.test.ts` + `scripts/probe-offline-network.mjs` verify it empirically; the remaining promotion gate is a fresh external pass on the promoted commit.)_
**External audit blocker (CLAUDE.md v3.6.1 rule, ≥2 independent external auditors with DIFFERENT methodologies) — re-evaluated 2026-05-25 against the corrected audit doc.** v3.8.0 was promoted on internal confidence alone; v3.8.1 retracted the incorrectly-attributed Cursor audit (overclaim #11 — wrong project). On 2026-05-25 the same auditor delivered the CORRECTED enquire-mcp audit on commit `7a9fdbd` / `v3.8.0-rc.15` (`docs/audits/v3.8.0-rc.15-external-2026-05-25.md`): **verdict 4.85/5, ship-blockers: none**. All 5 actionable findings (M-REG-1, L-HYB-1, L-OIA-1, INFO-1, INFO-3) were already closed by rc.18→v3.8.5. INFO-2 (R-10 residual at >66% excluded) closes in v3.9.0-rc.3. **The v3.8.0 stable promotion was retroactively justified by this audit** — but the v3.6.1 rule requires ≥2 INDEPENDENT external auditors with DIFFERENT methodologies. Counting Mavis (v3.6.0, 4.9/5) + Anonymous (v3.6.0, CRITICAL) + Round-7 (v3.6.2) + Round-12/13/14/15/16 + 2026-05-25 corrected pass: **5+ independent external audits across v3.6.x→v3.8.x cascade**, methodologies span change-driven (Mavis-style) + state-driven (Round-22 / 2026-05-25 corrected) + adversarial-CVE (Round-7). **Blocker met.** _(Historical — this paragraph records the v3.8.0-era resolution of the ≥2-auditor gate; the STANDING rule and the current promotion gate are stated in the header above. v3.9.0, v3.10.0, and v3.11.5 have since all promoted to `@latest`.)_
---
## Goal (historical — v3.6.0 sprint, shipped 2026-05-15)
Released **enquire-mcp v3.6.0** per the planned RC sequence. This section is preserved as historical context — the sprint is closed.
Directive: **"Максимальное качество и уверенный топ-1 из всех Obsidian MCP по технологии и надёжности."**
## Scope (closed) — v3.6.0 RC sequence + promotion
- **v3.6.0-rc.1**: `tools.ts` (4252 lines) → 5 domain modules in `src/tools/` + barrel
- **v3.6.0-rc.2**: `index.ts` (3665 lines) → `src/cli.ts` + `src/server.ts` + `src/prompts.ts` + `src/tool-registry.ts` + `src/tool-manifest.ts`
- **v3.6.0-rc.3**: Full TSDoc (`@param` / `@returns` / `@example`) on 44 tools + 19 prompts + 20 `src/` modules (~1300+ lines of doc-comments)
- **v3.6.0-rc.4**: TypeDoc + GH Pages auto-generated API reference + Public benchmarks (`docs/benchmarks.md`, MRR/NDCG@10/Recall@K vs 3 main competitors)
- **v3.6.0 (stable)**: promote rc.4 → npm `latest`, GH release marked Latest
## Quality bar — required on every release (no exceptions)
1. All tests pass (current count: 818+ at v3.7.17; tests grow with each audit cycle — see CHANGELOG)
2. Lint clean (biome 0 warnings/errors)
3. `tsc` strict + `noUncheckedIndexedAccess` clean
4. Coverage thresholds met (lines ≥86, statements ≥82, functions ≥75, branches ≥74)
5. `scripts/check-changelog-coverage.mjs` passes (CHANGELOG claims = reality within 0.5pp)
6. `scripts/smoke.mjs` synthetic vault scan passes
7. All 13 required CI gates green on PR (lint, test×2, smoke, audit, coverage, version-consistency, docs, oia, protocol-conformance, package-consumer, mcpb-basic, docker — `docs` added v3.7.10, `oia` promoted from advisory v3.8.0-rc.6; the v4 gates use the official client, packed artifacts, and container introspection)
8. Daily-check report after merge: 0 regressions, 0 new CodeQL / Dependabot alerts
9. CHANGELOG entry with TL;DR blockquote + method note
10. All docs-consistency invariants stay green (extend as new surfaces appear)
## Audit checkpoint — after every RC
- **Self-audit** via root-cause-sweep methodology (memory: `method_audit_root_cause_sweep.md`): check the drift class hasn't returned from previous cycles
- **Any external audit report** (Mavis / MiniMax / other) — pause until processed; either instance-fix OR class-fix
- **All rejections of auditor recommendations** must be documented inline in the CHANGELOG with reasoning (see v3.5.14 L-2 for the empirical-rejection pattern)
- **New findings in the current RC** → fix BEFORE the next RC, do not carry into the final v3.6.0
## Exit criteria — v3.6.0 is "closed" when
1. npm `latest = 3.6.0` (not RC)
2. GH release v3.6.0 marked Latest
3. All 4 RC merged + tagged + on npm under `rc` dist-tag
4. `docs/api-reference/` published to GH Pages (auto-generated TypeDoc)
5. `docs/benchmarks.md` published with real MRR/NDCG/Recall numbers on a BEIR/TREC subset + competitor comparison
6. CHANGELOG v3.6.0 entry with major summary: before/after, measurable improvements (coverage delta, lines-per-file delta, build time delta)
7. Twitter thread (@OomkaBear) with top-3 differentiators
8. Daily-check shows 0 regressions + clean security for 7 days after final
9. If external audit on v3.6.0 happens — must return ≥ 4.8 / 5.0
## Non-goals (NOT doing in this sprint)
- Multi-vault support (out of scope per `docs/COMPARISON.md` positioning)
- OAuth (bearer-only is a deliberate security-positive design choice)
- Live Obsidian integration via Local REST API (different positioning vs `cyanheads/obsidian-mcp-server`)
- Formula evaluator for Bases DSL (deferred to v3.7+)
- Any API breaking changes (this is a MINOR bump; major surface stays stable per STABILITY.md)
## Anti-patterns to avoid
- **Big-bang refactor** — always phased via RCs
- **Copy-paste coverage stats** from sub-agent output (lesson from v3.5.12)
- **Optional dep removal without empirical test** (lesson from v3.5.14 L-2)
- **Hardcoded counts in docs without an invariant** (rule since v3.5.9)
- **Dismissing an auditor without CHANGELOG reasoning** (rule since v3.5.14)
- **Compressing CHANGELOG for aesthetics** — audit trail trumps style
- **Merge without green daily-check on main** afterward
- **Claim "all N callsites" before grep-verifying** — overclaim class repeated 3× in K-1 saga (v3.6.1, v3.6.2, v3.6.4). Solution: structural enforcement (`tests/k1-class-invariant.test.ts`) > CHANGELOG promises. **Rule since v3.6.4**: any "N of N fixed" claim requires a test gate or it doesn't ship.
- **Reactive same-day patching** — 5 releases (3.6.0→3.6.4) on 2026-05-15 happened because each post-ship audit found another instance. Lesson: **audit BEFORE ship, not after**. After a retroactive correction patch (like v3.6.4), allow 24h of dogfood on main before next patch — surfaces regressions and breaks the "instance-spotted → instance-patched" cycle. **Rule since v3.6.4**: after a CRITICAL or retroactive-correction patch, the next patch waits ≥24h unless a new CRITICAL is found in production.
- **Invariant test without negative-control** — a test that ALWAYS passes proves nothing. Every new invariant test must have a sibling test that fails when the invariant is violated (see `tests/peek-meta.test.ts` "NEGATIVE control" pattern from v3.6.4). **Rule since v3.6.4**.
- **TSDoc header drifts from function body** — every overclaim instance since v3.6.1 (7 documented) has the same shape: code changed inside a function, but the function-level TSDoc / file-header / block-comment describing the behavior wasn't updated in the same commit. TypeDoc + IDE hover then publish the stale (lying) description. **Rule since v3.7.15**: every fix that changes function internals MUST include the matching TSDoc header update in the same commit; reviewers MUST diff the header alongside the body. Examples of the drift: v3.7.14 F1 (renameNote body fixed, header lied), v3.7.14 F2 inside the SAME patch (renameFile body fixed by F2, header still said "Atomic via fs.rename").
- **Single class-sweep is not enough — same-release recursion happens** — v3.7.14 F1 fixed overclaim #6, and v3.7.14 F2 SHIPPED overclaim #7 inside the very same patch. The author of an audit-driven fix sees the immediate problem but doesn't apply the lesson to OTHER changes in the same diff. **Rule since v3.7.15**: after every audit-driven release that closes a "class" finding (overclaim, TSDoc drift, TOCTOU, etc.), run a post-merge re-sweep specifically scanning that patch's own diff for fresh instances of the same class. The recursion rate observed across v3.6.x-v3.7.x is high enough that this is a required step, not optional.
- **Tag the SQUASH-MERGE commit on main, not the feature-branch HEAD** — v3.7.14 was tagged against the pre-merge branch SHA (orphan, not on main), and `.github/workflows/release.yml`'s "Assert tag is on main" guard correctly refused to publish (overclaim #8). The CHANGELOG implied the ship completed before it actually did. **Rule since v3.7.15**: the post-merge release procedure is *always*: `git checkout main` → `git pull origin main` → `git log -1 --oneline` (capture the squash-merge SHA) → `git tag -a vX.Y.Z <that-SHA> -m vX.Y.Z` → `git push origin vX.Y.Z`. Lightweight tags are rejected by the release preflight. Never tag from a feature branch — the squash-merge produces a NEW commit whose SHA differs from the branch HEAD.
- **Internal change-driven sweeps miss state-driven failure modes — run OIA before claiming "no open findings"** — every external auditor since v3.6.0 has found stale fragments that internal class-sweeps missed: README badges, CLAUDE.md titles, file-header comments, stale CLI references in docs, stale npm-script references in script docstrings. Root cause: my methodology is CHANGE-DRIVEN (look at what changed, fix the class, verify nearby) while external audits are STATE-DRIVEN (read every file as it exists, verify each claim against reality). These find non-overlapping failure modes. **Rule since v3.7.17**: run `npm run check:oia` (`scripts/oia-walk.mjs`) before claiming "no open audit items" in any release. The script automates the state-driven walks catalogued in its own header; early additions covered stale currency claims, workflow/CLI/script existence, inline defaults and coverage comments, scope completeness, and GitHub Actions SHA pinning. Default mode exits 1 on any finding; `--allow` overrides documented architectural deferrals but cannot suppress Vitest focus-control or scanner-error findings. **Round-19 (v3.7.17) shipped the rule + the script after the 5th external audit caught 4 cheap stale fragments my v3.7.16 pre-merge RCA missed.**
- **"Drift" findings demand a full-surface sweep BEFORE per-instance fix** — when an external audit reports a drift finding (CLI help text, inline comment, doc fragment), the natural reflex is to fix the reported instance. **Don't.** Drift findings are almost never isolated — they're symptoms of a shared surface where the structural defense is incomplete. Pre-rc.11 the N-5 (`--watch` help drift) finding triggered TWO per-instance fixes (rc.6 + rc.7) that left the same flag still drifting, plus left 9 more flags in the same class unidentified. **Rule since v3.8.0-rc.11**: before fixing the reported instance, run a Python/grep sweep across the entire surface type the finding implicates (all `.option()` calls if it's CLI text; all inline `// current X%` comments if it's coverage; all README badges if it's currency). Let the sweep count instances; if N > 1, write a structural defense (test invariant or shared module) that makes future drift impossible, then fix all instances in ONE PR. The cost of the sweep is ~30 min; the cost of N reactive patches is 3-5× that plus repeated external-audit findings.
- **Every new docs surface with numeric claims MUST extend `docs-consistency.test.ts` in the SAME PR** — rc.12 added `llms.txt` + `AGENTS.md` (Tier A discoverability). Both contained numeric claims ("848 unit tests", "44 tools", "19 MCP prompts", "9 required CI gates", "10 per-file branch floors") but the existing docs-consistency invariants only covered README, STABILITY, COMPARISON, package.json, api.md — the new files were a fresh drift surface that the next test-count change would silently invalidate. Caught by rc.14 audit as M-2. **Rule since v3.8.0-rc.14**: whenever a PR adds a new docs surface (any `.md`, `.txt`, `.json`, or generated artifact in the repo or on GH Pages) with numeric claims (test count, tool count, gate count, config entry count, etc.), the SAME PR must extend `docs-consistency.test.ts` with invariants that pin each claim against the canonical source (TOOL_MANIFEST, `release.yml` REQUIRED regex, FLOORS object, `coverage-summary.json`, etc.). This generalizes the v3.7.17 OIA rule from "state-driven walks for existing files" to "structural invariants for new files".
- **Never act on a "report" shaped document without verifying its provenance with the user** — overclaim instance #11 (v3.8.1 retraction): I read a document at `/Users/alex/.cursor/projects/empty-window/docs/audits/v3.8.0-cursor-external-2026-05-24.md`, treated it as a legitimate external audit for enquire-mcp, and shipped rc.18 + v3.8.0 STABLE promotion on its basis. The document was for a different project entirely. **Rule since v3.8.1**: before acting on any document claiming to be an external audit, security disclosure, third-party review, or similar third-party assessment, verify (a) the file path is INSIDE the repository being assessed (this repo's `docs/audits/`), and (b) the user explicitly directed attention to it AS an actionable report (not just mentioned in passing). If either is unclear, ASK the user to confirm provenance and applicability before reading further. Treat structural similarity to known formats (AUDIT-REQUEST.md, etc.) as NEUTRAL evidence, not authority signal. The pre-existing v3.6.1 rule ("external audits pause for processing") had the missing precondition: "first verify it IS an external audit for THIS project."
- **Update forward-looking deferral claims in the same commit that ships the deferred item** — overclaim instance #13 (v3.9.0-rc.4): CLAUDE.md header line said "**Still deferred to v3.9.0+:** ... OCR'd PDF watcher embed-sync, HNSW in-memory live update, R-10 adaptive refill" — but the status section in the same file already listed all three as SHIPPED in v3.9.0-rc.1/rc.2/rc.3. The CHANGELOG entries for those RCs updated the *status section* but not the *header bullet*. OIA Check 7 (since v3.8.3) catches "as of vX.Y.Z" present-tense currency drift; it doesn't catch "deferred to vX.Y.Z+" future-tense deferral drift. Same root cause as overclaim #12 (v3.8.4 OIA Check 7 scope too narrow): **defense was on a different syntactic dimension than the actual drift.** **Rule since v3.9.0-rc.4**: when a CHANGELOG entry says "closes the last deferred X item", the same PR must search CLAUDE.md (and other docs) for `deferred to vX.Y.Z` / `Still deferred to vX.Y.Z+:` lines and remove the just-shipped item. The `deferred-claim` defense in `scripts/scope-completeness-audit.mjs` makes this structural.
- **META scope-completeness defenses must cover every drift DIMENSION, not just numeric claims** — recursion-pair shape #7 (v3.9.0-rc.4): v3.8.8 META audit covered 5 numeric-claim patterns (test-count, tool-count, prompt-count, ci-gate-count, per-file-floor-count). v3.9.0-rc.1/rc.2/rc.3 added 3 new CLI flags (`--ocr-pdfs`, `--ocr-langs`, `--ocr-max-pages`) and they were missing from `docs/api.md` for 3 RCs because **the META defense covered the wrong dimension**. Same shape as v3.8.3 → v3.8.4 (OIA Check 7 scope was too narrow on FILES); now it recurs at the DIMENSION level. **Rule since v3.9.0-rc.4**: whenever a new structural defense is added (`docs-consistency.test.ts`, OIA check, scope-completeness defense), explicitly enumerate the drift dimensions it covers vs leaves uncovered, and add the uncovered dimensions to a "deferred defense" list in the same PR. Currently covered dimensions: numeric-claim, deferred-claim, cli-flag-coverage, currency-version (OIA Check 7). Uncovered (TODO): feature-mention drift (new feature ships but README hero/featurelist doesn't mention it), behavior-claim drift (TSDoc claims X but impl does Y — partly covered by tests, but not as a structural defense), error-message drift, **claimed-guarantee-vs-code-guard drift (see next rule)**.
- **Never claim an ENFORCED guarantee the code doesn't actually enforce** — overclaim instances #15 (SLSA-3) + #16 (OCR offline) (v3.9.0-rc.7 comprehensive audit). Two findings of one shape: (#15) the README badge linked to slsa.dev **Build L3** and 8+ surfaces claimed "SLSA-3", but `release.yml` only runs `npm publish --provenance` = **SLSA Build L2** (hosted builder + signed provenance; L3 needs an isolated, non-falsifiable builder via `slsa-framework/slsa-github-generator`). (#16) SECURITY.md claimed "zero outbound network calls in serve mode" + `ocr.ts` TSDoc claimed a pre-flight "throws if language not installed" check + CLI help said "runtime CDN download is blocked", but `extractPdfWithOcr` only emits a stderr warning then calls `createWorker` which silently CDN-fetches `<lang>.traineddata`; the `install-ocr-lang` subcommand referenced in 4 files never existed. **Root cause**: the project's invariant apparatus checks NUMERIC + doc-consistency drift but has NO defense for "we promise enforcement X; does a code path actually enforce X?". This is the most dangerous overclaim class because it's externally verifiable (an auditor checks the badge → the spec → the workflow; a privacy-conscious user runs OCR offline and watches it phone home) AND it directly attacks the project's rigor/privacy brand. **Rule since v3.9.0-rc.7**: any claim of an *enforced* guarantee (security level, "blocked", "zero X", "fails closed", "validated before Y") MUST point to the specific code guard that enforces it; if the guard doesn't exist, either build it or downgrade the claim to describe actual behavior. The v3.9.0-rc.5 "OCR docs unification" failed this — it unified the *prose* about a procedure while the *enforcement* the prose implied was never built (papered the wrong layer). Candidate structural defense (deferred): an OIA check that greps SECURITY.md / TSDoc for enforcement verbs ("blocked", "zero outbound", "fails closed", "throws if") and flags each for manual code-guard verification.
- **A heuristic SECURITY detector must analyze the same semantic space its sink consumes — and you must adversarially FUZZ your own fix** — overclaim instance #17 (v3.9.0-rc.24). rc.21 hardened `obsidian_open_questions` against overlapping-alternation ReDoS, and its `isCatastrophicRegex`/`alternationBodyAmbiguous` TSDoc asserted the analysis "never under-flags a real first-char overlap" (a soundness GUARANTEE — same claimed-guarantee-vs-reality class as #15/#16). But the detector compared **surface syntax**, not the **character actually matched at the sink**: (a) `(a|A)+` slipped because the tool compiles `new RegExp(pattern, "i")` (so `a`/`A` overlap) but `leadingAtomToken` compared case-SENSITIVELY; (b) `(\x61|a)+` / `(\u{61}|a)+` (= `(a|a)+`) slipped because the helper returned the raw byte after `\` instead of the decoded char. Both reproduced ~16s V8 hangs at ≤12 chars on bearer-auth `serve-http` — i.e. **the rc.21 security fix shipped two fresh instances of the very ReDoS class it closed** (the recursion this project most fears, now manifest in a SECURITY detector). Root cause: the detector reasoned over the *literal pattern text* while the dangerous backtracking happens over the *set of inputs the compiled regex matches* (case-folded, escapes resolved) — two different semantic spaces. **Rule since v3.9.0-rc.24**: (1) any heuristic that guards a sink (regex compile, SQL, shell, path) MUST normalize its input into the SAME space the sink interprets — fold case if the sink is case-insensitive, decode escapes if the sink decodes them, resolve aliases — before comparing; when normalization is uncertain, err toward the *unsafe* classification (over-flag), never toward "looks safe." (2) The standard CI gates CANNOT catch a detector false-negative (a missed bypass passes every test that doesn't already contain it), so every security-detector change REQUIRES an explicit adversarial self-fuzz pass: enumerate the equivalence classes of the threat (here: case variants, every escape syntax that aliases a literal, nullable/overlap shapes) and assert each is caught, with a disjoint POSITIVE control proving the normalizer doesn't over-reject. This generalizes "audit before ship" + "post-merge re-sweep" specifically to detectors: fuzz the detector, don't just unit-test the happy path. **Strengthened v3.9.0-rc.25 (overclaim #18, the 3rd recursion of this exact class)**: a fresh audit reproduced a CRITICAL the rc.24 fix STILL missed (`(a?b|b)+$` optional-leading-atom; `(a?){25}` nullable body; `(a{2,5})+` variable body) — because (1) my rc.24 "enumerate the equivalence classes" pass is itself incomplete (I cannot enumerate every shape of an undecidable property), and (2) my rc.24 self-fuzz was a ONE-OFF manual run, not a CI gate, so it couldn't catch a regression or a newly-introduced gap. **The rule that actually ends the treadmill**: for a recurring detector class, ship a GENERATIVE fuzz as a PERMANENT CI test (`tests/redos-fuzz.test.ts`) — generate a deterministic random corpus, and for every input the detector classifies SAFE, run the real sink (a timed `exec` in a worker, with a re-confirm to avoid load-flake) and fail if it actually misbehaves. This converts "did I think of every shape?" (undecidable, recursion-prone) into "does any SAFE-classified input actually hang?" (empirical, self-checking). Hand-enumerated shape lists are the floor; the generative fuzz is the ceiling. Also: when the sound general fix over-flags some safe inputs (here `(a?b)+`/`(\w+\s)+` — variable but anchored), DOCUMENT the over-flag in the detector TSDoc and accept it (a security guard's false positive < a hung event loop) rather than chasing EDA-precise detection (hundreds of lines, its own bug surface).
- **The internal apparatus is drift/claim-driven and STRUCTURALLY BLIND to behavioral/threat classes — when an external lens finds a behavioral bug, internalize THAT LENS as an inventory invariant, don't just patch the instance** — meta-audit conclusion (v3.9.0-rc.36, a 3-sub-agent in-house audit cross-checked against an independent grep sweep). Categorizing all 12 OIA checks + the invariant suite showed **~85% are claim/drift** ("does a doc CLAIM match reality?": stale versions, count mismatches, SLSA-level strings, coverage-comment drift) and only K-1 (data-corruption), K-3 (MCP-contract), and OIA 4d/4e/9 (guard-presence greps + SHA-pin) touch behavior — each an incident-specific point defense, never a *category* the apparatus patrols. Consequence: **every behavioral defect of the rc.25→rc.35 line came from an EXTERNAL privacy/STRIDE auditor, never from a home-grown gate** — P-2 (HNSW-sidecar erasure), P-3 (path-leak in errors), R-5/AS#5 (unbounded-graph DoS), H-3 (fail-soft-where-fail-closed), M-9 (unpinned `run:` download). The drift gates can't enter those categories because they read *doc/config text*, not *control flow / on-disk state / matched-input space*. **Rule since v3.9.0-rc.36**: when an external auditor (or your own threat-model pass) finds a behavioral bug, the fix is NOT complete until the *lens that found it* is encoded as a permanent **inventory-based invariant** — enumerate the artifacts/sites of that class and assert the property empirically, so the NEXT instance fails CI. rc.36 shipped three: `tests/erasure-invariant.test.ts` (every on-disk artifact's eraser references every sidecar suffix — writers ⊆ erasers, closes the P-2 class), `tests/resource-bound-invariant.test.ts` (every always-on whole-vault scanner must be CAP-or-EXEMPT classified — a new unclassified scanner fails CI, ends the R-5→AS#5→F-4/F-5 recursion), OIA Check 12b (orphan-`dist` files — closes the L-3 stale-build class at its root). This is the same transform the rc.25 ReDoS generative fuzz made: convert "did we remember to bound/erase/sanitize X?" (undecidable, recursion-prone, auditor-dependent) into "does the inventory have an entry and does its property hold?" (a self-checking gate). **Behavioral dimensions — status:** supply-chain `run:`-download pinning (M-9) → **CLOSED v3.10.0-rc.26**: OIA Check 9b now requires any tag-pinned release-archive `run:` download to ALSO be SHA256-verified (content-pin); the one such download (`mcp-publisher` in release.yml) is hash-pinned + `sha256sum -c` fail-closed, detection-power inject/revert-verified. Paired-sink PDF/OCR error parity (H-3) → **CLOSED v3.9.0-rc.33**: `extractPdfText` throws on an inverted `pageRange`, parity with OCR's `resolveOcrPageRange`. **Still uncovered** (deferred, named so they're not silently skipped): a generalized enforcement-verb→code-guard taxonomy (beyond the SLSA/OCR-specific 4d/4e), and the `block`-granularity FTS5↔embed chunk-INDEX divergence for frontmatter'd notes (documented + accepted in rc.17 — prefer the default `note` granularity; not a bug).
## Method note
Apply **root-cause sweep methodology** consistently: every bug → identify the class → ship class fix + per-instance backfill in one PR. If during this sprint the methodology spots 2+ instances of the same class, escalate to a mandatory class fix BEFORE v3.6.0 stable — do not defer.
**"Каждый этап и элемент системы проаудирован, все ошибки устранены"** = after v3.6.0 stable there is zero open issue, zero CI failure, zero docs drift, zero coverage gap below threshold, zero un-rejected (and undocumented) auditor recommendation.
---
## Current phase status
- **v3.12.0-rc.15 (candidate; GitHub Pages product front door):** replaces the generated-doc root with a dependency-free acquisition landing for humans, AI agents, and search crawlers while retaining the full TypeDoc tree at `/api/` and every legacy deep symbol URL. A deterministic composite builder validates the artifact, and both PR CI and main deployment use that same output; **1716 → 1720 source tests.**
- **v3.12.0-rc.16 (candidate; TOP-1 repository conversion rebuild):** reframes the product around portable agent memory + local document intelligence, restores a dated evidence-bound ✓/✕ matrix against pinned competitor surfaces, extends the Pages front door with the same decisive comparison, and replaces the social card with a modern vault → local intelligence → agents composition; **1720 → 1720 source tests.**
- **v3.12.0-rc.17 (candidate; evidence-grade LongMemEval-S pre-run):** corrects the benchmark from one index per question to one global index with per-question folder scope, removes answer-id leakage from paths/headings, pins the official cleaned dataset by schema + SHA-256, normalizes session mtimes to dataset-relative age for reproducible recency comparison, and writes raw per-query/provenance/hardware/timing/index-size evidence; **1720 → 1720 source tests.**
- **v3.12.0-rc.18 (candidate; FTS5 scoped-index class fix):** the official full run exposed an O(N²) fresh-build path (`DELETE` by UNINDEXED `rel_path` before every insert). FTS schema v6 adds one indexed, relevance-neutral encoded path token per chunk for exact replacement/removal and prefix-scoped folders; the raw path remains a residual check, `doctor` enforces the schema, and a class invariant forbids the bare delete from returning; **1720 → 1720 source tests.**
- **v3.12.0-rc.19 (candidate; explicit q8 embedding inference contract):** the exact clean rc.18 dense pre-run exposed that transformers.js v4 selected the 470 MB FP32 graph for a custom model id despite the public ~118 MB q8 contract. Every embedder now passes `dtype:"q8"`; doctor requires `model_quantized.onnx`; embed schema v4 rebuilds old vectors; and HNSW signatures include that schema so stale FP32-derived sidecars cannot load; **1720 → 1720 source tests.**
- **v3.12.0-rc.20 (published earlier candidate; evidence-complete benchmark contract):** extracts one shared fail-soft/strict embedding synchronizer, audits declared versus physical rows including malformed vectors/indices/kinds, pins the official LongMemEval cohort and completed query/signal counts, and requires exact release/source/`dist`/model-byte provenance before `publishable:true`; **1720 → 1740 source tests.**
- **v3.12.0-rc.21 (published earlier candidate; bounded maintenance roll-up):** advances `@biomejs/biome` 2.5.4→2.5.6, aligns the Biome schema, advances `@types/node` 26.1.1→26.1.2, and pins every workflow checkout use to the official signed v7.0.1 commit. Runtime dependencies and behavior are unchanged; validation is remote-CI-only; **1740 → 1740 source tests.**
- **v3.12.0-rc.22 (published earlier candidate; literal Node-floor CI):** keeps the protected `test (22)` context name but replaces floating Node 22 with exact 22.13.0, asserts that runtime equals `engines.node`, enforces strict engine admission, performs a native SQLite/FTS5 probe, and pins the existing MCP/FTS smoke to the same floor. A parsed-workflow invariant rejects floating/mismatched floor configurations, missing step bindings and fail-open required jobs without adding a source-test declaration. Runtime dependencies and behavior are unchanged; validation is remote-CI-only; **1740 → 1740 source tests.**
- **v3.12.0-rc.23 (published earlier candidate; blocking Windows path safety):** adds a pinned non-vacuous `windows-2025` hostile-filesystem lane as a fail-closed prerequisite of `smoke`; rejects Windows reserved/ADS/trailing-alias paths before I/O; normalizes public, persisted and watcher relative identities to `/`; rechecks persistent-cache privacy on canonical physical identity; and canonicalizes case-variant rename sources before link planning/rollback. Real junction and case-folded privacy controls run only remotely; generic cmd.exe, watcher-event, network-filesystem and concurrent-junction claims are explicitly excluded; **1740 → 1744 source tests.**
- **v3.12.0-rc.24 (published predecessor; Windows watcher convergence proof):** extends the blocking real-Windows lane with one-mutation/passive-observer contracts for ordinary rename, case-only rename, same-path atomic replacement and a moved-in directory containing a real external junction. Every sampled observation in the stable window must satisfy the final-state predicate, which must also survive watcher close/drain: FTS marker/path and EmbedDb path/preview/audit state remain correct, attached HNSW labels plus path/preview metadata equal EmbedDb, and the junction sentinel never reaches the FTS ingestion boundary. Runtime implementation logic and dependencies are unchanged apart from release version metadata; **1744 → 1748 source tests.**
- **v3.12.0-rc.25 (published predecessor; watcher startup activation barrier):** freezes watched EmbedDb capability, arms a private process-restart interlock before chokidar, closes the ignoreInitial gap with post-ready FTS and exact path/mtime EmbedDb audits, then derives bounded multi-generation replay from final disk state after all late attachments. Exact stale DB keys purge independently of physical aliases; HNSW falls back rather than publishing an unattached static graph; close drains and propagates activation failure; strict recovery removes derived artifacts before the guard. Fail-soft per-sink staging remained S-8d, mtime-preserving replacements were outside the drift proof and hardlinks remain S-8e. Deterministic real-FTS5/EmbedDb/fake-HNSW lifecycle, guard and TypeScript-AST tests cover the contract without chokidar timing; **1748 → 1773 source tests.**
- **v3.12.0-rc.26 (candidate; one-generation watcher staging):** captures and revalidates one ordinary path generation, stages Markdown/PDF lexical and semantic work from one read, retries one drift once and commits FTS5 → EmbedDb → HNSW synchronously. Fatal staging/embedding failures retain the prior live generation; guarded activation rejects. Optional OCR remains fail-soft and clears stale semantic rows when the new PDF has no extractable text. Live mutation uncertainty quarantines semantic routing until restart; HNSW-only uncertainty falls back to EmbedDb and suppresses persistence. Search rechecks health after its final awaited boundary. Cross-store crash rollback, query-level hybrid snapshots, deliberate same-account path ABA and physical aliases are explicit residual boundaries; **1773 → 1776 source tests.**
- **v3.12.0-rc.27 (candidate; physical-alias watcher convergence):** production listens/captures before an identity-only seed, then independently stages and revalidates every discovered current hardlink path under one physical-identity plus exact-path lock set. Every missing origin performs a fresh bounded inventory; seeded membership only narrows the commit group. First-observed unlink refreshes survivors without reprocessing unrelated single-link notes; pathname recreation, sibling replacement, post-inventory alias creation and pre-lock membership drift replan from final state; Windows stale casing purges before canonical upsert; transient admission I/O preserves prior rows. Startup is one-shot, pending readiness is rejected by close, and the lifetime native-error listener is removed with the watcher. Unavailable identity takes a serialized, 50,000-path-count-capped all-visible/remembered-path lane; distinct case/NFC/NFD files never fold, leaf symlinks and escaping intermediates remain excluded. Network/virtual filesystems without truthful `nlink` and alias-consistent IDs, writes visible only through unadmitted aliases, fd-bound adversarial ABA, durable cross-store rollback and query snapshots remain explicit boundaries; **1776 → 1795 source tests.**
- **v3.12.0-rc.28 (candidate; exact-Origin HTTP admission):** validates configured CORS origins before resource preparation, rejects wildcard/opaque/malformed/non-origin forms, accepts native clients that omit `Origin`, and returns `403` for every present unlisted value before OPTIONS, health, routing, auth, rate, body, session or MCP work. CORS is now a post-admission browser grant sourced from the exact server allowlist. The final MCP 2026-07-28 SDK/protocol migration remains a separate semver-major RFC; **1795 → 1795 source tests.**
- **v3.12.0-rc.29 (published predecessor; competitive positioning + privacy integrity):** turns the 129-project scan into a precise freshness/citation/read-only acquisition wedge; separates Markdown/PDF hybrid retrieval from Canvas/Dataview/Base structured tools; publishes the MCP-client privacy boundary; refreshes site/JSON-LD/AI-reader/GitHub discovery surfaces; and adds a remote-only social-preview artifact loop with source-bound proof counts. Runtime/API/dependencies are unchanged; **1795 → 1795 source tests.**
- **v3.12.0-rc.31 (candidate; SDK registration seam):** replaces the mutable SDK-instance `registerTool` override with an SDK-agnostic composition facade; preserves allowlist-first/denylist-second filtering for built-in and late programmatic registrations; invokes lifecycle calls against the original raw server without overwriting it during adapter construction; and leaves the 46-tool, 19-prompt/resource, schema, dependency, transport, CLI and persistence contracts unchanged. Remote-only GitHub CI is the execution gate; **1795 → 1795 source tests.**
- **v3.12.0-rc.30 (published; agent recipes + launch kit):** packages six client-neutral lifecycle recipes and stable-channel directory/community copy; strengthens initialize privacy/write guidance; gives `extract_todos` a default-surface fallback; removes raw-score confidence gates and the false `vault_wiki_compile` idempotence claim; canonicalizes compile paths; makes filtered PDF-tool gaps explicit; corrects hit-level stale and feedback-sidecar data-at-rest documentation; and makes `obsidian_context_pack` return PDF candidates separately instead of parsing their bytes as Markdown. Core search ranking, tool/prompt counts, persistence formats and dependencies are unchanged; the context-pack response gains additive `skipped_pdf_candidates`; **1795 → 1795 source tests.**
- **v3.12.0-rc.14 (candidate; verified client install actions):** classifies every generated client config as `uri`, `command`, or `copy-only` from a dated official-doc evidence map. VS Code receives its documented encoded MCP review URI; Claude Code/Codex receive copy-and-run commands; Marketplace/Registry-only clients retain explicit manual config. Round-trip and no-invented-link controls extend the existing client-config test; runtime retrieval and dependencies are unchanged; **1716 → 1716 source tests.**
- **v3.12.0-rc.13 (candidate; agent-native initialize guidance):** adds deterministic configuration-aware `initialize.instructions` to every stdio, stateless HTTP, and stateful HTTP server instance. The response recommends only live tools, explains the recall → evidence → source-read loop, path/line/page grounding, freshness uncertainty, write and feedback posture, and the untrusted-retrieved-content boundary. A 2,048-byte cap plus default/max/filtered/no-recall profiles and two real MCP handshakes guard the contract; **1710 → 1716 source tests.**
- **v3.12.0-rc.12 (candidate; public conversion + evidence integrity):** rebuilds the public comparison as an enquire-first TOP-1 battlecard, adds a runnable cited-memory example above the README fold, removes competitor recommendations from acquisition/AI-agent surfaces, replaces the social card's incomplete command, and scopes unsupported absolute HNSW/FTS5 performance claims across all 11 READMEs, CLI help, TSDoc, MCP descriptions, structured metadata, quickstart and templates. Existing docs-consistency coverage gains competitor-promotion + performance-claim negative controls without adding a source-test declaration. Runtime retrieval code and dependencies are unchanged; **1710 → 1710 source tests.**
- **v3.12.0-rc.11 (published predecessor; hermetic compiled-CLI model-alias regression; exact tag/npm gitHead `15a6eaa`):** the real built `build-embeddings` command runs behind a deterministic test-only transformer loader and process-level fetch/http/https tripwires. The regression requires exit zero, verifies the stored `bge` alias and proves no network marker; a deliberate request is the negative control that proves the tripwire is live. Runtime code and dependencies are unchanged; **1710 → 1710 source tests.**
- **v3.12.0-rc.10 (published predecessor; release identity + distinct-gate integrity; exact tag/npm gitHead `f47d903`):** `release.yml` rejects a trigger tag that differs from `v${package.json.version}` and delegates CI readiness to a pure evaluator that selects the latest run for each of nine exact context names. Duplicate/prefix matches cannot hide a missing context; pending, skipped and failed latest runs cannot qualify. The workflow mirror is pinned to the operational inventory. **1703 → 1710 source tests.**
- **v3.12.0-rc.9 (historical candidate; superseded `brace-expansion` HIGH remediation):** this candidate resolved `brace-expansion@5.0.8` for the dev-only TypeDoc → minimatch path after `GHSA-mh99-v99m-4gvg`; later `GHSA-rgw5-rvv9-x895` established that `5.0.8` remained affected, so the current root override floor is `5.0.9`. The empty source allowlist stays empty and runtime dependencies are unchanged. **1703 → 1703 source tests.**
- **v3.12.0-rc.8 (published predecessor; OCR admission + cancellation cleanup; exact tag/npm gitHead `399376f`):** every tool/watcher OCR call shares one FIFO process-wide slot, a four-call waiting cap and a ten-minute wall-clock budget including queue wait. SDK cancellation reaches pdfjs render, Tesseract termination and PDF cleanup; an expired caller returns immediately but the slot stays leased until cleanup settles. The risk-triggered sweep also closes a pre-existing `loadingTask` leak on document-acquisition rejection, bounds retained-buffer queue growth and corrects stale resource-limit claims. **1692 → 1703 source tests.**
- **v3.12.0-rc.7 (published predecessor; token-setup documentation integrity; exact tag/npm gitHead `168bf24`):** creates `~/.enquire` before both HTTP-guide token redirects and `~/.config/enquire` before the ChatGPT-example redirect. The existing docs-consistency declaration gains an order-sensitive analyzer plus missing/late/ready controls, so the exact clean-machine failure class is guarded without increasing the canonical test count. Runtime behavior is unchanged; **1692 → 1692 source tests.**
- **v3.12.0-rc.6 (published predecessor; autonomous discoverability tail; exact tag/npm gitHead `c919beb`):** adds `llms-ctx.txt`, npm-packaged agent text surfaces, schema-supported MCP Registry title/website identity, repository-wide CODEOWNERS, security-aware support routing, and an exact 20-topic memory/Obsidian/MCP/primary-agent + adjacent retrieval portfolio. The official Registry schema audit rejects the proposed categories/keywords because those fields are not standard and the official roadmap explicitly excludes tags/categories. Existing positive/negative documentation invariants now patrol the new surface; runtime behavior is unchanged; **1692 → 1692 source tests.**
- **v3.12.0-rc.5 (published predecessor; compiled benchmark model-state matrix + TOP-1 funnel; exact tag/npm gitHead `386d69a`):** closes the corrupt-model false-ready path by requiring a successful inference at the catalog dimension and a complete non-empty synthetic embedding sync before strict benchmark evidence can be written. Diagnostic artifacts declare `meta.partial` in JSON; a process-level present/missing/corrupt × strict/diagnostic matrix runs the real script and compiled `dist/` behind an offline loader fixture. The same release moves README ×11 plus npm/MCP/AI metadata to the explicit TOP-1 product funnel and patches direct runtime `js-yaml` 5.2.2. Runtime serve behavior is unchanged; **1690 → 1692 source tests.**
- **v3.12.0-rc.4 (published predecessor; evidence-pinned comparison refresh; exact tag/npm gitHead `c52beee`):** re-verifies OHS, cyanheads, Markus, mcpvault and Basic Memory against immutable source commits; rebuilds the buyer decision matrix; marks translated legacy matrices historical; removes stale factual uniqueness claims across docs, CLI/schema text and source comments; and adds a positive/negative-control current-claim invariant. The intentionally promotional TOP-1 hero remains. Runtime retrieval behavior is unchanged; **1690 → 1690 source tests.**
- **v3.12.0-rc.3 (published predecessor; compiled retrieval scenario matrix; exact tag/npm gitHead `c70be24`):** replaces the narrow 150-note cold-only H-1 regression with an exact `cold/warm × 1/9 queries × 100/6400 notes` matrix. Cold cells permit one corpus pass, warm cells permit zero additional reads, and every changed-snapshot negative control requires exactly one rebuild. Test-only; runtime behavior is unchanged; **1690 → 1690 source tests.**
- **v3.12.0-rc.2 (published predecessor; S-1 first-run orchestration; exact tag/npm gitHead `f076892`):** adds `first-run` as a preview-first wrapper over existing activation primitives. Default mode runs only non-destructive `configure` and prints exact planned effects; `--apply` authorizes hybrid setup + `rerank-bge` acquisition + tiered doctor, while basic never schedules index/model state. Raw child argv preserves one package identity, privacy filters and explicit model/quantization choices; first failure stops the chain and emits an idempotent resume command. **1681 → 1690 source tests.**
- **v3.12.0-rc.1 (candidate; pre-PR audit fixes applied):** replaces migration-capable doctor opens with bounded immutable SQLite snapshots, exact current-schema/model-cache checks and tier-aware `required`/`unverified` readiness; adds package-coherent preflight rendering, offline query/eval/programmatic boundaries, and strict fingerprinted eval comparison with packaged `eval-compare`. Privacy-filter propagation and cross-platform command rendering are part of the pre-PR audit. **1638 → 1681 source tests.**
- **v3.11.7-rc.8 (published `@rc`; exact tag/npm gitHead `53c7911`):** closes the release-audit false green exposed before rc.7 publication. CI now resolves a fresh published-consumer graph without root-only overrides, rejects new/stale advisories, and carries exactly three RC-only upstream exceptions with reachability reasoning. Stable promotion is blocked until that consumer graph is clean. A flaky client-scheduling assertion in the H-2 rollback regression is replaced by a test-only barrier that proves genuine concurrent-DELETE admission without changing product behavior. Includes rc.7's staged native TypeScript 7 emit; **1638 source tests** unchanged.
- **v3.11.7-rc.7 (merged; deliberately never tagged/published; superseded by rc.8):** staged TypeScript 7 migration. Product `build`/`dev`/`prepare` explicitly invoke the exact native 7.0.2 alias; stable TypeScript 6 remains the root tool only because K-1 and TypeDoc 0.28 still consume the classic Compiler API. The K-1 positive test pins the live split; its NEGATIVE sibling mutates both dependency declarations and all three emit scripts. Runtime behavior and the **1638 source-test** count are unchanged.
- **v3.11.7-rc.6 (previously published `@rc`):** closes the >`DELETE_DRAIN_MS` H-2 data-integrity tail. Every persistent MCP mutator is tracked and serialized by its shared persistence owner. On an expired ordinary drain, rollback-safe rename/replace/archive operations cancel and restore their invocation's committed effects; atomic/single-effect writes finish; DELETE returns retryable 409 while retaining the session and response channel. Manifest-derived `inFlightWrites` covers callback-settled/response-pending requests, and shutdown waits through the write-integrity tail. **1624 → 1638 source tests.**
- **v3.11.7-rc.5 (merged; shipped within rc.6):** closes A8's static class gate. The maximally-enabled built server is serialized into a deterministic 46-tool schema inventory and exact diff; project policy separately bounds explicit dialect/object roots, 16 KiB per tool, 128 KiB total and depth 16. Revisioned profiles enforce the observed array-`items` rejection for ChatGPT/Gemini/Grok, leave Claude/Cursor unverified pending credentialed smoke, and explicitly prevent generic vendor documentation from becoming a universal LCD. Runtime behavior is unchanged. **1617 → 1624 source tests.**
- **v3.11.7-rc.4 (merged; shipped within rc.6):** model-agnostic transfer of the useful T-Search orchestration pattern without its 36B checkpoint. `obsidian_context_pack` gains opt-in `subqueries[]` (≤5 extras, sequential, ≤6 search pipelines), preserves original top-1, reserves one candidate per atomic question, then RRF-fills; its trace reports candidates/zero hits but never self-declares semantic coverage. `vault_research` now carries saved evidence + reasons, covered/unresolved state, query history, and a next goal through at most two rounds before a ranked cited handoff. Default behavior is unchanged; no server-side LLM or implicit write. **1605 → 1617 source tests.**
- **v3.11.7-rc.3 (merged; shipped within rc.6):** post-promotion public-truth audit, no runtime defect. Re-derived stateful-session lifecycle from transport + tests, CI governance from tracked workflows + live branch protection, reranker language posture from the model catalog/CLI, and stable feature availability from release history. Corrected SECURITY, all 11 READMEs, AGENTS/llms/ROADMAP, JSON-LD, exported ServeOptions TSDoc and docs/api; one class-wide invariant carries pre-fix negative controls. **1604 → 1605 source tests.**
- **v3.11.7-rc.2 (previously published `@rc`):** mandatory post-merge re-sweep of unpublished rc.1 found A10-F1 [MED] — two hardlink paths for one inode bypassed the canonical-path append queue (cap 10 → 12). The queue now keys `dev+ino` and verifies the opened descriptor retained that identity; 500 compiled-dist races produced zero violations, and a path-key mutation makes the hardlink regression fail. **1602 → 1604 source tests.**
- **v3.6.0 stable shipped**: tools.ts + index.ts splits + Full TSDoc + TypeDoc on GH Pages + public benchmarks. Internal 9-layer audit produced 4.85/5 verdict. Mavis external 4.9/5.
- **v3.6.1 emergency patch shipped**: closed 3 CRITICAL ship-blockers caught by an anonymous external auditor that all 3 prior audits missed. (Overclaim instance #1: "CRIT-1 closed" was 1 of 10 callsites.)
- **v3.6.2 shipped**: 13 Medium + 14 Low + 4 HIGHs from internal + external audits. Claimed "K-1 RESIDUAL CLASS full fix" + "all 10 callsites". (Overclaim instance #2: actually 4 of 10.)
- **v3.6.3 shipped**: marketing pivot to "memory layer for AI agents" framing — README + npm description + GitHub About/Topics + package.json keywords. Zero code/behavior changes; pure discovery patch.
- **v3.6.4 shipped**: K-1 class TRULY FINAL closure — fixes the 5 residual `cli.ts` callsites that v3.6.2 left + adds `tests/k1-class-invariant.test.ts` (structural class guard) + 3 caller-pattern integration tests (positive bge / positive trigram / negative-control). Retroactive TSDoc corrections in `embed-db.ts` + `fts5.ts`. CHANGELOG explicitly names v3.6.2 as overclaim instance #2 and v3.6.1 as #1.
- **v3.7.0 shipped**: quality batch closing 8/8 post-v3.6.4 audit items. M-1 E2E preservation tests (setup/eval/build-embeddings) + M-2 AST-based K-1 invariant with fixture negative-controls + M-3 recursive `SRC_DIRS` scan + L-1 peek-result caching (19.9× speedup on search hot path) + L-2 bench rerun + L-4 marketing positioning permeation into docs/api.md / docs/QUICKSTART.md / docs/COMPARISON.md + per-file branch coverage floors (`scripts/check-per-file-coverage.mjs`) + GitHub repo metadata invariant. **K-1 saga now structurally enforced at 4 levels (grep, AST, caller-pattern, fixture-based negative-control). Last instance in this thread.**
- **v3.7.1 shipped**: external audit response. The v3.6.0 audit report (`AUDIT-enquire-mcp-2026-05-15.md`) was processed in full — 36/38 findings were already closed by the v3.6.1→v3.7.0 cascade; 1 material residual (`SECURITY.md` drift — said `.base` unevaluated predicates were "permissive" but v3.6.2 HN-2 had flipped to fail-closed) was fixed in this patch + 2 docs touch-ups (api.md channels → v3.7.x, QUICKSTART Node version framing). 1 finding (L-1 index.ts rc.2 historical comment) is documented as accepted with reasoning. Zero code/API/behavior changes.
- **v3.7.2 shipped**: round-3 audit response. Found 13+ inline `// v3.6.3 K-1 ...` mis-attributions (v3.6.3 was marketing-only, K-1 actually closed in v3.6.4). Mass-fixed all stamps + added `tests/k1-version-stamp-consistency.test.ts` as 5th-level structural guard for the K-1 class. The doc-drift class has now recurred 4 times; this invariant terminates the iteration.
- **v3.7.3 shipped**: round-4 audit response (24h after v3.7.2). Caught **self-applied methodological violation**: v3.7.2's invariant lacked a negative-control sibling test. Extracted scanning into `scanK1Stamps()` pure function + added fixture-based negative-control. **All 5 K-1 enforcement levels got negative-control coverage.**
- **v3.7.4 shipped**: round-5 audit response — class-vs-instance recursion correction. v3.7.3 fixed ONE instance of "post-v3.6.4 invariant lacking negative-control" (k1-version-stamp) but the CLASS had a second open instance (`tests/github-metadata-invariant.test.ts`). Plus reranker model count (5) gate.
- **v3.7.5 shipped**: 2nd external audit response (CRITICAL). `enquire-mcp-audit-report-v3.6.2.md` (round-7) found K-1 (embedder thread-through silent corruption) + K-2 (read-only search DROP TABLE) that 5 internal audit rounds missed. 1-line fix `loadEmbedder(args.model)` → `loadEmbedder(model.alias)` + throw-on-mismatch instead of bootstrap rebuild + M-1 SECURITY docs drift in api.md/tool-registry.ts. Re-confirms v3.6.1 method note: ≥2 independent external auditors with DIFFERENT methodologies.
- **v3.7.6 shipped**: 8 ship-ready findings from v3.6.2 audit batched (H-4 PDF stale rows · H-5 serve-http examples · M-5 TypeDoc CI gate · M-9 chmod parent · M-10 HNSW signature includes quantization · M-12 reranker -Infinity · L-3/L-4 docs). 786 tests unchanged; 2 tests updated for M-10 signature change.
- **v3.7.7 shipped**: visual + marketing refresh — new social-preview.png with emotional value prop + visual flow (vault → enquire-mcp → 5 AI agents). README hero rewritten with "The problem / The solution" narrative + sticky nav. Zero code changes.
- **v3.7.8 shipped**: positioning calibration — restored "The most advanced Obsidian MCP" credential to README H3 (paired with value prop), restored OpenClaw across 5 README surfaces + GitHub About + Topics (swapped context-engineering for openclaw in 20-cap).
- **v3.7.9 shipped**: round-11 audit response — positioning permeation completion. Caught 5 drift findings from v3.7.8 calibration: docs/QUICKSTART.md and docs/api.md had agent lists without OpenClaw, `tests/github-metadata-invariant.test.ts` REQUIRED_TOPICS + ABOUT_LEADS_WITH still carried v3.7.0 values (drift across 4 patches), CLAUDE.md status section stuck at v3.7.4. All synchronized.
- **v3.7.10 shipped**: round-12 audit — 10 findings closed (DQL `likeToRegex` 3 sub-bugs, `EmbedDb.upsertNote` transactionality verify, FTS5 transaction sibling fix, CI workflow `REQUIRED` includes `docs`, etc.). 786 tests.
- **v3.7.11 shipped**: round-13 self-audit — 3 findings closed including v3.7.10 silent overclaim correction (CHANGELOG D4 claimed `examples/` added to `package.json#files` but Edit hit a file-modified race; verified post-merge, actually added in v3.7.11). 5th overclaim instance documented. 787 tests.
- **v3.7.12 shipped**: round-14 external audit response — 3rd independent external audit since v3.6.0 (Mavis on v3.6.0, anonymous on v3.6.0, this round-14 on v3.7.5). 10 ship-ready findings: H2 `.base` path normalization, H4 `./tool-manifest` missing from exports, M3 listCanvases mtime-as-bytes leak, M4 HTTP body cap < file cap, M6 graph-boost wasted I/O on PDFs, M11 HTTP docs token-env primary, L1 stateful clarification, L2 QUICKSTART version stale, L4 reranker catalog vs verified, L6 TypeDoc treatWarningsAsErrors. 2 false positives confirmed (PNG + queries.jsonl). 19 architectural items deferred to v3.8.0. **801 tests** (+14 negative-controls); +2 docs-consistency invariants.
- **v3.7.13 shipped**: round-15 external audit response — 4th independent external audit since v3.6.0. 15 ship-ready findings: H1 PDF page slicing pre-extraction DoS, H2 HTTP stateful pre-initialize resource leak, H3 engines.node floor mismatch, M1 renameNote rollback recovery, M2 overwrite=false atomic create (wx flag), M3 OCR lang regex validation, M5 7→8 required gates, M8 per-file coverage fail-on-missing, M9 chmod parentExisted pattern in saveDiskCache, M10 docs/api.md broken link → SECURITY.md, M11 benchmarks latency dedup, M12 COMPARISON test count gate, L2 OCR/read_pdf page range refine, L4 github-metadata fail-loud + CI GH_TOKEN, L7 exclude docs/audits from npm package. 2 findings already closed by v3.7.12 (L1 `.base` normalization, L3 canvas mtimeMs). **813 tests** (+11 negative-controls); +1 docs-consistency invariant; npm tarball -228 KB (docs/audits/ excluded).
- **v3.7.14 shipped**: round-16 SELF-audit — class-sweep methodology applied to v3.7.13 fixes. 5 findings: F1 v3.7.13 silent overclaim (M1 fixed renameNote ordering but TSDoc header still described OLD buggy order — 6th overclaim instance), F2 M2 sibling (renameFile had same stat-then-rename TOCTOU race; fix via link()+unlink() atomic exclusive create), F3 appendNote stat-then-append race let parallel writers bypass maxFileBytes (fix via single open fd + write), F4 hardcoded "8 required CI gates" not gated by docs-consistency (v3.5.9 anti-pattern recurrence — new invariant against release.yml REQUIRED regex), F5 automated gh release create in release.yml (M13 from round-15, originally v3.8.0-deferred). **815 tests** (+2 negative-controls + invariant); +1 docs-consistency invariant.
- **v3.7.15 shipped**: round-17 POST-MERGE audit on v3.7.14 — **meta-recursion finding**: v3.7.14 F1 closed overclaim #6 but v3.7.14 F2 SHIPPED overclaim #7 in the same patch (renameFile TSDoc still said "Atomic via fs.rename" after F2 changed the impl to link()+unlink()). 3 fixes (R17-1 renameFile TSDoc, R17-2 appendNote TSDoc enhancement, R17-3 docs/COMPARISON.md "5 models" missed v3.7.12 L4 instance) + 1 new invariant (R17-4 COMPARISON reranker honesty). Plus overclaim #8 (v3.7.14 orphan-tag procedural error caught by "Assert tag is on main" guard) + overclaim #9 (v3.7.14 F5 was permission-incomplete, `HTTP 403`, fixed via `permissions: contents: write`). 9 documented overclaim instances now. CLAUDE.md anti-patterns extended with 3 new rules. **816 tests** (+1 invariant). F5 release automation finally verified working end-to-end on v3.7.15 tag push.
- **v3.7.16 shipped**: round-18 external audit response — 5th independent external audit since v3.6.0, on the v3.7.5 codebase (commit b9daf39). The v3.7.6→v3.7.15 cascade had already closed many findings; this patch addresses still-open critical + high-impact items. **10 fixes**: P1-1 OCR network-policy disclosure (stderr warning + SECURITY.md), P1-2 OCR 200-page default cap, P1-4 persistent-cache + privacy filter lifecycle, P1-5 watcher PDF lifecycle, P1-6 macOS case-insensitive write privacy bypass (canonicalize parent via realpath), P2-13 title-based write fail-on-ambiguity (silent data corruption), P2-14 validateNoteProposal privacy check, P2-15 FTS5 tag LIKE `%`/`_` escape, P2-16 graph_boost `#chunk-N` regex (fixes `C# Notes.md`), P3-23 PDF install hint v5.7.284, P3-28 safeFts5Query reserved-word quoting (contract change), P3-30 issue template ChatGPT + Claude Desktop. 14 architectural findings deferred to v3.8.0 backlog. **816 tests** (1 test contract change for P3-28).
- **v3.8.0-rc.6 shipped**: round-23 external audit response (6 fixes). T-FLAKE-1 vitest per-it timeout (build-embeddings CI flake), N-4 protobufjs GHSA-jggg-4jg4-v7c6, N-5 serve-http `--watch` help parity, ARCH-1 circular import (`buildEmbedText` → embed-pipeline.ts), R-4 contextPack hard budget cap, OIA promoted advisory → required (9th gate). **838 tests** (unchanged). No open audit items from rounds 1–23.
- **v3.8.0-rc.7 shipped**: post-rc.6 self-audit — 3 fixes. α-class TSDoc drift (contextPack budget-cap paragraph missed in rc.6 R-4), N-5 sibling (`serve --watch` help text, rc.6 only updated `serve-http`), watcher.test.ts chokidar FSEvents startup delay (task #36 — 50ms warmup in line-170 test mirrors line-140 sibling). **838 tests** (unchanged).
- **v3.8.0-rc.8 shipped**: round-24 external audit response (2 findings). T-1 contextPack hard-cap zero test coverage → `tests/context-pack.test.ts` (4 tests, positive+negative controls); INFO-2 `embed-pipeline.ts` missing from per-file FLOORS → added at 84%. **842 tests** (+4); `tools/meta.ts` branches 67.66% → 73.85%; 10 per-file floors (was 9).
- **v3.8.0-rc.9 shipped**: round-7 external audit response (3 fixes). W-FLAKE-2 50ms chokidar warmup missing from R-7 embed tests (sibling of rc.7 #36 incomplete sweep); R-10 HNSW k multiplier 4× → 6× limit + TSDoc documentation of residual under-return; N-new qs 6.15.1 → 6.15.2 (GHSA-q8mj-m7cp-5q26 DoS). **842 tests** (unchanged).
- **v3.8.0-rc.10 shipped**: backlog items P3-25/P3-21/P3-27 + watcher floor lift. P3-25 `extractHeadings` tilde-fence fix (CommonMark `~~~` blocks now correctly excluded); P3-21 `--persistent-index` help text gating-wording drift corrected (both flags required); P3-27 HNSW metadata dim/size/rowsByLabel shallow validation before native constructor (clean rebuild instead of C crash). Watcher floor 69% → ≥71% via `attachEmbed` error-path NEGATIVE control. Post-merge self-audit found 2 α-class TSDoc instances (extractHeadings header + loadHnswFromDisk returns-null bullet list), fixed inline without a new RC (same-day doc-only commit on main). **846 tests** (+4 negative-controls).
- **v3.8.0-rc.11 shipped**: post-rc.10 root-class fix for M-1 (CLI help text drift) + L-1 (stale `// current ~X%` coverage comments). RCA-driven sweep extended scope from the 2 instances rc.10 audit reported to a **structural class fix**. M-1: lifted 9 more shared flags (--disabled-tools, --enabled-tools, --tokenize, --quantize-embeddings + 5 defensive) into `cli-help.ts` (13 constants total); new `tests/cli-parity.test.ts` invariant asserts every shared flag has identical help text (allowlist for intentional short-form); invariant caught +1 drift during development (`--quantize-embeddings`), proving the defense works. L-1: OIA walk check 6 — scans `// current X%` in coverage script against `coverage-summary.json`, drift >1pp fails; caught 2 more stale comments (ocr.ts 24%→31.03%, http-transport 66.86%→69.39%). **848 tests** (+2 cli-parity invariants).
- **v3.8.0-rc.12 shipped**: AI/LLM discoverability Tier A. Added `llms.txt` at repo root (https://llmstxt.org/ standard) + `AGENTS.md` (Cursor 2.0 / Claude Code / Codex convention) + tightened npm description (756→506 chars) + README "TL;DR for AI agents" callout + "Set up in your AI agent" copy-paste prompts. `publish-docs.yml` serves `llms.txt` at https://oomkapwn.github.io/enquire-mcp/llms.txt. **848 tests unchanged.**
- **v3.8.0-rc.13 shipped**: AI/LLM discoverability Tier B. Added `mcpName` field to package.json (`io.github.oomkapwn/enquire-mcp`), `server.json` at repo root, `CITATION.cff` with 6 paper refs (HyDE/RRF/BM25/Louvain/HNSW/BGE). After ship, submitted to **official MCP Registry** (registry.modelcontextprotocol.io) — `io.github.oomkapwn/enquire-mcp@3.8.0-rc.13` is `active`, `isLatest=true`. Glama.ai/mcp.so/smithery.ai auto-sync from canonical registry (24-48h). Also opened **awesome-mcp-servers PR #6838** in Knowledge & Memory category. Post-ship `server.json` schema validation fix merged via PR #117 (description ≤100 chars, PositionalArgument valueHint+value, NamedArgument name+type:named ordering). **848 tests unchanged.**
- **v3.8.0-rc.14 shipped**: post-rc.13 audit closure — root-class fix for "new files introduce drift surface without invariant coverage" (M-2). Extended `tests/docs-consistency.test.ts` with 7 new invariants covering numeric claims in llms.txt (test count, tool breakdown 33+4+7, MCP prompt count, CI gate count) + AGENTS.md (test lower-bound, per-file floor count, CI gate count). Plus L-2 (CLAUDE.md status section was stale at rc.12 — same α-class drift we keep fighting), L-3 (MCP Registry submission was not documented in CHANGELOG), L-4 (PR #117 server.json fix was not documented). **855 tests** (+7 docs-consistency invariants).
- **v3.8.0-rc.15 shipped**: meta-recursion fix — overclaim instance **#10**. rc.14 added 7 invariants for M-2 BUT none had NEGATIVE control siblings, violating CLAUDE.md rule since v3.6.4. Same recursion class as v3.7.14 F1+F2 (overclaim #6+#7 in same patch). M-3 fix: refactored 7 M-2 checks into pure functions; added 7 NEGATIVE control sibling tests. Plus L-5 snapshot header on audit-request doc. **862 tests** (+7 NEGATIVE controls).
- **v3.8.0-rc.16 shipped**: META-invariant — STRUCTURAL ENFORCEMENT of the CLAUDE.md rule since v3.6.4 ("every invariant test must have NEGATIVE control sibling"). `tests/meta-invariant-coverage.test.ts` scans every `tests/*-invariant.test.ts` file, fails CI if any lacks NEGATIVE control OR explicit `META-INVARIANT-EXEMPT` marker. **Recursion class structurally impossible going forward.** **868 tests** (+6 META-invariant tests).
- **v3.8.0-rc.17 shipped**: Multi-subcommand CLI drift audit — closes rc.11 RCA finding. Sweep of 12 subcommands found 4 byte-identical lift candidates (--cache-file, --index-file, --quantize-embeddings, --tokenize) and 5 intentional context-specific drifts. New cli-parity invariant fails CI on byte-identical inline drift. **872 tests** (+4 cli-parity invariants).
- **v3.8.0-rc.18 shipped**: Self-audit response (post-rc.17 sweep). 3 fixes: S-AUDIT-1 (server.json was 4 RCs behind npm — extended check-version-consistency.mjs from 5 → 7 surfaces), S-AUDIT-2 (terminal vault.isExcluded() filter in searchHybrid for ε-class defense-in-depth), S-AUDIT-3 (documented test:coverage → check:oia local workflow). Class-fix: Rule 6 extended to cover version-bearing files. **872 tests** (unchanged). (Original rc.18 entry incorrectly attributed these to "external Cursor audit" — see v3.8.1 retraction.)
- **v3.8.0 STABLE shipped**: Promoted @rc → @latest after 18 RCs of methodology hardening. All 9 required CI gates green, 872 tests, 89.91% line coverage. **External audit blocker per v3.6.1 rule was silently violated at promotion time** — see v3.8.1 retraction for overclaim #11 details. **Resolved 2026-05-25**: corrected enquire-mcp audit on `7a9fdbd` / rc.15 (`docs/audits/v3.8.0-rc.15-external-2026-05-25.md`) returned 4.85/5 with no ship-blockers — retroactively justifying the promotion. npm `@latest = 3.8.0` stays.
- **v3.8.1 shipped**: Retroactive correction patch — overclaim instance #11. rc.18 + v3.8.0 STABLE incorrectly cited a "Cursor external audit 4.85/5" as basis for promotion (the document I read was for a DIFFERENT project — T-Invest — and was misapplied). Technical rc.18 fixes (S-AUDIT-1/2/3) remain valid. Documentation patched: Cursor refs removed, new CLAUDE.md anti-pattern rule added ("verify document provenance"). **2026-05-25 update**: the same auditor delivered the correct enquire-mcp audit on rc.15 (`docs/audits/v3.8.0-rc.15-external-2026-05-25.md`) — 4.85/5, no ship-blockers, 5 of 6 actionable findings already closed by rc.18→v3.8.5 cascade. v3.8.1 retraction was about misdirected delivery, not about the verdict itself being wrong. **External audit blocker per v3.6.1 — re-evaluated as MET** (see header note). **872 tests unchanged.**
- **v3.8.2 shipped**: State-driven docs refresh — 6 stale-version fixes (A-1/2/3/5/7/8). CLAUDE.md header "v3.7.x maintenance + v3.8.0 architectural" → "v3.8.x stable maintenance + v3.9.0 architectural"; backlog rephrased; docs/api.md "stable v3.7.x @latest" → "stable v3.8.x"; docs/COMPARISON.md 4 timestamps refreshed. Class lesson: change-driven sweeps miss docs files not actively edited; OIA Check 1 covers src/ but not docs/. **872 tests unchanged.**
- **v3.8.3 shipped**: OIA Check 7 — extend stale-currency-claim detection to docs/ + CLAUDE.md. Scope: CLAUDE.md + docs/*.md (excluding docs/audits/). 5 present-tense patterns + history-context skip. **872 tests unchanged.** ⚠️ Scope too narrow — post-v3.8.3 sweep found same drift in README.md (B-1) and examples/*.md (B-2). Triggered v3.8.4 (overclaim #12 + recursion-pair shape #6).
- **v3.8.4 shipped**: OIA Check 7 scope expansion — closes the narrow-scope recursion in v3.8.3 itself (overclaim #12). Check 7 now walks 8+ markdown surfaces. 2 new patterns: "capabilities|claims|features|snapshot as of vX.Y.Z" + "wait for|coming in|planned for|will land in vX.Y.0" with major.minor comparison. **872 tests unchanged.**
- **v3.8.5 shipped**: T-2/T-3/T-4 E2E backlog closure. New `tests/e2e-handlers.test.ts` with 7 tests (878 it + 1 gated `it.skipIf`) covering communities/HyDE/serve-http via spawn-dist + JSON-RPC pattern. **878 tests** (+6 vs v3.8.4, 1 gated skip).
- **v3.8.6 shipped**: Tier C discoverability completion — Schema.org JSON-LD injection on GH Pages. New `scripts/inject-jsonld.mjs` reads package.json + injects `SoftwareApplication` JSON-LD blob into TypeDoc-generated `docs/api-reference/index.html` `<head>`. Wired into `publish-docs.yml` after `npm run docs:api`. Idempotent. Recognized by Google AI Overviews, Perplexity, Bing Copilot. **878 tests unchanged.**
- **v3.8.7 shipped**: HTTP transport hardening — P2-10 stateful session races (3 conditions: cap-check TOCTOU, sweep vs in-flight, DELETE vs concurrent use) + P2-11 close cleanup gap (registry session leak on `httpServer.close()`). `src/http-transport.ts` gains `inFlight` refcount + `closing` flag on every `StatefulSession`, `pendingInits` counter on `SessionRegistry`, `runWithRefcount(session, fn)` helper wrapping every `handleRequest` call, DELETE marks-then-deletes before SDK protocol shutdown, `closeAll(timeoutMs)` graceful drain on the registry, new `shutdownHttpServer(server)` helper tracked via module-private `WeakMap<HttpServer, HttpServerExtras>`. Signal handlers (SIGINT/SIGTERM) + tests both use the new helper. **+9 tests (mix of POSITIVE/NEGATIVE control siblings, including a concurrent-init TOCTOU test that fires 6 parallel POSTs at cap=2 and asserts ≤2 succeed); 888 tests total. No API breaks.**
- **v3.8.8 shipped**: META structural-defense scope completeness audit — closes the recurring "recursion-pair shape" class (6 documented instances since v3.6.x). New `scripts/scope-completeness-audit.mjs` enumerates every numeric-claim defense's scope + exempts; sweeps the entire repo for matching patterns; any file containing a tracked pattern that's NOT covered fails CI. Wired into both `tests/scope-completeness-invariant.test.ts` (change-driven gate) and `scripts/oia-walk.mjs` Check 8 (state-driven sweep). Initial defense set: 5 patterns (test-count, tool-count, prompt-count, ci-gate-count, per-file-floor-count). Immediate gap closed: STABILITY.md "44 tools" was already gated by docs-consistency but missing from the audit manifest. **+5 tests (3 POSITIVE + 2 NEGATIVE controls); 893 unit tests total.**
- **v3.9.0-rc.1 shipped**: OCR'd PDF watcher embed-sync — closes the last deferred v3.8.0 backlog item. New CLI flags `--ocr-pdfs` / `--ocr-langs` / `--ocr-max-pages` (lifted into `addAdvancedRetrievalOptions` so both `serve` + `serve-http` get them). Watcher gains `setOcrPdfs(enabled, langs?, maxPages?)` for late-binding after `attachEmbed()`. PDF event handler runs Tesseract on image-only PDFs (when OCR enabled) and feeds OCR pages through `embedSinglePdf`'s new `preExtractedPages` parameter. Fail-soft when Tesseract deps missing / language pack absent. **+5 tests (4 POSITIVE + 1 NEGATIVE); 898 unit tests total. HNSW in-memory live update deferred to rc.2.**
- **v3.9.0-rc.2 shipped**: HNSW in-memory live update — closes the last architectural deferral from v3.8.0. `EmbedDb.upsertNote/deleteNote` now return the row ids affected (`{oldIds, newIds}` / `number[]`). `HnswIndex` interface extended with `applyDiff(removeLabels, addPoints)` / `resize(n)` / `capacity()` — applyDiff markDelete's old labels then addPoint's new vectors with `replaceDeleted=true` (reuses slots, auto-resizes). Watcher gains `attachHnsw(hnsw, rowsByLabel)` for late-binding (mirrors attachEmbed pattern); server.ts wires it on both built-fresh and loaded-from-disk HNSW paths. Search results now reflect vault edits within the watcher debounce window (~250ms); pre-3.9.0 needed serve restart. Fail-soft on capacity exhaustion / hnswlib crash. **+13 tests (10 POSITIVE + 3 NEGATIVE controls); 911 unit tests total. No API breaks (additive).**
- **v3.9.0-rc.3 shipped**: R-10 adaptive HNSW refill — closes the last open INFO finding from the corrected 2026-05-25 external audit on rc.15. New `adaptiveHnswRefill()` helper in `src/tools/search.ts` doubles k up to 3 attempts when post-filter hits < limit; closes the >66% excluded under-return class that rc.9's static 6× multiplier couldn't fully solve. Archived audit doc + AUDIT-REQUEST for next pre-stable pass. **+7 tests; 918 unit tests total.**
- **v3.9.0-rc.4 shipped**: Full state-driven self-audit — closes 3 HIGH + 4 MEDIUM findings + documents overclaim #13 + recursion-pair shape #7 + extends META scope-completeness with 2 new defenses (`runDeferredClaimAudit`, `runCliFlagCoverageAudit`). User-facing docs (README, api.md, QUICKSTART, llms.txt, AGENTS.md) updated to mention v3.9.0-rc.1/rc.2/rc.3 features (OCR flags, HNSW live update, adaptive refill). 12 stable CLI flags added to `docs/api.md` flag table. CLAUDE.md self-contradiction (header "deferred" vs status "shipped") fixed. **+5 tests (3 POSITIVE + 2 NEGATIVE controls); 923 unit tests total.**
- **v3.9.0-rc.7 shipped**: Tier 0 integrity batch from the comprehensive audit (code agent + docs agent + competitor research). Fixes 2 brand-critical overclaims: **#15 SLSA-3** (badge linked to slsa.dev L3 but `release.yml` only does `npm publish --provenance` = SLSA Build L2 — downgraded the claim across README badge/hero/table/releases + package.json + llms.txt + COMPARISON to "signed build provenance (SLSA L2); L3 generator on roadmap") + version/RC drift (README "currently rc.3"→rc.6, QUICKSTART, benchmarks, OIA "6 checks"→8 in AGENTS.md). Reranker claim corrected from generic "+5-10 NDCG@10" to **measured +15.5 NDCG@10 / +24.7 MRR**. New `ROADMAP.md`. `false/` npm-cache junk → `.gitignore`. CITATION.cff version field. New anti-pattern documented (overclaim #15 + the "claimed-guarantee vs code-guard" class). **Docs/config-only; tests unchanged.** OCR offline enforcement (#16, "implement" decision) → rc.9; watcher per-file serialization race (H1) → rc.10 (re-sequenced when rc.8 became the integrity-batch — see rc.8 entry).
- **v3.9.0 STABLE → v3.9.1 → v3.10.0-rc.1→rc.78 → v4.0.0-rc.7 (current roll-up; `@rc`=4.0.0-rc.7 tracked-candidate marker; last verified predecessor=4.0.0-rc.3; `@latest`=3.11.6)**: condensed status for everything shipped after the rc.35 entry below (per-RC detail lives in CHANGELOG.md). **v3.9.0 promoted `@rc`→`@latest`** (maintainer call on the ≥2-auditor gate; CI published + MCP-registry OIDC synced); **v3.9.1** post-stable hardening. **v3.10 line (all on `@rc`):** (a) **forgetting-aware staleness** (rc.1→rc.10) — `age_days`/`stale` on every hit from live mtime, the `obsidian_stale_notes` tool (45th), opt-in `--recency-weight` recency re-rank (default off, provable no-op), fusion-stage `pruneExcludedHits`, **frontmatter-aware `obsidian_search`** (`filter_frontmatter`); (b) **install-live-test + bug-report batch** (rc.11→rc.15) — hermetic test cache (`XDG_CACHE_HOME`→temp), model-path resolver, reranker observability + pre-cache, `query`/`prune` CLI, `writeAndWaitFor` watcher-flake root fix; (c) **comprehensive 7-agent system audit → MEDIUM batch M1–M10** (rc.16→rc.22; 0 CRIT/0 HIGH on `src/`) — M5/M6 retrieval correctness, M1 chunking parity, M4 DoS-cap completeness, M3 signal-shutdown orchestrator, M7 privacy/right-to-erasure (shared HNSW-stem helper), M2/M10 docs-integrity, M8/M9 test-theater + CI-GUARD + audit-level; (d) **round-2 post-MED re-sweep** (rc.23→rc.25) — a 3-agent audit on the *shipped* commit caught a **HIGH I introduced in rc.19**: `serve-http` `server.close()` hangs forever on a lingering keep-alive connection (the flush-`process.exit(0)` hatch rc.19 removed had masked the latent hang) → fixed via bounded `closeServerBounded` (`closeIdleConnections` + grace→`closeAllConnections`), empirically reproduced + fix-verified; then a LOW code batch (rc.24: `query_base` cap + parser `lastIndexOf` + watcher unlink-cleanup) and a LOW docs-currency batch (rc.25). (e) **SYS-1 supply-chain** (rc.26) — closed the two deferred behavioral dimensions: M-9 `run:`-download CONTENT-pin (the mcp-publisher release tarball is now SHA256-verified `sha256sum -c` fail-closed, not just tag-pinned; **OIA Check 9b** extended to require it, inject/revert-verified) + documented H-3 (PDF/OCR parity, closed rc.33) as closed. **(f) seeklink-inspired discoverability batch** (rc.27→rc.31): Docker/Glama (`Dockerfile` + advisory `docker` CI job that builds the image + smoke-tests `tools/list` — we had `glama.json` but no Dockerfile; the job caught both a real build break AND my own smoke-script EPIPE bug on first run), README trust-batch (honest "When NOT to use it" + self-propagating agent-rule snippet + the **44→45** tool-count drift-class fix across 3 docs + a broken Karpathy gist link), `llms.txt` full agent-contract, bilingual **`README.zh.md`** (中文) + `[English]·[中文]` switcher, eval **`failure_bucket`** diagnostics (hit@1/hit@k/miss/no_labels/error; metrics byte-identical). **(g) post-rc.31 3-lens audit** (rc.32 docs/test-infra + rc.33 code): re-swept the seeklink batch (0 CRIT/0 HIGH, every finding per-item re-verified) → the **CLAUDE roll-up α-class is now STRUCTURALLY pinned** (`check-version-consistency.mjs` enforces the roll-up's `(current roll-up; `@rc`=<version>)` marker == package.json on every rc, ending the 6×-recurring status-stale class), vacuous-on-deletion tool-count guards hardened, eval error-bucket end-to-end assertion, and FTS5 `--persistent-index` made fail-soft (closes the "auto-degrades gracefully" claimed-guarantee gap — parity with the already-fail-soft PDF/embed/HNSW paths). rc.33 also surfaced + fixed a DEEPER latent bug the audit didn't name: `peekFtsMetaSafe` wrapped the better-sqlite3 *load* but not `new Database()`, so a corrupt / unreadable / directory persistent-index file crashed serve at the pre-open peek (before the open() fail-soft) — now it truly never throws (any failure → null → TF-IDF). Plus eval polish: `recallAtK`/`ndcgAtK` dedupe duplicate relevant paths (no >1.0 inflation) + `formatEvalResult` dynamic id-column width. **(h) rc.34 RCA re-sweep** — the post-rc.33 re-sweep (mandated by the "fix the class, not the instance" rule) found the rc.33 fix's SIBLING: `peekEmbedDbMeta` had the same `new Database()`-outside-try shape AND is called UNGUARDED on the `embeddings_search` hot path (before that fn's own try) + in CLI subcommands — so a corrupt / directory `.embed.db` errored the search / crashed the CLI. Hardened to truly-never-throw (any failure → null → graceful degrade); +3 tests. **(i) rc.35** — README reorder (maintainer call): the "🏆 Why it's the best" comparison section moved above "⚡ Quick start" (lead with differentiation before install; the hero `claude mcp add` one-liner keeps "try it now" reachable from the top) — README-only, no count change. **(j) rc.36** — **CRITICAL ReDoS, the 4th recurrence** of the class (rc.21/24/25), found by a fresh 7-lens behavioral/threat workflow-audit. `isCatastrophicRegex` computed its catastrophe verdict ONLY at a quantified-group close (`)` pop), so a BARE top-level run of adjacent overlapping unbounded quantifiers (`\w*\w*…$` — measured 16s V8 hang, reachable via the always-registered `obsidian_open_questions` on bearer-auth serve-http) was classified SAFE — **overclaim #19** (the TSDoc's "never under-flags" was false). Fixed with `frameAdjacentOverlap` evaluated on the TOP frame + every group body; overlap decided by **probe-membership** (delegates the char-class truth-table to V8 — so `\d*\s*`/`[#.]+\s+` stay accepted and `\w*\d*` is caught, no hand table to mis-write) + a `.`-greedy **absorber tail-exemption** (keeps the shipped default `…\s*[:\-]?\s*(.+)$` safe — that pattern is benign ONLY because `(.+)` absorbs the tail; `…\s*\s*$` alone is 12s catastrophic). **Root durable fix + the lesson that matters:** the rc.25 generative fuzz couldn't catch this because its GENERATOR only ever emitted quantified GROUPS (`(…)+$`) — never a bare top-level concatenation — so the recurrence-shape was outside the corpus. Extended `genPattern` to emit `seq seq $` (SAFE corpus 43→390), so the empirical net now covers top-level adjacency. **A generative fuzz is only as strong as its generator's SHAPE-coverage; when a detector class recurs, audit whether the fuzz can even PRODUCE the new shape — not just whether the detector handles the shapes you already thought of.** **~1024 → 1164 source tests** (rc.36 cases are data-driven array entries on the existing loop `it()`s — canonical count unchanged). **Lesson reinforced:** the home-grown gates are drift/claim-driven and blind to runtime behavior (the rc.23 shutdown HANG + this rc.36 ReDoS both passed every gate; only the external-lens workflow-audit found rc.36) — the post-merge re-sweep + external-lens audits remain essential. **(k) rc.37** — privacy/right-to-erasure batch from the same audit (#3/#4 MED + #8 LOW): the cross-vault `prune` now erases the `<hash>.json` parse cache (FULL note bodies) + its `.tmp` leftover — pre-rc.37 a decommissioned vault's full-text cache survived `prune` FOREVER (the whitelist regex omitted `.json`); the **erasure invariant now patrols the `prune` eraser too** (writers ⊆ prune — that eraser surface was unguarded, which is exactly why #3 shipped; same shape as the rc.36 fuzz-generator gap, now at the eraser level); and an emptied `--use-hnsw` embed-db erases its stale `.hnsw.bin`/`.meta.json` sidecars (the `.meta.json` carries deleted notes' raw `text_preview`). **1164 → 1168 source tests.** **(l) rc.38** — correctness + resource batch (#5 MED + #2 MED): (#5) `.base` `not:` no longer INVERTS the v3.6.2 HN-2 fail-closed rule — `not: 'inDate(...)'` / `not: '<typo>'` was returning EVERY row (negating the unevaluable→`false` to `true`); now negation evaluates its child against a FRESH `unevaluated` PROBE set (the real one is shared across rows, so a size-delta only fires on the first row — the bug my first attempt hit) and excludes if the child touched any unevaluable predicate, merging them back so they still surface. SECURITY.md's fail-closed claim is now true under negation. (#2) the embedder/reranker ONNX session is **cached per-alias** (module-level promise-cache, build extracted to `buildEmbedder`/`buildReranker`) — pre-rc.38 EVERY `obsidian_search` rebuilt the ~110–120MB session (undermining the sub-10ms claim; N concurrent authed queries → N simultaneous sessions). **1168 → 1170 source tests.** **(m) rc.39** — the MANDATED post-rc.36 ReDoS re-sweep + its fix (HIGH). The re-sweep (a broader-generator 20k fuzz than the CI one) confirmed rc.36's fix is sound BUT surfaced the **inherent undecidable residual**: 80 SAFE-classified nested patterns (e.g. the 37-char `\W?(([ca]*?){0,3}|c{2,5}b{2,5}){0,3}$`) genuinely hang V8 — NOT an rc.36 regression, the long-standing limit of ANY static detector. **Maintainer chose the architectural class-ender: bound the SINK.** `getOpenQuestions` now matches a CALLER pattern on a WORKER THREAD with a hard wall-clock budget (`matchLinesBounded`, `MAX_QUESTION_SCAN_MS`=5000) → the main event loop can NEVER hang for any pattern; a pattern that blows the budget is rejected fail-closed. `isCatastrophicRegex` stays as the cheap pre-filter; default pattern stays inline. **This PERMANENTLY ends the 4×-recurring ReDoS class** — the right move per the project's own "fix the class architecturally, don't chase EDA-precise detection (undecidable, its own bug surface)" rule. **1170 → 1174 source tests** (matchLinesBounded unit incl. the detector-missed-pattern-rejects-within-budget + a getOpenQuestions end-to-end sink-bound integration). **Lesson:** a static analyzer for an UNDECIDABLE property (ReDoS) is forever incomplete — the durable fix is to bound the SINK (so no input can exceed a budget), not to keep extending the analyzer. **(n) rc.40** — the full LOW/INFO audit tail (closes the wq9ml34gr workflow-audit completely): **#6** watcher close()-window race — stop the chokidar watcher FIRST + early-return onChange/handle when `closed`, so an edit landing mid-shutdown can't apply a live diff the just-flushed sidecar misses; **#7** `flushHnswToDisk` clears `hnswDirty` BEFORE the saveTo await (re-set on failure) so a concurrent applyDiff isn't clobbered; **#9** `--stale-days` honesty — help/`ServeOptions` TSDoc/`docs/api.md` no longer claim it controls the `stale` flag (the flag is hardcoded 365; the flag only tunes recency RE-ranking); **#11** K-3 now derives fs/vault-mutating exported handlers from `write.ts` source (`fsMutatingExports`) and asserts ⊆ `KNOWN_WRITE_HANDLERS` — a new mutator wired READ_ONLY can't silently advertise readOnlyHint (+ NEGATIVE control); **#12** `resource-bound` `discoverScanners` now also matches `Promise.all`/`.map(async`/`for await` fanout (not just `for (`) so a pure-parallel whole-vault reader can't escape the cap-or-exempt gate (+ NEGATIVE fixture); **#13** `eval` `hits_relevant` Set-dedup (mirrors rc.33; INFO, unreachable at note granularity); **#14** dropped the unused `id-token: write` from `dist-tag-cleanup.yml` (least-privilege). All LOW/INFO, behavioral impact bounded (the watcher races were lost-fast-reload-only; the test-infra gaps were latent). **1174 → 1177 source tests.** **(o) rc.41** — counter-positioning docs (caura-memclaw study output): the "Grounded, not extracted" hero (README + COMPARISON) only countered the *chat-memory* cohort (mem0/Zep/Supermemory); sharpened to ALSO distinguish from the *server-fleet*-memory category (multi-tenant cloud stores that paraphrase agent traffic) — enquire is **single-user, local-first, zero cloud calls during serve**. Docs-only, no count/claim-surface change. (Marketing — blog/landing/Discord — stays maintainer-driven; never committed to the public repo.) **1177 source tests unchanged.** **(p) rc.42** — full state-driven RE-AUDIT (4 parallel lenses: code · docs · tests/CI · security/privacy) of the shipped rc.35→rc.41 line + every surface. The code re-sweep confirmed rc.35→rc.41 sound, BUT **the two newest RCs each recursed a documented class** (the post-merge re-sweep working as designed) + 2 docs drifts: **F1 [HIGH]** rc.41's "zero cloud calls during serve" was ASPIRATIONAL not ENFORCED — embedder/reranker `loadEmbedder`/`loadReranker` could CDN-fetch on a local-cache miss (same claimed-guarantee-vs-code-guard class as overclaim #16/OCR, left un-closed for the ML path). Now serve/serve-http call `setEmbeddingsOffline()` → transformers.js `env.allowRemoteModels=false` → cache-miss fails CLOSED (`offlineModelLoadError` + install hint); build-embeddings/install-model unaffected; **OIA Check 4f** mirrors 4e to regression-proof it; SECURITY.md documents the enforcement. **F2 [MED]** rc.40's own K-3 `fsMutatingExports` derive-check was scope-too-narrow — scanned only write.ts, but `chatThreadAppend` (a WRITE handler) lives in read.ts, so a new read.ts mutator would escape BOTH it AND the layer-1 scan; now unions `WRITE_HANDLER_SOURCES=[write.ts,read.ts]` (+ chatThreadAppend sanity assert + archiveNote delegating-blind-spot documented). **F3/F4 [LOW]** QUICKSTART `44-tool`→45 (+ scope-completeness hyphen-pattern + QUICKSTART scope, closing the class) + README.zh.md re-synced (test-count `1100+`→`1170+`, rc.41 fleet-memory clause added). **1177 → 1181 source tests** (+4 `tests/embeddings-offline.test.ts`). **Lesson (reinforced for the Nth time): a multi-lens state-driven re-audit AFTER shipping is non-negotiable — change-driven sweeps are blind to exactly the drifts that ship, and an audit-driven fix routinely recurs its own class in the very next RC (here rc.40 AND rc.41 both did). Catch it in-house, close with a structural defense, not an instance patch.** **(q) rc.43** — first batch of the 14-lens **ultracode full-project audit** (129 agents, adversarial 3-judge verify; **0 CRIT / 0 HIGH** confirmed — exceptionally clean for a 20+-round base; 9 MED / 24 LOW / 4 INFO + 2 critic gaps). rc.43 = retrieval/i18n-correctness code MEDIUMs: **M1** folder-prefix filter matched ZERO rows for emoji (astral-char) folder names — JS `prefix.length` (UTF-16 units) bound to SQLite `substr(…,1,?)` (code points); fixed BOTH `fts5.ts`+`embed-db.ts` → `substr(…,1,length(?))` binding the prefix twice (+emoji tests both). **M7** `computeModularity` summed the Newman null-model penalty ONLY over ADJACENT same-community pairs → inflated Q that could rank a degenerate single community above the correct split; rewritten to the exact `Σ_c (in_c/2m − (tot_c/2m)²)` (verified split Q=0.3571 > all-in-one, was inverted) + exported + tested. **G1** (critic gap) no NFC/NFD normalization → `[[café]]` (NFC) never resolved to `café.md` (NFD, macOS); new `foldKey()` NFC-folds every wikilink index key + query in `findBestMatch` (+ tests). **1181 → 1189 source tests.** Follow-ups: rc.44 (serve/watcher/OCR robustness — M2 canvas-OOM hard-cap, M5 watcher-sync-dead-when-ftsIndex-null, M6 shutdown FTS-before-watcher reorder, G2 walk depth-bound), rc.45 (privacy/tool-handler — M3 path-leak root-class, M4 frontmatter fidelity, chatThreadAppend range, `--late-chunk-context 0`), rc.46 (docs/test-infra/script drift batch — M8 files[], M9 + ~18 LOW/INFO + structural guards). **(r) rc.44** — audit batch 2/4, serve/watcher/OCR robustness: **M2** `clampOcrScale`'s 0.1 floor defeated `MAX_OCR_CANVAS_DIM` for a MediaBox >50,000pt (1,000,000pt → 100,000px → ~40GB canvas OOM, falsifying the SECURITY guarantee); dropped the floor (cap-derived ratio is the safe ceiling; requestedScale is [0.5,4]-clamped upstream) + hard-cap the final canvas pixels at the call site. **M5** watcher embed-db + HNSW live-sync was silently DEAD whenever `ftsIndex` was null (the `!ftsIndex` early-return preceded all embed/HNSW logic) while server.ts printed "enabled" banners; now early-return only when BOTH fts AND embed-db are null, every `ftsIndex` call optional-chained. **M6** `shutdownHttpServer` closed FTS5 BEFORE draining the watcher → in-flight chokidar events wrote to a closed handle; reordered to drain watcher → close watcherEmbedDb → flush cache → close fts LAST (mirrors shutdownStdioDeps). **G2** (critic gap) `walk`/`walkAnyExt` had no recursion-depth bound (cap applies only AFTER full traversal); added `MAX_WALK_DEPTH=64`. **INFO** `ocrLangIsInstalled` accepted a `.gz`-only pack the `gzip:false`/`readOnly` worker can't read → require uncompressed `.traineddata`. M2 + ocrLang tests updated (both had encoded the buggy floor / `.gz` acceptance — the "test pinned the bug" pattern). **1189 source tests unchanged** (both rewrites). **New env lesson:** this shell is `zsh`, where `status` is read-only — never name a poll/loop var `status`. Maintainer-gated next: v3.10.0→`@latest` (fresh ≥2-auditor pass), published LongMemEval score. **caura-memclaw study (this session): the remaining borrowable idea = closed-loop retrieval feedback ("Karpathy Loop", a `mark_useful`-style tool) — a NET-NEW persistent-state feature with data-at-rest + erasure-invariant implications, best sequenced AFTER the @latest promotion (so the promotion audit isn't complicated) and with a privacy-design pass; captured for the v3.10.x feature line, not shipped reactively.** **(s) rc.45** — audit batch 3/4, the **abs-path-leak CLASS** (HIGH + privacy brand). The RCA-driven re-sweep proved rc.43's **G1** (NFC) and the audit's **M3** (path-leak) were INSTANCE fixes, not CLASS fixes — sibling leak sinks were live. Root-fix at the source: **`src/vault.ts`** gains `private sanitizeFsError(err)` (strips `this.root`+sep from `err.message`/`err.path`/`err.dest`, preserves `err.code` + the ENOENT message shape) wrapping `stat`/`readFile`/`readBinaryFile` — so EVERY caller (M3's `Vault.stat` + the `chatThreadRead` sibling + all others) returns vault-relative errors to MCP clients instead of echoing the absolute vault path (fingerprinting on serve-http). Two more sinks of the same class: **`src/ocr.ts`** `assertOcrLangsInstalled` dropped `${dir}` from its install-hint throw (HIGH — leaked the host home/tessdata dir); **`src/embeddings.ts`** `offlineModelLoadError` dropped the raw transformers.js cause (it embeds the absolute model-cache path under `~/.cache/huggingface`). Tests that had PINNED the leaked text (asserted the raw cause / abs path was present) were flipped to `not.toMatch` + a NEGATIVE control proving `/Users/secret/.cache/huggingface` is absent while the slash-free model id is still named (the "test pinned the bug" pattern again). **1189 → 1191 source tests** (+2: `tests/security.test.ts` Vault error-message-privacy describe; embeddings NEGATIVE control). **Lesson:** a privacy/leak fix is NOT a class fix until the eraser/sanitizer sits at the SOURCE every caller funnels through — patching the one sink the auditor named (M3) leaves the siblings (ocr/embeddings/chatThreadRead) live; same shape as the rc.37 erasure-surface gap and the rc.43 NFC-instance gap. The matching structural defense (an OIA check that greps error throws for `vault.root`/abs paths) is sequenced into rc.49 (audit-hardening batch). **(t) rc.46** — the **NFC name-resolution CLASS** (pulled forward ahead of the planned docs/test-infra batch — a correctness class outranks docs drift). rc.43's `foldKey()` (G1) was an INSTANCE fix; an RCA + exhaustive signature sweep found the SAME Unicode NFC/NFD bug live in **14 name-comparison sites across 5 files**: `communities.ts` wikilink-graph build+lookup (106/127), `vault.ts` `findByTitle`/`findAllByTitle` (both sides), `bases.ts` `linksTo` outbound-build + query + `file.name ==` want/got, `tools/meta.ts` lint_vault_wiki titleSet build+lookup, `tools/search.ts` title 3-grams, `tools/write.ts` `suggestSimilar` (the sweep caught search.ts + write.ts which the RCA had MISSED). Every one folded a note name with `.replace(/\.md$/i,"").toLowerCase()` or `stripMd(…).toLowerCase()` but NO `.normalize("NFC")` — so on macOS (APFS returns NFD; wikilinks/titles are NFC) an accented name silently failed to resolve. Fixed at the root: new dependency-free leaf module **`src/name-fold.ts`** exporting `foldName(s) = s.normalize("NFC").toLowerCase()`; all 14 sites + meta.ts's `foldKey` (now `foldName(stripMd(s))`) route through it. **Structural defense (P0 inventory invariant): `tests/name-fold-invariant.test.ts`** — a pure detector greps every `src/**/*.ts` for the `.md`/`.base`-strip-then-lowercase + `stripMd*().toLowerCase()` signatures and fails CI on any unfolded site (+ NEGATIVE control proving the detector isn't vacuous + a `foldName` NFC/NFD/ASCII/over-folding unit). Same transform as the rc.25 ReDoS fuzz / rc.36 resource-bound manifest: convert "did we NFC-fold every name comparison?" (recursion-prone) into a self-checking gate. **+5 tests (1191 → 1196).** **Lesson:** the foldKey INSTANCE fix never triggered a sweep of its siblings — and even the RCA's hand-enumeration missed 2 of 14; only an exhaustive signature grep found them all, which is exactly why the durable fix is the inventory invariant, not the 14 edits. Docs/test-infra/script drift (M8 files[], M9, ~18 LOW/INFO) re-sequenced to rc.48 (rc.47 took the higher-priority correctness class). **(u) rc.47** — the **range-arithmetic (body-relative line-number) class** (2 MEDIUM, RCA-confirmed). `obsidian_open_questions` (`getOpenQuestions`) and `readNote(format:"map")` (`extractHeadings`) both indexed line numbers on `parsed.body` (frontmatter-STRIPPED) and emitted `i + 1`, so for any note with YAML frontmatter the reported `line` was short by the frontmatter length — an agent jumping to it lands too early. Both now use the parser's already-exposed `bodyStartLine` → `bodyStartLine + i` (file-absolute; `bodyStartLine` is 1 with no frontmatter so frontmatter-less notes are unchanged). Tests: a frontmatter'd `getOpenQuestions` note whose marker is on file line 8 asserts `line===8` (not the old body-relative 4); the existing `readNote map` test now asserts `headings[0].line===6` (not 2). **+1 source `it()` (1196 → 1197)** (the map assertion is added in place). **Refuted/deferred from this batch** (RCA verdicts): `embeddingsSearch` `db.open()` path-leak = **refuted** (rc.34 `peekEmbedDbMeta` fail-soft already guards it); `frontmatterSet` trailing-newline = real but LOW + distinct (roundtrip-serialization-fidelity) → **rc.48**; `chatThreadAppend` line_end + `--late-chunk-context 0` = not RCA-confirmed, left unchanged. **Reconciliation of the 58-agent RCA (`wl76qny44`, 13 confirmedNew):** error-path-info-leak ×3 → rc.45 ✅; encoding-semantic-space/NFC ×4 (of 14 swept) → rc.46 ✅; range-arithmetic ×2 → rc.47 ✅ (this); help-parser-tsdoc-drift ×3 + structural-defense-scope ×1 (prompts-in-api invariant) → rc.48; the meta's P0 hardening (name-resolution NFC inventory — already delivered as `name-fold-invariant.test.ts`; abs-path-leak OIA check; meta-meta behavioral scope-completeness) → rc.49. **(v) rc.48** — the **docs/TSDoc-drift batch** (RCA `help-parser-tsdoc-drift` ×3 + `structural-defense-scope` ×1 + the refuted-but-real frontmatter LOW). **Overclaim #16 OCR residual**: `tool-registry.ts`'s `obsidian_ocr_pdf` description + inline `lang`-schema comment still claimed trained-data "download on first use" — but rc.10 made OCR offline-enforced (`assertOcrLangsInstalled` fail-closed + `cacheMethod:"readOnly"`); rewritten to the enforced reality (pre-install via `install-ocr-lang`). `server.ts` `ServeOptions.enableWrite` TSDoc named two NON-existent tools (`obsidian_append_note`/`obsidian_rename_file` → real `obsidian_append_to_note`/`obsidian_rename_note`). `STABILITY.md` always-on header `(33)`→`(34)` (the list under it always had 34). `package.json` `files[]` dropped the dead `docs/api-reference` (GH-Pages-generated, never git-tracked, never produced before `npm pack` — `prepublishOnly` has no `docs:api`). **`frontmatterSet` trailing-newline fidelity** (`write.ts`, roundtrip-serialization class — RCA-refuted from the swept classes but confirmed REAL/LOW): `matter.stringify` always appends a `\n`; a frontmatter-only edit silently added one to a body saved without it. gray-matter `.content` preserves the original state (verified empirically), so the fix drops the added `\n` iff the original lacked it. **Structural defense**: the docs/api.md prompts table was stale at **10 of 19** with NO invariant pinning it (`structural-defense-scope`) — backfilled the 9 missing prompts + a new `docs-consistency` invariant asserts every `registerPrompt()` ∈ the api.md prompts section (+ NEGATIVE control). **1197 → 1200 source tests.** **Deferred to its own RC**: `pdfjs-dist` 5→6 (PR #177) — a MAJOR dep bump needs isolated install + PDF-render + OCR-canvas verification, not coupling to a clean docs batch. **Lesson**: an enforcement-claim fix (rc.10 OCR offline) isn't complete until EVERY surface that describes the old behavior is swept — the tool-registry description + inline comment drifted ~38 RCs past the code change because the rc.10 sweep fixed SECURITY.md/api.md but not the registry prose; the rc.49 enforcement-verb OIA check is the structural close. **(w) rc.49** — **abs-path-leak class TRUE root closure** (re-audit HIGH `RC45-WRITEPATH-LEAK` + MED `CODE-1`). A multi-lens state-driven re-audit (6-lens workflow, adversarial verify) of the shipped rc.45→rc.48 line found rc.45 **recursed its own class**: it wrapped 3 READ sinks and CLAIMED closure "at the SOURCE every caller funnels through", but the WRITE path (`writeNote`/`renameFile`/`appendNote`) + `readNote` (the primary read funnel) still threw RAW fs errors embedding the host abs path to bearer-auth serve-http clients. Root fix in `src/vault.ts`: private sanitizing wrappers (`statSafe`/`realpathSafe`/`readFileSafe`/`writeFileSafe`/`mkdirSafe`/`openSafe`/`renameSafe`/`linkSafe`/`copyFileSafe`/`unlinkSafe`) — **all 27 raw `fs` sinks in the Vault class** (incl. cache/startup internals) now route through them (`err.code` preserved → EEXIST/EXDEV flow intact). **P0 inventory invariant `tests/abs-path-leak-invariant.test.ts`**: parses the Vault class, fails CI on any method with a raw non-`.catch` `fs` sink lacking `sanitizeFsError` (+ NEGATIVE control) — the next sink cannot escape. **The behavioral leak test caught a residual the static sweep MISSED**: `resolveSafePath`'s `realpath` leaked `ENOTDIR` with the abs path (I'd excluded realpath assuming it was always `.catch`'d) → added `realpathSafe` + included `realpath` in the invariant. **Lesson: a leak-class fix needs a BEHAVIORAL test (force the error, assert no root), not just a static grep — and rc.45 proves an INSTANCE fix that claims class-closure is the project's signature recursion; the inventory invariant is what actually ends it.** **1200 → 1204 source tests.** Re-audit tail: rc.50 (js-yaml phantom dep MED + chatThreadAppend line drift LOW), rc.51 (docs drift: api.md `--hnsw-persist` phantom flag + SECURITY body-cap). **(x) rc.50** — re-audit batch 2 (supply-chain + line-drift). Declaring the previously-phantom **`js-yaml`** dep (SC-PHANTOM-JSYAML-01 MED: `bases.ts` `await import("js-yaml")` for `.base` parsing was undeclared, resolved only via gray-matter's transitive pin + npm hoisting) surfaced that **`npm audit` had gone red PROJECT-WIDE** — two NEWLY-PUBLISHED advisories on transitive deps (the bare gate at rc.49 passed; the advisory DB updated since): **protobufjs** high+moderate (GHSA-wcpc-wj8m-hjx6 / -f38q-mgvj-vph7, via the dev/optional `@huggingface/transformers`→onnxruntime) and **js-yaml** moderate (GHSA-h67p-54hq-rp68 merge-key DoS, via gray-matter). protobufjs is FIXED (`overrides`→7.6.4, in-range, no break). js-yaml has NO fix that doesn't break gray-matter (v4 removed `safeLoad`, which gray-matter's engines.js binds at import), so it's ACCEPTED via a new **scoped audit gate** `scripts/check-audit.mjs` (replaces the bare `npm audit` in prepublishOnly + ci.yml + release.yml; same thresholds prod≥moderate/dev≥high; fails on every advisory EXCEPT documented ALLOWLIST entries, each with rationale + resolution path). Also **CODE-2** chatThreadAppend line_start/line_end drifted past EOF (counted newlines in the un-stripped body while the write strips trailing newlines) → count in the trimmed written string + line_end by newline-count not split-length (fixed the new-note branch sibling too). New guards: `tests/phantom-import-invariant.test.ts` (every dynamic `import()` root must be a declared dep) + `tests/check-audit.test.ts` (gate pure-core + allowlist drift guard). **1204 → 1210 source tests.** **Lesson:** declaring a phantom dep is the RIGHT fix even when it surfaces a pre-existing transitive advisory the bare gate was about to flag anyway; and a security gate needs a *documented-exception* mechanism (accept-with-reasoning) so one un-fixable advisory doesn't force lowering the bar for everything. **The ci.yml/release.yml changes need a maintainer web-UI merge (CI token lacks `workflow` scope).** rc.51 = docs drift (api.md `--hnsw-persist` phantom flag + SECURITY body-cap). **(y) rc.51** — re-audit batch 3 (FINAL), docs-drift LOWs + class-D guards. Two claim-vs-reality drifts the gates missed: `docs/api.md` documented a **phantom `--hnsw-persist`** flag (only `--no-hnsw-persist` exists; persistence is opt-out default-on) and `SECURITY.md` claimed a **fixed 4 MB** HTTP body cap while `deriveHttpBodyCap` computes `max(4 MB, max-file-bytes×1.5)`=7.5 MB at default (http-transport.md was already correct). Both fixed + **`tests/cli-flag-docs-invariant.test.ts`**: scans api.md `--flag` tokens, asserts each ∈ real cli.ts `.option()` set (+ NEGATIVE control flagging `--hnsw-persist`), and pins the SECURITY body-bomb line to the derived wording. OIA Check 3 validates subcommands, not `--flag` prose — this closes that gap. **1210 → 1213 source tests.** **This CLOSES the 6-lens rc.45→rc.48 re-audit (1 HIGH + 2 MED + 3 LOW all shipped: rc.49 abs-path-leak class, rc.50 js-yaml/protobufjs/scoped-audit-gate/chatThreadAppend, rc.51 docs).** Tracked follow-ups (not reactive): js-yaml advisory de-allowlist (gray-matter YAML-engine migration → de-allowlist GHSA-h67p-54hq-rp68), pdfjs-dist 5→6 (PR #177), v3.10→@latest promotion (fresh ≥2-auditor pass). **(z) rc.52** — dependency bumps. **pdfjs-dist `^5.7.284→^6.0.227`** (closes the rc.48-deferred major + dependabot #177): v6 engines `>=22.13.0` match ours exactly; we consume the Node `legacy/build/pdf.mjs` + `getDocument`, stable across the major (in-the-wild v6 pain is webpack/ESM bundling, N/A to our dynamic import) — verified by the full 55-test PDF/OCR suite (real fixtures) + `isPdfjsAvailable()` + a real-extraction smoke. The bump's `npm install` re-resolved **hono** and surfaced **5 newly-published advisories** on `hono<=4.12.24` (prod transitive via MCP SDK→@hono/node-server: 1 HIGH CORS-wildcard-with-credentials + 4 MED Lambda/serve-static — mostly Lambda-adapter-specific, N/A to our Node serve-http) → fixed in-range by bumping the existing `overrides` `hono ^4.12.21→^4.12.26` (no major). **Tests unchanged (1213).** **Lesson (reinforced 3rd time, after rc.50 js-yaml/protobufjs): a routine dep bump's `npm install` re-resolves the whole tree against the LIVE advisory DB — expect it to surface unrelated newly-published transitive advisories, and treat the scoped-audit-gate red as a feature (it caught hono), not noise.** Remaining tracked: js-yaml advisory de-allowlist (gray-matter YAML-engine migration), v3.10→@latest promotion (fresh ≥2-auditor pass). **(aa) rc.53** — **dropped gray-matter → js-yaml@4; the js-yaml advisory is RESOLVED at the root, not allowlisted** (closes #170). gray-matter@4 hard-binds js-yaml@3's removed `safeLoad`/`safeDump` at module load, which PINNED the vulnerable js-yaml@3 (GHSA-h67p-54hq-rp68) in the tree with no v3 fix → the only way to clear the advisory was to remove gray-matter. New in-repo **`src/frontmatter.ts`** (`parseFrontmatter`/`stringifyFrontmatter`) is a faithful PORT of gray-matter's split + stringify (the `---` delimiter, `----` 4-dash guard, comment-only-emptiness check, CR/LF strip after the closing fence, UTF-8 BOM strip, `newline()` join) on **js-yaml@4.2.0** (`load`/`dump` = the v3 safe semantics; `@types/js-yaml@4` added). Swapped at all 4 call sites (parser/meta/write/bases; bases `parseBase` dropped `SAFE_SCHEMA` — v4 `load` is safe-by-default). `content` stays a verbatim suffix so parser.ts `bodyStartLine` `lastIndexOf` holds. **De-allowlisted GHSA-h67p-54hq-rp68** → `check-audit.mjs` ALLOWLIST empty again (strictest posture; drift-guard test asserts `[]`). **1213 → 1224 source tests** (+11 `tests/frontmatter.test.ts`). **Verification method that made a parser-replacement safe: a DIFFERENTIAL test asserting byte-identical `{data,content}`+stringify vs gray-matter over a broad corpus BEFORE removing it (then deleted — it imported the removed dep); the shipped guard is standalone. Lesson: a migration's real verification is the differential diff vs the incumbent, not the new code's own tests — and even the diff corpus had a gap (BOM), caught only by the pre-existing parser BOM test.** _(Corrected in (bb): the "byte-identical" claim holds for STRUCTURAL parsing only — the corpus also missed the SCALAR-resolution dimension; see rc.54.)_ **(bb) rc.54** — **frontmatter migration HARDENING** (post-rc.53 state-driven audit; closes overclaim **#20** + a real corruption bug — exactly the class the marathon's "audit after each stage" exists to catch). The audit confirmed the migration is fundamentally SOUND but rc.53 was NOT the "byte-identical port" it claimed (**#20**, the signature claimed-guarantee-vs-reality class): js-yaml@4 is **YAML 1.2**, gray-matter's js-yaml@3 was **YAML 1.1**, so SCALAR resolution diverges — bare octal `0755`→755 (was 493), `0888`→888 (was "0888"), sexagesimal `12:34:56`→string (was 45296), underscore `1_000`→string (was 1000); the rc.53 differential corpus had none of these shapes so the diff "passed" while incomplete on the scalar dimension (same shape as the rc.53 BOM gap + the rc.36 fuzz-generator gap — the corpus couldn't PRODUCE the divergent shape). "Byte-identical" is now scoped to STRUCTURAL parsing, the divergence documented in the `frontmatter.ts` header + **pinned as a deliberate contract** in `tests/frontmatter.test.ts` (4 scalar-contract `it()`s). **Real corruption fix (FM-SCALAR):** a NON-mapping top-level frontmatter doc (bare scalar `---\nhello\n---` or a sequence) was cast to `Record` → `frontmatter_set` would spread it char-indexed and write corrupt YAML back; now coerced to `{}` (gray-matter parity, +2 guards). **Enforcement-claim fix (SECURITY.md `.base` threat-model):** claimed "`SAFE_SCHEMA` … no anchor-expansion … YAML bomb rejected at parse time" — but js-yaml (v3 AND v4) resolves anchors/aliases and has no billion-laughs guard, a PRE-EXISTING overclaim (claimed-guarantee-vs-code-guard); rewritten to enforced reality (safe-by-default core schema, no `!!js/function`; merge-key DoS fixed in v4; alias bombs NOT specifically rejected — bounded only by the single-user local-vault threat model). **gray-matter doc-drift sweep:** README/SECURITY/api.md/CONTRIBUTING/typedoc/read.ts+write.ts TSDoc still named gray-matter as the live engine ~1 RC after removal → all → js-yaml@4 (CONTRIBUTING mandatory-dep list gray-matter→js-yaml; typedoc 44→45). **SC-1:** `@huggingface/transformers` was in BOTH dev+optional deps → removed the dev dup (runtime-optional; also puts its protobufjs subtree in prod audit scope, already covered by the 7.6.4 override). **Structural defense:** `tests/no-graymatter-invariant.test.ts` fails CI if gray-matter reappears as a declared dep OR is imported in any `src/**/*.ts` (+ NEGATIVE control; prose mentions allowed). CHANGELOG rc.53 "byte-identical" corrected inline. **1224 → 1233 source tests.** **Lesson: a parser migration's differential corpus must cover the SCALAR-RESOLUTION dimension, not just the structural split — two parsers can be byte-identical on delimiters/stringify and still resolve `0755` differently; "byte-identical port" is precisely the enforced-guarantee claim that needs the audit-after-each-stage gate.** **(cc) rc.55** — **independent code-correctness batch** (3 findings from the rc.53 audit + 1 post-rc.54-audit recursion). **CT-LINE-OFFBY1 [MED, recurrence of rc.50 CODE-2]**: `chatThreadAppend`'s `line_start` pointed one line BEFORE the appended `### role · ts` heading (the prior content line / a blank line), and the new-note branch hardcoded `4` — actually the blank line, the heading is line 5. Unified all 3 branches to derive `line_start`/`line_end` from the heading marker's offset in the FINAL written content (rc.50's no-past-EOF property preserved). **CHUNK-SURROGATE-SPLIT [MED]**: `chunkContent`'s oversize-line hard-cut used `slice(i, i+maxChars)` on UTF-16 code UNITS, so a boundary mid-emoji emitted a lone surrogate (corrupt code point) into the indexed chunk; now backs the cut off by one when the boundary unit is a high surrogate (chunk may be maxChars−1; no pair ever split; re-join is lossless). **OPTDEP-MODULE-PATH-LEAK-02 [HIGH, abs-path-leak sibling outside rc.49's Vault scope]**: the optional-dep `import()` catches in `ocr.ts`/`pdf.ts`/`embeddings.ts` interpolated `err.message` into the thrown Error — Node's ERR_MODULE_NOT_FOUND message EMBEDS the importing file's ABSOLUTE path ("imported from /Users/.../dist/ocr.js"), leaking the host layout to bearer-auth serve-http clients. New leaf `src/optional-dep.ts` `optionalDepDetail(err)` surfaces only the error CODE; all 6 import-catches route through it. Structural defense: `tests/optional-dep-leak-invariant.test.ts` (curated loader inventory: a raw `${err.message}`/`${String(err)}` interpolation in any listed loader fails CI + NEGATIVE control). **FM-SCALAR-DATE [post-rc.54-audit recursion]**: rc.54's FM-SCALAR coercion (`typeof === "object" && !Array.isArray`) let a bare top-level Date scalar (`---\n2026-01-01\n---`, which js-yaml resolves to a `Date`) slip through as `data` — a recursion of the very class rc.54 closed; tightened to a PLAIN-object check (`isPlainObject`: rejects Date/RegExp/array/null) + a Date-scalar regression test. **1233 → 1239 source tests.** **Lesson: the post-merge audit-after-each-stage caught rc.54's own coercion recursing one RC later — the inventory invariant for the leak sibling (optional-dep) is the durable close, mirroring rc.49's Vault leak invariant.** **(dd) rc.56** — **CLOSES the rc.53 audit** (its final 2 LOW of 21; rc.54 closed 16, rc.55 closed 3). **RS-3 [LOW]**: `docs/QUICKSTART.md` (×2) still cited `pdfjs-dist@5.7+` as the Node-floor lowest-common-denominator after the rc.52 5→6 bump → `pdfjs-dist@6+`. **FM-3 [LOW, documented-rejection verdict]**: tab-indented YAML frontmatter throws on js-yaml@4 ("tab characters must not be used in indentation") → `parseNote` falls back to whole-body. Verified NOT a migration regression — the YAML spec forbids tabs for indentation and js-yaml@3 (gray-matter) enforced it identically; AND the frontmatter TEXT stays indexed/searchable in the body (no data loss), it just isn't parsed into `data`. Documented in the `frontmatter.ts` header + pinned with a throw-contract test (rather than "fixing" a non-bug — the documented-rejection pattern). **1239 → 1240 source tests.** **This concludes the entire rc.53→post-rc.54 audit cascade (21 confirmed findings + 1 recursion, all shipped or reasoned-rejected across rc.54/rc.55/rc.56).** **(ee) rc.57** — **fresh 8-lens ultracode audit (Batch A+B, security).** A new from-scratch multi-lens workflow-audit (code-correctness · concurrency · resource-DoS · security-privacy · MCP-contract · frontmatter-migration · tests-CI · supply-chain; 24 agents, 3-skeptic adversarial verify) of the shipped rc.54→rc.56 line returned **5 confirmed findings (1 HIGH / 1 MED / 3 LOW)**. rc.57 ships the two SECURITY ones: **DQL-PARSE-QUADRATIC-DOS [HIGH]** — the always-registered, read-only `obsidian_dataview_query` had `query: z.string().min(1)` with NO `.max()` feeding `splitClauses`'s `input.slice(i).toUpperCase()`-per-whitespace O(n²) tokenizer → a long query pins the main event loop (CPU-DoS for all serve-http clients). Fixed: `MAX_DQL_QUERY_LEN=4096` fail-closed in `parseDql` (the shared sink) + zod `.max()` at the boundary + **linearized** `splitClauses` (fixed-length per-keyword compare, no whole-tail slice/upcase) + new **`tests/parser-input-cap-invariant.test.ts`** (curated: every parser-fed always-on tool input — open_questions, dataview_query — must carry `.max(<cap>)`; + NEGATIVE control). **OPTDEP-SQLITE-PATH-LEAK-EMBEDDB [MED]** — a NEW instance of the rc.55 OPTDEP-MODULE-PATH-LEAK class: `embed-db.ts` + `fts5.ts` `await import("better-sqlite3")` loaders interpolated raw `err.message` (a multi-line `${…}` my rc.55 line-oriented grep-sweep missed) → Node's `ERR_MODULE_NOT_FOUND` abs path reaches serve-http clients via `signal_errors.embeddings`. Fixed: both loaders (outer import + inner native-probe) route through `optionalDepDetail`; **extended the `optional-dep-leak-invariant` inventory** from 3→5 files (the rc.55 inventory was scope-too-narrow — the signature "instance fix ≠ class fix"; the JS detector was always multi-line-capable, only the inventory was incomplete). **1240 → 1246 source tests.** **Lesson: my rc.55 sibling-sweep used a LINE-oriented `grep` that structurally cannot see a multi-line `${…}` interpolation — the audit's code-path reading found it; an inventory invariant (covering the files) is the durable close, not a one-off grep.** **(ff) rc.58** — **fresh-audit Batch C+D (3 LOW correctness), CLOSES the fresh 8-lens audit.** **CT-LASTINDEXOF-COLLISION [LOW, recursion of rc.55]**: `chatThreadAppend`'s rc.55 `newBody.lastIndexOf(headingMarker)` could match a byte-identical `### role · ts` line embedded in `args.content` (same-second timestamp) → `line_start` into user content / past EOF. Anchored the offset to the APPENDED block (`trimmed.length + toAppend.indexOf(headingMarker)` — `toAppend` has exactly one heading and it always precedes any content copy), collision-proof. **CONC-1 [LOW]**: `chatThreadAppend` is read-modify-WRITE (not atomic O_APPEND like `Vault.appendNote`), so two concurrent appends to the SAME note are a lost-update window; the TSDoc's understated "last-write-wins" note is rewritten to an explicit CONCURRENCY CONTRACT (callers must serialize same-note appends; structural fix via atomic append deferred — the heading-injection/new-note branches need a full write). **FM-DATE-SILENT-MUTATION [LOW]**: js-yaml resolves a bare `created: 2026-01-15` to a midnight-UTC `Date`, and `dump` re-serialized it as `2026-01-15T00:00:00.000Z` — so a `frontmatter_set` on an UNRELATED key silently appended a time to every bare date (breaking date-only Dataview queries) AND falsified the rc.54 header's "ISO dates … unaffected" claim. `stringifyFrontmatter` now deep-walks `data` and renders a midnight-UTC Date as `YYYY-MM-DD` (a genuine non-midnight timestamp is left full-ISO); header claim corrected; +round-trip test (POSITIVE) + non-midnight NEGATIVE control. **1246 → 1249 source tests.** **This CLOSES the fresh 8-lens audit (5 confirmed: 1 HIGH + 1 MED + 3 LOW, all shipped across rc.57+rc.58).** **Lesson: rc.58 closed a recursion of rc.55 (CT-LASTINDEXOF) and a residual of rc.54's own header overclaim (FM-DATE), exactly the "audit-driven fix recurs its own class" pattern the post-merge re-sweep exists to catch — this is now the 3rd recursion-pair this session (rc.54→FM-SCALAR-DATE, rc.55→OPTDEP-SQLITE, rc.55→CT-LASTINDEXOF).** **(gg) rc.59** — **post-rc.58 re-sweep finding: a 6th OPTDEP-leak sibling + the detector's own blind-spot.** The mandated post-merge re-sweep found `hnsw.ts`'s `loadHnswlib` (`await import("hnswlib-node")`) leaks the importing file's abs path via a `const msg = err.message; …throw new Error(\`…${msg}\`)` **INDIRECTION** — which the rc.57 `optional-dep-leak-invariant` detector was structurally BLIND to (it matched only DIRECT `${err.message}`), AND which the 8-lens audit + my rc.55 line-grep both missed. RCA: the throw is **fail-soft-caught server-side** (server.ts brute-force fallback → operator stderr, not the client), so it's **LOW** — but the fix + detector-strengthening are the durable win. Fixes: (1) `loadHnswlib` → `optionalDepDetail`; (2) added `hnsw.ts` to the loader inventory (3→6 files); (3) **strengthened the detector to be indirection-aware AND throw-scoped** — it now flags `err.message`/`String(err)` (direct) OR any `const`-captured var interpolated inside a `throw new Error(...)`, while NOT flagging server-side `process.stderr.write(…${msg})` (a const-msg used only in a stderr log is operator-side, not a client leak); NEGATIVE control extended to prove the indirection IS caught and the stderr case is NOT. `tools/search.ts`'s `signalErrors.* = msg` chokepoint was investigated — its upstream import/model sources are all sanitized (rc.45/rc.57), so it is fed path-free; left as-is (not a confirmed live leak). **1249 source tests unchanged** (the detector rewrite + NEGATIVE-control expansion stay in the existing `it()`s). **Lesson: a detector for a leak class must model the SINK (a thrown Error reaching the client), not a surface token (`${err.message}`) — the rc.57 detector caught the token but not the `const msg` indirection NOR distinguished throw from stderr; this is the 4th recursion-pair this session, and the throw-scoped indirection-aware detector is what finally ends the OPTDEP sub-class.** **(hh) round-2 fresh audit (rc.60→rc.62)** — a 2nd from-scratch 8-lens workflow-audit (30 agents, 3-skeptic verify; deeper into round-1-skimmed modules: numeric/periodic/http/cli/bases/parser/frontmatter-nested/write-path) returned **7 confirmed (1 HIGH / 2 MED / 4 LOW)**, batched smallest-blast-radius-first. **(rc.60) WRITE-1 [HIGH, data-loss] — `renameNote(overwrite:true)` silently LOST the source when the destination backlinks the source.** The backlink-rewrite `plan[]` excluded only the SOURCE (`isSource`), never the DESTINATION: with `B.md` linking `[[A]]`, after `renameFile(A→B,overwrite)` moved A's content onto `B.md`, the post-rename backlink loop wrote B's PRE-rename (rewritten) content back over it → A's content gone, envelope still reported success. Fix: also exclude the destination (`e.absPath === toAbsCheck`) from `plan[]` (its post-rename content IS the moved source, self-refs already fixed via sourcePlan). `archiveNote` inherits the fix (delegates). +2 tests (data-loss regression + NEGATIVE control: dest that doesn't backlink still works). **1249 → 1251 source tests.** **Lesson: the rename orchestrator builds a backlink plan against PRE-rename content but writes it POST-rename — any path the rename MUTATED (the destination) is stale in the plan; the source-only exclusion was the gap. The fresh round-2 audit (deeper write-path lens) found a genuine HIGH data-loss the prior 25 rounds missed — multi-lens behavioral audits remain the highest-yield gate.** **(rc.61) round-2 tail batch 2** — three write-path fidelity fixes. **WRITE-2 [MED]**: `frontmatter_set` on a note whose existing frontmatter is malformed YAML (e.g. a TAB used for indentation — js-yaml@4 rejects it, `parseNote` falls back to whole-body) blindly prepended a SECOND `---` block, doubling/corrupting the frontmatter; now `frontmatterSet` re-parses `note.content` with `parseFrontmatter` first and REFUSES (clear "not valid YAML" error) if it throws — fail-closed instead of silent corruption (+ NEGATIVE control: a clean no-frontmatter note still gets one added). **WRITE-3 [LOW]**: a case-only rename (`Foo.md`→`foo.md`) on a case-INSENSITIVE FS was blocked — needed fixes at BOTH layers: the `renameNote` tool's "Destination already exists" guard saw the source AS the dest, AND the `vault.renameFile` `link()`+`unlink()` primitive can't atomically self-replace. Now the tool skips its existence guard for a case-only path difference and `vault.renameFile` detects a same-inode case rename (`isSameInodeCaseRename`) and uses a plain `rename`; a case-SENSITIVE FS with a distinct existing `foo.md` still throws EEXIST (`vault.renameFile` is the authority). **FM-PROTO-KEY-DROP [LOW, rc.58 regression]**: rc.58's `normalizeDateOnly` deep-walk rebuilt objects with `out[k] = …`, which for a literal `__proto__` frontmatter key hit the prototype SETTER and silently DROPPED it (data loss vs a direct dump); now uses `Object.defineProperty` so every key — incl. `__proto__` — becomes a real own enumerable property and survives re-stringify. **1251 → 1255 source tests.** **Lesson: WRITE-3 needed fixes at BOTH layers (tool existence-guard + vault primitive) — fixing only the audit-cited primitive (vault.ts) left the user-facing tool still throwing; and FM-PROTO is the 5th recursion-pair this session (rc.58's own `normalizeDateOnly` regressed `__proto__`-key preservation while closing the date-mutation class).** **(rc.62) round-2 tail batch 3 — CLOSES the round-2 audit.** **HTTP-CORS-EXPOSE-SESSION-ID [MED]**: `applyCors` set `Access-Control-Allow-Headers` (lets a browser SEND `Mcp-Session-Id`) but NOT `Access-Control-Expose-Headers`, so cross-origin JS couldn't READ the `Mcp-Session-Id` the server returns on `initialize` → every stateful follow-up looked like a new session; added `Access-Control-Expose-Headers: Mcp-Session-Id` (+2 tests). **CLI-SERVEHTTP-RECENCY-FAILLATE [LOW]**: `serve-http` builds `prepareServerDeps` lazily (per session, first request), so a typo'd `--recency-weight 5` / `--stale-days x` / `--reranker-top-n 0` started the server and only threw on the first search; extracted the validation into a leaf module **`src/retrieval-opts.ts`** (`parseRecencyConfig` + `validateServeHttpRetrievalOpts`) called at the `serve-http` boot (fail-FAST, mirroring stdio `serve`'s eager `prepareServerDeps`) AND reused by `prepareServerDeps` (single source of truth; +11 unit tests w/ POSITIVE+NEGATIVE controls). The leaf module avoids the `no-internal-imports` Class-A invariant (tests can't value-import `server.ts`). **PERIODIC-WW-LOCALE-CONFLATION [LOW, documented-deliberate]**: the lowercase Moment week tokens `ww`/`wo`/`gggg` are locale-aware in Moment but resolve to ISO-8601 here (identical to `WW`/`Wo`/`GGGG`) — enquire ships no locale DB, ISO is the Obsidian Periodic-Notes default; documented in `periodic.ts` + pinned with a contract test (lowercase == uppercase == ISO). **Post-rc.61 re-sweep (FM-PROTO sibling)**: the `frontmatter_set` SET loop (`after[k]=v`) has the same prototype-setter shape, but zod's `.record()` STRIPS a literal `__proto__` key from `args.set` before it reaches the loop (verified empirically) → UNREACHABLE; rc.61's fix covered the reachable file-derived path (js-yaml DOES create an own `__proto__`). `bases.ts:641` `out[k]=fm[k]` is a read-only query-result projection (no write-back) — not corruption. **1255 → 1270 source tests** (+15). **This CLOSES the round-2 fresh 8-lens audit (7 confirmed: 1 HIGH rc.60 + 2 MED + 4 LOW across rc.61+rc.62).** **(ii) round-3 fresh 12-lens audit (rc.63→rc.66)** — a 3rd from-scratch Workflow audit on the shipped rc.62 commit (12 lenses; each finding → 3 independent adversarial skeptics, kept only if ≥2 confirm real; 31 agents) returned **6 confirmed (1 HIGH / 2 MED / 3 LOW), 0 dropped**, batched highest-severity-first. **(rc.63) HIGH ReDoS — `likeToRegex` adjacent-`.*` (DQL `like`), the project's THIRD ReDoS family.** `obsidian_dataview_query`'s `LIKE` translator emitted one `.*` per `*`, so N adjacent `*` compiled to `^.*.*…$` — adjacent unbounded quantifiers backtrack catastrophically against a non-matching subject WITHOUT any nesting (the rc.21/24/25/36 detector and the rc.39 sink-bound both covered `obsidian_open_questions`, but DQL `like` compiles its OWN RegExp and was never covered). Empirically reproduced + INDEPENDENTLY re-confirmed (anti-overclaim): an 11-char `**********Q` vs a 40-char subject hung V8 >5s — a remote event-loop DoS for ALL serve-http clients via the always-registered, read-only tool, no write/CLI gate. The TSDoc literally asserted "catastrophic-backtracking-SAFE by construction" (**overclaim #21**, the claimed-guarantee-vs-code-guard class). Fix: collapse a run of consecutive (unescaped) `*` into a SINGLE `.*` (a run of LIKE wildcards is semantically one `*`), so the compiled source has only NON-adjacent `.*` separated by required literals = linear; an escaped `\*` stays a literal (not collapsed). TSDoc corrected to point at the real guard. **Structural defense (the length-cap tests never exercised EVALUATION):** a STATIC unit (compiled source must contain no `.*.*` adjacency for adversarial star runs) + an EMPIRICAL worker-timed net (run the ACTUAL `likeToRegex` output against adversarial non-matching subjects in a worker with a wall-clock budget + re-confirm; the next adjacency regression fails CI on behavior). **1270 → 1273 source tests.** **Lesson: the rc.39 sink-bound ended the ReDoS class FOR `obsidian_open_questions` — but a SECOND RegExp-compiling sink (DQL `like`) existed and was never enumerated; a class isn't closed until EVERY sink of that class is found, and the durable close is an evaluation-time guard, not a source inspection.** Remaining round-3 tail (sequenced): **rc.64** MED — `frontmatter_set` destroys valid-YAML NON-mapping frontmatter (scalar/sequence; rc.61 WRITE-2 guard only caught the THROW case, not the `isPlainObject`-coerced-to-`{}` sibling) → expose a `coerced` flag from `parseFrontmatter`, refuse fail-closed; **rc.65** MED+LOW serve-http amplifiers — `read_canvas` O(K×N) per-node linear find (drop redundant `allMarkdown.find`, +resource-bound-invariant manifest) + `http-transport` `pendingInits` leak on a constructor throw (move incr + both ctors inside one try/finally); **rc.66** LOW — graph-boost membership not NFC-folded (last rc.46 name-comparison residual) + `normalizeDateOnly` strips time-of-day from explicit UTC-midnight timestamps (rc.58 over-correction; parse-time verbatim preservation OR document+pin). **(rc.64) round-3 batch 2 — MED silent write-path data-loss, the WRITE-2 sibling.** `frontmatter_set` on a note whose existing frontmatter is a valid-YAML NON-mapping (a bare scalar `---\nhello\n---` or a sequence `---\n- a\n---`) silently DESTROYED it: `parseFrontmatter` deliberately coerces a non-mapping doc to `data:{}` (the rc.54/rc.55 `isPlainObject` corruption guard), so `frontmatterSet` saw `before:{}`, built `after={...set}`, and `stringifyFrontmatter` REPLACED the original block with a fresh mapping — reporting a phantom success (`before:{}`, `changed_keys:['+key']`) while the scalar/sequence was lost. The rc.61 WRITE-2 guard only caught the THROW case (malformed YAML), not this valid-but-non-mapping sibling. Fix: `parseFrontmatter` now exposes a **`coerced` flag** (free — it already computes the `isPlainObject` branch) that is true iff a non-empty block was coerced away from a mapping; `frontmatterSet` REFUSES fail-closed (`"not a YAML mapping … editing it would replace and destroy that block"`) when `coerced`, generalizing the rc.61 guard from throws-only to throws-OR-coerced. A no-frontmatter note (`coerced:false`) still gets one added — the legitimate path. **Structural:** the `coerced` contract is pinned in `tests/frontmatter.test.ts` (sequence/scalar/Date→true; mapping/empty/comment-only/absent→false) + a refuse-on-non-mapping behavioral test asserting the file is BYTE-unchanged. **1273 → 1275 source tests.** **Lesson: the rc.61 WRITE-2 fix closed the THROW half of "frontmatter_set must not destroy what it can't represent as a mapping" but left the valid-YAML-non-mapping half open — the `coerced` flag closes the whole class at the parse layer (one signal both guard sites share), and the destroy-path test must assert BYTE-equality, not just that an error was thrown.** Remaining round-3 tail: **rc.65** (read_canvas O(K×N) + pendingInits leak), **rc.66** (NFC graph-boost + UTC-midnight Date). **(rc.65) round-3 batch 3 — two serve-http per-request amplifiers.** **read_canvas O(K×N) [MED]**: the always-on read-only `obsidian_read_canvas` did a per-file-node O(N) linear `allMarkdown.find(...)` scan → O(K×N) on the event loop (K canvas file-nodes × N vault notes, K bounded only by the 5 MB file cap → tens of thousands), pinning serve-http for all clients. It escaped `resource-bound-invariant` entirely (media.ts outside SCANNER_SOURCES + uses `listMarkdown` WITHOUT `readNote`, so `discoverScanners` can't see it). Fix: build a `byRelPath` Map ONCE for O(1) per-node lookup (the `findBestMatch` basename fallback already has its own cached index); + a SEPARATE resource-bound assertion (mirrors `queryBase`/`buildWikilinkGraph`) pinning the O(1) index present AND no per-node linear scan. **pendingInits leak [LOW]**: in stateful serve-http the `registry.pendingInits += 1` reservation + `buildMcpServer`/`new StreamableHTTPServerTransport` constructors sat OUTSIDE the `try/finally` that decrements, so a constructor throw leaked the counter PERMANENTLY → effective `maxSessions` cap eroded by one each time → eventual permanent 503 with zero live sessions. Fix: extracted exported `runWithPendingInit(registry, fn)` (increment + try/finally decrement) wrapping the WHOLE build+connect+initialize body (constructors now inside, catch undefined-guarded) → the decrement runs on every exit path. **Structural:** `runWithPendingInit` NEGATIVE-control unit (a THROWING init body leaves `pendingInits===0`; +50-iteration erosion check) + the read_canvas resource-bound assertion. **1275 → 1279 source tests.** **Lesson: both are serve-http per-request amplifiers the drift/claim gates are blind to — read_canvas escaped the resource-bound inventory because its signature (`listMarkdown` sans `readNote`) didn't match the auto-detector; the pendingInits leak is the classic "reserve before the try" shape. Durable closes: an explicit inventory assertion + a pure try/finally helper.** **(rc.66) round-3 batch 4 — CLOSES the round-3 audit (2 LOW correctness).** **NFC graph-boost residual**: the searchHybrid in-degree graph-boost membership test was the ONE name-comparison site the rc.46 NFC sweep missed — it compared `stripMd(wl.target)` WITHOUT case-fold, so the rc.46 detector's strip+lowercase signature never matched it, yet it still mis-resolved an accented note name (NFC wikilink target vs NFD candidate path on macOS APFS), silently dropping the α=0.005 in-degree tie-break for accented notes. Now both sides route through `foldName` (NFC + case-fold — the same canonical key as the other 14 sites + findBestMatch); pinned with a SEPARATE name-fold-invariant assertion (the generic detector can't reach the no-`toLowerCase` shape). **UTC-midnight Date fidelity [documented-deliberate]**: an EXPLICIT `2026-01-15T00:00:00Z` timestamp resolves (post-`load`) to the BYTE-IDENTICAL Date as a bare `2026-01-15`, so rc.58's date-only normalizer demotes BOTH to `YYYY-MM-DD` — the time-of-day is irrecoverable at stringify time. Accepted as a deliberate tradeoff (a true fix needs a custom js-yaml timestamp type on the freshly-hardened parser, for the SAME calendar date; far less harmful than the rc.58 bug it descends from); documented in the `frontmatter.ts` header + pinned as a contract test (midnight-UTC ts → date-only; the rc.58 non-midnight NEGATIVE control still holds). **1279 → 1281 source tests.** **This CLOSES the round-3 fresh 12-lens audit (6 confirmed: 1 HIGH rc.63 + 2 MED rc.64/rc.65 + 3 LOW rc.65/rc.66, 0 dropped).** **Lesson: the rc.46 NFC inventory invariant had a blind spot — a name comparison that DOESN'T lowercase (a case-sensitive raw `stripMd`) escaped its strip+lowercase signature; the durable close for such a detector-missed site is a separate pinned assertion, and the residual proves an inventory invariant is only as complete as the signatures it enumerates.** **(jj) post-rc.66 re-sweep (rc.67→rc.70)** — the mandatory post-merge re-sweep (a focused 6-lens Workflow, 3-skeptic verify, hunting siblings/recursions of the rc.63→rc.66 fixes on the shipped commit) found **6 confirmed (4 MED / 2 LOW), 0 dropped** — the round-3 fixes left siblings in adjacent code, the session's signature pattern, validating the re-sweep yet again. **(rc.67) MED remote DoS — `validateNoteProposal` O(broken-links × vault) filesystem-walk amplifier (rc.65 readCanvas resource-bound-escape sibling).** The always-on, bearer-reachable `obsidian_validate_note_proposal` called `suggestSimilar` PER broken `[[wikilink]]`, each doing a FRESH uncached `vault.listMarkdown()` walk; `content` was uncapped (`z.string()` no `.max()`) → a body packed with thousands of distinct broken targets = thousands of back-to-back whole-vault directory walks on the event loop, starving all serve-http clients. Escaped `resource-bound-invariant` for the SAME reason readCanvas did (calls listMarkdown but not readNote → `discoverScanners` blind; cost hidden in the `suggestSimilar` helper). Fix: `suggestSimilar` gains an optional pre-fetched `entries` arg; `validateNoteProposal` passes its single `all` listing + memoizes suggestions per target (Map) so the listMarkdown count is INDEPENDENT of broken-link count (constant, not per-link); + `.max(1 MB)` on the `content` schema (defense-in-depth bound). Test asserts listMarkdown calls stay CONSTANT as broken-link count grows 3→60. **1281 → 1282 source tests.** **Remaining re-sweep tail (sequenced):** **rc.68** MED globToRegex catastrophic backtracking (rc.63 ReDoS sibling — `**` emits adjacent `.*` with no run-collapse; operator-controlled --exclude-glob/--read-paths, not remote; apply the rc.63 collapse + cap + structural guard); **rc.69** MED DQL `file.name`/`file.path` NFC-blind comparison (rc.46/rc.66 sibling — bases.ts `file.name==` was folded rc.46, the DQL twin was missed; silent zero-match on accented names on macOS); **rc.70** reserve-before-try class (rc.65 sibling — EmbedDb/FtsIndex `open()` not close-on-throw → server.ts:435 leaks the SQLite handle for the serve lifetime; + startHttpServer dep leak on listen() error; root-class fix = make `open()` self-cleaning). **Lesson (reinforced, definitive): the post-merge re-sweep is NON-NEGOTIABLE — every audit round this session left siblings the next sweep caught (rc.57→rc.59, round-2→round-3, and now round-3→this re-sweep found 6 more incl. a REMOTE DoS); the drift/claim CI gates are blind to every one of these behavioral classes, so a fresh adversarial multi-lens read of the shipped commit is the only thing that finds them.** **(rc.68) re-sweep batch 2 — MED globToRegex catastrophic backtracking (rc.63 likeToRegex ReDoS sibling).** `globToRegex` (vault.ts, the `--exclude-glob`/`--read-paths` privacy filter, run via `.test()` on EVERY path on EVERY vault scan) had the IDENTICAL un-collapsed-wildcard defect rc.63 fixed in likeToRegex: the `**` branch consumed only TWO stars + a trailing `/`, so a globstar RUN emitted ADJACENT unbounded quantifiers — `****`→`^.*.*$`, `***`→`^.*[^/]*$` (measured >15s V8 hangs at ~8–12 runs). OPERATOR-controlled at boot (not remotely reachable → MED not HIGH, a self-inflicted hang), but a genuine un-enumerated sibling of the class rc.63's own TSDoc said 'is not closed until EVERY RegExp-compiling sink is enumerated.' Fix: consume the entire `*` run inline (→ one `.*`) AND a FINAL total collapse pass on the emitted source `out.replace(/(?:\.\*|\[\^\/\]\*){2,}/g, '.*')` — the inline `while` handles a single run (`***`) but REDUNDANT globstars split by a slash (`a/**/**/b`, found by my own structural test on the first build) still emit `.*.*` because the first `**` eats its trailing `/`, so the next is adjacent; the post-process makes the source provably free of adjacent quantifiers however the globstars are spelled. + `MAX_GLOB_PATTERN_LEN`=1024 cap (fail-fast on an absurd operator glob). **Structural guard**: `security.test.ts` asserts no adjacent unbounded quantifiers in the compiled source for an adversarial globstar corpus (incl. `a/**/**/b`, `**foo**`) + a wall-clock linearity check + a NEGATIVE control proving the adjacency detector fires on `^.*.*$`/`^.*[^/]*$` + the cap throw. **1282 → 1285 source tests.** **Lesson: my first inline-only fix (consume one run) was INSUFFICIENT — the structural adjacency test caught `**/**` (two runs split by a consumed slash) on the very first build, so the durable close is a TOTAL post-process collapse on the source, not a per-run consume; and globToRegex is the exact 'enumerate EVERY sink' the rc.63 lesson named, missed by the rc.63 sweep.** **Remaining re-sweep tail: rc.69** (DQL file.name/path NFC-blind), **rc.70** (reserve-before-try open()/listen() leaks). **(rc.69) re-sweep batch 3 — MED DQL `file.name`/`file.path` NFC-blind comparison (rc.46/rc.66 NFC sibling).** `obsidian_dataview_query` (always-on, bearer-reachable) projected `file.name`=`stripMd(entry.basename)` and `file.path`=`entry.relPath` RAW (NFD on macOS APFS) and compared them against user-authored NFC predicate literals with no normalization — `looseEq`/`contains` only lowercase, `like` uses `iu` (no NFC fold) — so `WHERE file.name = "Café"` silently returned ZERO rows for an accented note. `bases.ts`'s `file.name ==` twin was folded in rc.46; this DQL sink was the missed sibling (the rc.46 name-fold detector is blind to it — the `stripMd` happens in `resolveField` and flows as a variable into the comparators, so neither detector signature appears at the comparison site). Fix: `.normalize("NFC")` the `file.name`/`file.path` projection (resolveField + the row `out`) AND the string predicate literal in `parseValue` — both sides NFC, the comparators handle case. Behavioral test: an NFD-on-disk `Café.md` resolves a NFC `WHERE file.name = "Café"` (+ `contains` + a non-matching NEGATIVE control); the test fails if the normalize is dropped (the regression guard). **1285 → 1286 source tests.** **Lesson: the rc.46 NFC inventory invariant (signature: strip-then-lowercase) structurally cannot see a comparison where the strip and the compare are in different functions — DQL is the 2nd such blind-spot after rc.66's graph-boost; the durable close for these is a behavioral test that fails on a dropped normalize, not another detector signature.** **Remaining re-sweep tail: rc.70** (reserve-before-try open()/listen() handle leaks — CLOSES the re-sweep). **(rc.70) re-sweep batch 4 — CLOSES the post-rc.66 re-sweep — reserve-before-try handle-leak class (rc.65 sibling).** Three findings, one class: (#3 MED) `server.ts:435 await db.open()` sat OUTSIDE its `try{…}finally{db.close()}`, so `EmbedDb.open()` throwing at pragma/bootstrapSchema on a corrupt/legacy `.embed.db` (the `existsSync`-gated `--use-hnsw` path) leaked the SQLite handle + WAL/SHM locks for the WHOLE serve lifetime (the outer catch only nulled hnswContext, server ran on); (#4 LOW root) `EmbedDb.open()` + `FtsIndex.open()` assigned `this.db` BEFORE pragma+bootstrapSchema with no internal cleanup — an undocumented 'caller must close on throw' contract; (#5 LOW) `startHttpServer` leaked prepareServerDeps' fts/watcher handles when `httpServer.listen()` rejects (EADDRINUSE), bounded/OS-reclaimed. **Root-class fix (per the rc.45/rc.49 'fix the source every caller funnels through' lesson):** made BOTH `open()` methods SELF-CLEANING — `this.db = new Ctor(...)` then `try { pragma; bootstrapSchema } catch (e) { this.close(); throw e }` — so a post-construction throw releases the handle for EVERY caller regardless of its own discipline (this alone closes #3 + #4); + wrapped `startHttpServer`'s listen() promise so a reject `await shutdownHttpServer()` before re-throwing (#5). **Structural guard (behavioral):** corrupt-db tests for both `EmbedDb`/`FtsIndex` assert a 1st `open()` throws AND a 2nd `open()` RE-THROWS — the precise proof the handle was released (pre-fix the `if (this.db) return` guard made the 2nd a silent no-op; the re-throw is impossible unless `this.db` was reset). **1286 → 1288 source tests.** **This CLOSES the post-rc.66 re-sweep (6 confirmed: 4 MED + 2 LOW, all shipped across rc.67→rc.70).** **Lesson: the rc.65 reserve-before-try fix (runWithPendingInit) was an INSTANCE fix; the durable close for the class is making the resource-acquiring primitive itself self-cleaning (close-on-throw inside open()), so no call site can leak — and the 2nd-open-re-throws assertion is the behavioral proof a handle was released without reaching into private state. Every audit round + re-sweep this session left siblings the NEXT sweep caught (round-3→re-sweep found 6 incl. a remote DoS); the post-merge re-sweep is the non-negotiable gate the drift/claim CI is blind to.** **(kk) post-rc.70 re-sweep (rc.71→rc.74)** — the mandatory post-merge re-sweep (a 6-lens Workflow, 3-skeptic verify, hunting siblings/recursions of rc.67→rc.70 on the shipped rc.70 commit) found **6 confirmed (1 HIGH / 5 MED), 0 dropped** — every audit round + re-sweep this session leaves siblings the next sweep catches, the session's signature pattern, validating the re-sweep yet again. **(rc.71) ReDoS class — literal-separated unbounded quantifiers in BOTH likeToRegex (DQL `like`, HIGH, remote) + globToRegex (privacy filter, MED), the rc.63/rc.68 siblings.** rc.63 collapsed only ADJACENT `*` runs and rc.68 only adjacent globstar runs; a pattern with wildcards SEPARATED BY LITERALS (`*a*a*…` → `^.*a.*a…$`, `**a**a…`) was untouched and catastrophic — empirically **110 s** for `*a`×14 vs a 41-char subject via the always-on, bearer-reachable `obsidian_dataview_query`; both prior TSDocs falsely claimed 'linear-time' (**overclaim #22**). The catastrophe scales with the SUBJECT length (≈ C(len,k) backtracks), so a wildcard COUNT cap is NOT structurally safe (a long path/field value blows up at a handful of wildcards), and the JS atomic-group emulation `(?=(.*))\1` was empirically REJECTED (it stops backtracking but CHANGES semantics — it can't yield chars back to a required following literal; 22/22 corpus mismatches). **Root fix (per the rc.39 'bound the SINK, don't chase shapes' lesson): a shared dependency-free leaf `src/wildcard-match.ts` (`matchWildcardTokens` = a tabular O(tokens×len) DP + `compileLikeTokens`/`compileGlobTokens`); both sinks now match WITHOUT a backtracking regex.** `likeToRegex`→`compileLike`, `globToRegex`→`compileGlob` (each returns a `{test}` matcher so the DQL caller + all 9 privacy-filter call sites stay byte-identical; the Vault `excludeRegexes`/`readPathRegexes` fields became `excludeMatchers`/`readPathMatchers`). **Structural guard `tests/wildcard-match.test.ts`: (a) a DIFFERENTIAL regression test — the new matcher vs INLINED copies of the pre-rc.71 regex builders over a broad LIKE+glob corpus, 0 mismatches proves behavior preservation (the rc.53 differential-corpus method) — and (b) a LINEAR-budget test running the exact `*a*a…`/`**a**a…` shapes that hung V8 and asserting <500 ms.** Incidental fix: the pre-rc.71 likeToRegex omitted `?` from its regex-specials set, so any LIKE value with `?` produced an INVALID regex and THREW; the matcher treats `?` as a literal (LIKE's only wildcard is `*`). **1288 → 1300 source tests.** **Lesson: a ReDoS class is NOT closed by collapsing the shape the LAST fix saw (adjacent runs) — the SAME class re-manifests as literal-separated quantifiers in EVERY regex-compiling sink; the durable close is to bound the sink STRUCTURALLY (a non-backtracking matcher that cannot exceed a linear budget for ANY input), proven by a differential-vs-old + linear-budget test pair, not by extending a shape-collapse the next sibling shape escapes.** Remaining re-sweep tail (sequenced): rc.72 (validateNoteProposal findBestMatch O(K×N) amplifier — rc.67 sibling), rc.73 (bases.ts path/file.path NFC-blind — rc.69 sibling), rc.74 (pdfjs document/loadingTask reserve-before-try leak — rc.70 sibling). **(rc.72) MED — `validateNoteProposal` findBestMatch O(K×N) amplifier (rc.67 sibling, remote).** rc.67 closed the `suggestSimilar` re-walk in the wikilink loop, but `findBestMatch` (called per link in the SAME loop) still fell into an O(N) `for (const e of entries) endsWith("/"+target)` scan for the path-qualified MISS case (not covered by the `indexFor` WeakMap) — so a 1 MB body of K distinct path-qualified broken `[[a/X]]` links → O(K×N), measured 8+ min at K=150k/N=20k on the always-on, bearer-reachable `obsidian_validate_note_proposal`. The rc.67 test used basename-only `[[NoSuchNote{i}]]` targets that hit the O(1) byBasename miss and NEVER reached the endsWith branch (the test generator couldn't produce the failing shape — the rc.25/rc.36 generator-blindspot pattern, now at the TEST level). **Root fix: `EntryIndex` gains a `bySuffix` map (every `/`-aligned relPath tail at segment index ≥1, first-wins in entries order = byte-identical to the old scan), so the path-qualified miss is O(1) — closing the class at the `findBestMatch` helper, which benefits EVERY caller (find_similar, get_note_neighbors, rename, validate).** Differential-verified (28 corpus cases, 0 mismatches vs an inline old-endsWith reference) + a behavioral test: 5000 path-qualified MISSES vs N=20000 = ~19 ms (the old O(K×N) would be minutes). **1300 → 1301 source tests.** **(rc.73) MED — bases.ts `path`/`file.path` startsWith/contains NFC-blind (rc.69 NFC sibling, remote).** `obsidian_query_base` (always-on, bearer-reachable) compared `ctx.path` (raw `e.relPath`, NFD on macOS APFS) against the unnormalized NFC `.base` filter literal in the `path startsWith`/`path contains`/`file.path startsWith`/`file.path contains` branch — neither operand normalized — so `path startsWith "Café/"` silently returned ZERO matches for an accented folder, while the sibling `file.name ==` branch 10 lines below already folded via `foldName`. rc.69 NFC-fixed the DQL `file.name`/`file.path` twin and noted the bases.ts `file.name ==` was folded in rc.46, but missed this path/file.path branch — the rc.46/rc.69 name-fold detector is blind here (the strip happens in one place, the raw `startsWith`/`includes` in another; no `toLowerCase` signature to grep). **Fix: NFC-normalize `ctx.path` once at its assignment + the predicate literal (NFC-only, NOT case-fold — `path` is case-SENSITIVE in Obsidian/Dataview); the result-row projection keeps the raw relPath verbatim.** Behavioral test: an NFD-on-disk `Café/note.md` resolves an NFC `path startsWith "Café/"` + `file.path startsWith` + `file.path contains` with a non-matching NEGATIVE control (mirrors the rc.69 DQL test) — fails if the normalize is dropped. **1301 → 1302 source tests.** **(rc.74) MED/LOW — pdfjs document/loadingTask reserve-before-try leak in BOTH `extractPdfText` + `extractPdfWithOcr` (rc.70 reserve-before-try sibling, bearer-reachable) — CLOSES the post-rc.70 re-sweep.** Both pdfjs callers acquired `doc = await loadingTask.promise` BEFORE guards that throw post-acquisition: `pdf.ts extractPdfText` ran its page-range + maxPages throws with the cleanup as PLAIN TRAILING CODE (no try/finally at all); `ocr.ts extractPdfWithOcr` had `resolveOcrPageRange` + the maxPages guard OUTSIDE the existing try (whose finally only covered the page loop). So a crafted `obsidian_read_pdf` (`pages:[1,600]` on a ≥600-page PDF → exceeds DEFAULT_PDF_MAX_PAGES=500) or `obsidian_ocr_pdf` (`pages:[1,250]` >DEFAULT_OCR_MAX_PAGES=200, or a post-clamp-inverted range) leaked a pdfjs document + worker port per call on serve-http, accumulating over the serve lifetime. **Fix (per the rc.70 self-cleaning lesson): wrap the FULL lifecycle (from `doc = await loadingTask.promise` through metadata) in a try whose finally always runs guarded `doc.cleanup()` + `loadingTask.destroy()`** — `extractPdfText` gains the finally it never had; `extractPdfWithOcr` moves resolveOcrPageRange + the maxPages guard + worker creation inside, and the finally `worker?.terminate()`s (the worker is undefined when a pre-worker guard throws) then destroys doc/loadingTask, each `.catch(()=>{})` so a cleanup error never masks the original. **Test: a CI-running behavioral cleanup test for `extractPdfText` (30× post-acquisition maxPages + inverted-range throws on a real `makePdf` fixture, then a NORMAL extraction succeeds — a leaked doc/worker would exhaust handles/hang) + a deps-gated behavioral test for `extractPdfWithOcr` (visible `ctx.skip()` when lang packs are absent, since assertOcrLangsInstalled gates before getDocument).** **1302 → 1304 source tests.** **This CLOSES the post-rc.70 re-sweep (6 confirmed across rc.71→rc.74: 1 HIGH ReDoS + 1 MED amplifier + 1 MED NFC + 1 MED/LOW pdfjs-leak, plus the rc.71 glob-ReDoS MED folded in).** **Lesson: the rc.70 reserve-before-try fix made the SQLite primitive self-cleaning but did not enumerate the pdfjs-document dimension of the same class — every resource-acquiring sink (SQLite handle, fd, HNSW index, pdfjs doc, worker) must be checked for "is the cleanup wired to a finally that covers EVERY post-acquisition throw"; the re-sweep is the gate that finds the un-enumerated sinks the drift/claim CI is blind to. **(ll) post-rc.74 re-sweep (rc.75) — CLEAN line, 1 LOW closed.** The mandatory post-rc.74 re-sweep (6-lens Workflow, hunting siblings/recursions of the rc.71→rc.74 fixes on the shipped rc.74 commit) returned **0 CRIT / 0 HIGH / 0 MED, 1 LOW** — the FIRST re-sweep this session whose findings left no behavioral sibling of the prior batch (the recursion bottomed out). **(rc.75) LOW — DQL `LIKE` Unicode case-fold divergence (rc.54-class differential-corpus gap).** rc.71's DP matcher folds via `String.prototype.toLowerCase()`, NOT the pre-rc.71 regex `i`+`u` ECMAScript canonical fold; they diverge (UNDER-match — fewer rows, no privacy over-exposure / DoS / crash) for ~22 exotic BMP codepoints (micro-sign `µ`, long-s `ſ`, final-sigma `ς`, the Greek symbol variants, the U+1C80–U+1C88 Cyrillic small-caps block, `ẛ`, `ι`). The rc.71 differential corpus was ASCII+`café` only, so its "0 mismatches on every corpus pair" assertion was in reality scoped to ASCII — it structurally could not produce a folding-divergent codepoint (the rc.54 lesson: a differential corpus is only as strong as the shapes it can produce). The glob path is unaffected (`compileGlob` is case-SENSITIVE, never folds — verified byte-faithful over an 80k-pair differential). **Fix (accept-and-document, the rc.54 playbook — a custom Unicode-canonical folder is its own bug surface): documented the divergence as a deliberate CONTRACT in the `wildcard-match.ts` `matchWildcardTokens` header + a `LIKE Unicode case-fold contract` describe that pins the DP `toLowerCase` semantics for representatives (`µ`/`ſ`/`ς`) AND proves the divergence is real + non-vacuous via a NEGATIVE control (the inlined old regex WOULD have matched), and re-scoped the differential describe's comment to "ASCII + ordinary-accented" so its claim is honest.** Docs+test only, zero runtime change. **1304 → 1306 source tests.** **This CLOSES the round-3 audit + both its re-sweeps (rc.63→rc.75).** **(mm) full state-driven audit (rc.76 + rc.77).** After the round-3 audit + both re-sweeps closed (rc.63→rc.75), a fresh FULL state-driven 6-lens Workflow audit (code primary/secondary · STRIDE security/privacy · resource-DoS · docs-claims currency · tests/CI/supply-chain; 13 agents, 3-skeptic verify) of the WHOLE shipped project (not a change-driven re-sweep) returned **0 CRIT / 0 HIGH / 1 MED / 1 LOW** — both genuine LATENT issues the change-driven sweeps structurally miss (untouched modules + a packaged-doc claim drift), the exact non-overlapping failure modes the state-driven lens exists to catch. **(rc.76) MED — truncate-before-sort in `listPdfs`/`listCanvases`/`listBases` (always-on read tools).** All three iterated the raw `vault.listFilesByExtension` result (readdir/walk order, NOT mtime), broke the loop at `out.length >= limit`, and sorted ONLY that already-truncated subset — so on a vault with MORE than `limit` (default 100) files of that type the response was an arbitrary walk-order subset, NOT the documented "newest first" — empirically reproduced (4 PDFs, limit=2 → the 2 OLDEST instead of the 2 newest). This list logic was untouched for many RCs (recent media.ts edits were rc.65/rc.44, not this loop), which is exactly why the change-driven sweeps missed it; `read.ts` listNotes/getRecentEdits/staleNotes/listTags are the correct sort-THEN-truncate pattern these 3 deviated from. **Fix: `all.sort((a,b)=>b.mtimeMs-a.mtimeMs)` BEFORE the truncation loop at all 3 sites** (so the first `limit` walked are genuinely the newest). **+3 regression tests (one per site) that assert WHICH files come back are the newest (5 files / limit 2, explicit `fs.utimes` mtimes) — the existing "honors limit" (length-only) + "sorts by mtime" (2 files, under the limit) tests provably never overlapped this >limit case; revert-verified all 3 FAIL with the pre-sort removed.** **1306 → 1309 source tests.** **Lesson: a full STATE-DRIVEN audit (read every module as-is) finds latent bugs in long-untouched code that change-driven sweeps + behavioral re-sweeps are structurally blind to — the truncate-before-sort defect survived ~30 audit rounds because nothing recently touched those three list loops; the always-on read tools deserve the same sort-then-truncate discipline read.ts already had.** rc.77 = the LOW (STABILITY.md gating drift + structural guard). **(rc.77) LOW — STABILITY.md `obsidian_full_text_search` gating drift + structural guard (CLOSES the full state-driven audit).** STABILITY.md (the packaged semver-contract doc) attributed `obsidian_full_text_search` to `--persistent-index` ALONE (lines 13 + 19), but server.ts:691 registers it under BOTH `--persistent-index` AND `--diagnostic-search-tools` (TOOL_MANIFEST gating = "--persistent-index + --diagnostic-search-tools"; tool-registry.ts + api.md ×4 all state it correctly — STABILITY.md was the lone outlier, drifted since v3.5.1). Same α-class as the rc.22 STABILITY reranker-default drift; LOW because it's a packaged-doc accuracy gap (no security/data impact) and the umbrella `obsidian_search` already exposes BM25/FTS5 with `--persistent-index` alone. Untested gap: the docs-consistency STABILITY guards pinned tool/prompt COUNTS but nothing pinned the per-flag GATING breakdown prose. **Fix: corrected both STABILITY.md lines + a new docs-consistency invariant `stabilityGatingMismatches` that DERIVES each non-"always" tool's flag-set from `TOOL_MANIFEST.gating` and asserts STABILITY.md's "opt-in via / gated by `<flags>`" breakdown headings name the same set (pure fn + a NEGATIVE control feeding the exact rc.77-drift string + a POSITIVE control on the corrected string).** Docs+test only. **1309 → 1311 source tests.** **This CLOSES the full state-driven audit (2 confirmed: rc.76 MED truncate-before-sort + rc.77 LOW gating drift) — and with it the entire rc.63→rc.77 cascade (round-3 12-lens audit + 2 re-sweeps + the full state-driven audit): 1 HIGH + 7 MED + 6 LOW across 15 RCs, every confirmed finding shipped or reasoned-accepted.** **(rc.78) config hygiene — biome.json migrated to 2.5.0 (config-drift surfaced by the rc.77 lint run).** The devDep is `@biomejs/biome@^2.5.0` (installed 2.5.0) but `biome.json` still declared `$schema` `2.4.16` AND used the `recommended: true` linter field that biome 2.5.0 DEPRECATED (removed in the next major) — both emitted as `i`/DEPRECATED warnings on every `npm run lint` (non-blocking, so they slipped through rc.71→rc.77 as the 3 lint "infos"; the α-class stale-version-claim, here in a tooling config). Ran `biome migrate --write` → `$schema` 2.4.16→2.5.0 + `recommended: true`→`preset: "recommended"` (the field RENAME — same rule set, verified by a full-repo `npm run lint`: 135 files, 0 errors, NO format/rule cascade). Also cleared the one remaining lint info (a pre-existing `useTemplate` nit in `docs-consistency.test.ts:67` — `"`"+p+"`"` → a template literal) so the lint output is now pristine (0 findings). **Config + 1-line test tidy only, no runtime/behavior change, 1311 source tests unchanged.** **Lesson: a `^`-ranged dev-tool (biome) silently minor-bumped past the config's pinned `$schema`, and the resulting version-mismatch + deprecation only showed as non-blocking lint INFOS — so the drift survived several RCs until a commit touched a file whose formatting the new biome reformatted. `biome migrate` is the clean close; the broader watch is that `^`-ranged tooling needs its config kept in lockstep.** **(nn) v3.10.0 STABLE — promoted `@rc`→`@latest` (maintainer call).** After 78 RCs (forgetting-aware staleness rc.1→rc.10 + frontmatter-aware search + the rc.16→rc.78 multi-round security/correctness hardening cascade: ReDoS class fully closed [non-backtracking matcher + sink-bound worker], abs-path-leak, NFC name-resolution, reserve-before-try, resource-DoS amplifiers, gray-matter→js-yaml@4, pdfjs 5→6), Alex chose "promote now" on the ≥2-auditor gate (rc.32 deep + rc.34/rc.35 from-scratch Mavis + the round-1/2/3 + state-driven internal multi-lens Workflow audits = the evidence base; fresh-on-HEAD external pass waived as for v3.9.0). Bumped 3.10.0-rc.78 → **3.10.0** across the 7 version surfaces + server.json + the stale `v3.9.x stable` docs/badge claims (README/README.zh/ROADMAP/QUICKSTART); CI publishes `@latest` + signed provenance (SLSA L2) + OIDC MCP-registry sync (stable-only). No API breaks (additive minor). **45 tools · 19 prompts · 1311 tests.** **(oo) v3.10.1 — internationalization patch.** Added README translations for the top-5 most-spoken languages — Spanish (`README.es.md`), Hindi (`README.hi.md`), Arabic (`README.ar.md`, RTL) — alongside English + 中文, via a 3-agent parallel translation Workflow (each agent read the EN + zh READMEs, returned a full translation with badges/links/code/tool-names preserved verbatim). 5-way switcher on every README (current language bolded), all 4 translations in `files[]`, + 2 structural guards (per-language numeric-claim invariant mirroring the rc.30 zh guard incl. the Arabic `موجِّه` diacritics; a 5-way switcher cross-link guard so a future 6th language can't drift the set). Docs only, no code/API change. GitHub About verified already-current (version-agnostic) — not touched. **1311 → 1313 tests.** **Remaining tracked** (all maintainer-gated): published LongMemEval score (reference-hardware run + sign-off); the closed-loop-feedback feature (`mark_useful`, product+privacy decision).
- **v3.11.6-rc.10 (in flight on `@rc`) — LONGMEMEVAL-S PEER-PROTOCOL ADAPTER (competitive-study C-2).** Upgraded `scripts/bench-longmemeval.mjs` to run OHS's public protocol: scope-per-question (already modeled — one temp vault per question), k=10, now the full OHS-comparable set (**nDCG@5/@10, MRR, Hit@1/@5, Recall@10, AllRel@10**) grouped by `question_type`, via new pure `aggregateByCategory`/`byCategoryRows`/`recencyDelta` (reusing the rc.5 `hitAtK`/`allRelevantAtK` metrics). Publishable + honest: **`--recency-compare`** (recency OFF vs ON per-category Δ — leads with temporal/knowledge-update/preference, our freshness differentiator a static-retriever peer can't tell), a mandatory **local-vs-cloud disclosure** header (we run LOCAL embeddings; OHS's 0.895 is CLOUD bge-m3, a different measurement), and **`--output`** raw JSON. `docs/EVALUATION.md` gains the peer-protocol reproduction section. **+5 tests.** The headline NUMBER still needs the external dataset + reference-hardware run + sign-off (maintainer-gated) — this ships the tooling one command away, NOT a claim. **Lesson: C-2 reuses rc.5's Hit@k/AllRel@k/by_category rather than re-deriving them (the eval keystone paying off) — and ships the honest-publishing SCAFFOLDING (scope + local-vs-cloud disclosure, raw-JSON output, differentiator pass) so the eventual number can't be an overclaim; build the adapter that makes a number honest BEFORE running it.**
- **v3.11.6-rc.9 (in flight on `@rc`) — SAFE DEPENDENCY BUNDLE (dependabot #340 + #341).** In-range minor/patch: js-yaml 5.2.0→5.2.1 (behavioral — frontmatter/`.base` engine) + dev group (biome 2.5.1→2.5.4, @types/node 26.0.1→26.1.1, @vitest/coverage-v8 4.1.9→4.1.10, sharp 0.35.2→0.35.3, typedoc 0.28.19→0.28.20). Bundled in ONE branch → lock re-resolves once vs the live advisory DB → **no new advisories** (`check-audit` clean). js-yaml re-verified behavioral: frontmatter+bases 75 tests pass (YAML 1.2 scalar contracts hold). biome patch needed `biome migrate` (`$schema`→2.5.4, rc.78 config-drift class). No `src/` change; 1552 source tests unchanged (count corrected rc.12). Landed off fresh `main` (=rc.8) after the stack merged, **not stacked** (so the lock re-resolves vs the real tree). **Lesson: a dep bump's `npm install` re-resolves the WHOLE tree against the live advisory DB — bundle the group in one branch to dodge the N-way lock conflict, run the scoped-audit gate, and re-run the BEHAVIORAL suite for any behavioral dep; do it off the CURRENT main, not a stale stack.**
- **v3.11.6-rc.8 (in flight on `@rc`) — FEEDBACK REALPATH-KEYING FIX + `vault_root` GUARD (S-4a, RFC-surfaced latent bug).** The multi-vault RFC's architecture-map surfaced a genuine SINGLE-vault bug: the closed-loop feedback sidecar keyed off the RAW `--vault` arg (`sha1(opts.vault)`) while FTS5/embed/parse-cache key off the CANONICAL `vault.root` (realpath'd) — so a symlinked/trailing-slash `--vault` path fragmented feedback across path spellings + desynced it from the realpath-keyed `prune` eraser. Fixed by keying `defaultFeedbackFile(vault.root)`; + a defense-in-depth `vault_root` guard on `FeedbackData` (persist + verify-on-open, mirrors the fts5/embed `data.root` guard — a foreign-root sidecar's entries aren't loaded; a pre-rc.8 no-`vault_root` file is still adopted). **+4 tests.** RFC's other Phase-0 items (HNSW-meta `vault_root`, `prune` orphan-only) are multi-vault-specific (near-zero single-vault value / single-vault prune is correct) → deferred to the multi-vault Phase 0/1, NOT shipped here. **Lesson: an architecture-mapping workflow aimed at a FUTURE feature (multi-vault) is also a latent-bug finder for the PRESENT — the feedback sidecar keying off the raw path while every sibling used realpath was a real single-vault correctness bug hiding in plain sight; ship the present-tense fix standalone and gate the future-tense hardening on the RFC's demand test.**
- **v3.11.6-rc.7 (in flight on `@rc`) — MULTI-QUERY FAN-OUT on `obsidian_search` (competitive-study C-4).** Cheap additive borrow from OHS: optional **`queries: string[]`** — each phrasing runs the full hybrid pipeline and the result LISTS are **RRF-merged** with the main `query`, so a note matching ANY phrasing floats up (complements HyDE, which reshapes ONE query; this fuses N phrasings). New `searchHybridMulti` reuses `reciprocalRankFusion`/`toRanked` (RRF fuses RANKS not scores → per-query reranker scores combine safely). DoS-bounded: `MAX_FANOUT_QUERIES=8` + each `.max(MAX_QUERY_LEN)` (verified by `parser-input-cap-invariant`); single-query path byte-identical. **+5 tests** (union / no-drop / single-equivalence / dedup / cap). Verified E2E. **Lesson: an additive retrieval-orchestration feature on an always-on tool needs its DoS bound (fan-out cap × per-query length cap) as the load-bearing guard — RRF over ranks is exactly the right merge because it sidesteps the cross-query score-incomparability the reranker doc already warns about; reuse the existing RRF primitive rather than re-derive fusion.**
- **v3.11.6-rc.6 (in flight on `@rc`) — ALIAS INDEXING + FTS5 COLUMN WEIGHTS (competitive-study C-3).** OHS indexes frontmatter `aliases:` + weights BM25 columns (title 10× / alias 5× / content 1×); enquire's FTS5 had ONE equal-weighted `content` column → **a note wasn't findable by its alias at all**, and a title match ranked no higher than a body mention. Fix: the `chunks` FTS5 table gains INDEXED **`title`** (basename; PDF filename) + **`aliases`** (from `aliases:`/`alias:`) columns; `bm25(chunks, 1, 10, 5)` positional weights (constants, tunable via `eval`/`eval:compare`). New pure `extractAliases`/`deriveFtsTitle`; both `reindexFile` callers (server `syncFtsIndex`, watcher) pass title+aliases; PDF indexes filename-as-title. Snippets unaffected (`snippet(chunks, 0,…)` = content col). **Persistence-format change: `SCHEMA_VERSION` 4→5** → existing `--persistent-index` auto-rebuilds once on open (existing meta-mismatch machinery). RRF chunk-ID parity intact (ids = `(rel_path, chunk_index)`); embed-db untouched. **+10 tests** (helpers w/ NEGATIVE controls + alias-findable / title-findable / title-outranks-body / snippet-not-polluted behavioral). Risk-triggered → **the from-scratch 6-lens re-sweep RAN (13 agents, 3 confirmed/4 refuted) and its findings are folded in**: the LOW regression it caught — per-chunk title storage + 10× weight let a big titled note FLOOD the BM25 candidate set — is fixed by storing title/aliases **only on chunk 0** (surfaces the note without the saturation; also kills the IDF dilution + storage cost); + alias count/length caps (index-amplification); + the v5 schema-history comment. **Lesson: the re-sweep earned its keep — my original per-chunk title storage was a real (if subtle, TF-IDF-rescued) ranking regression a persistence-format change slipped in; the chunk-0-only design the re-sweep forced is strictly better. AND rc.5 (eval overhaul) shipped FIRST precisely so this retrieval change is TUNABLE against a number, not guessed — the 10/5/1 weights are OHS's tested ratio as a starting point, and `eval:compare` can now A/B variants; the durable pattern is measure-then-tune, and the eval keystone is what made C-3 shippable with confidence.**
- **v3.11.6-rc.5 (in flight on `@rc`) — EVAL-SYSTEM OVERHAUL (keystone of the OHS competitive study).** `flowing-abyss/obsidian-hybrid-search` (closest tech peer) — its crown jewel is eval maturity. Brought `src/eval.ts` to that bar: **Hit@1/Hit@k/AllRel@k** metrics + **`by_category`** grouping (the weak-slice diagnostic) + per-query **`missed_paths`/`top_paths`** + an **`eval:compare` A/B delta tool** (`scripts/eval-compare.mjs`, `|Δ|≥0.01`=meaningful, exit 1 on regression) + `eval --output` + `npm run eval`/`eval:compare` + `category` on the golden set + **`docs/EVALUATION.md`** (methodology + failure-diagnosis + honest-publishing bar + cost guardrails). All additive (new fields optional → hand-built results stay valid); NDCG/Recall/MRR numbers unchanged. **Triple win: credibility lever (S-2), observability (S-5), and makes every future retrieval change PROVABLE not asserted.** **+16 tests** (each w/ NEGATIVE controls; count corrected rc.12). **Decoded OHS's headline first:** their 0.895 is scoped-per-question + a CLOUD model (bge-m3/OpenRouter); local/real-vault is 0.733, reranker flat — so we borrow their eval RIGOR, not their number or cloud-default (conflicts with local-first). Study + plan (STEAL eval / IMPROVE alias+column-weights / BORROW multi-query+gitignore / INSPIRE plugin+messaging / niche-guard) in `docs/audits/`. **Lesson: the highest-ROI competitive borrow from a peer is rarely a feature — it's their MEASUREMENT discipline; an eval that reports by-category + missed_paths + an A/B delta converts "did this retrieval change help?" from an assertion into a gated number.**
- **v3.11.6-rc.4 (in flight on `@rc`) — ACTIVATION: the `configure` command (the 2026-07-15 audit's #1 P0).** First NEXT-tier feature after the audit's "shift from the hardening treadmill to activation" — closes the "installed but not wired up" gap. New `enquire-mcp configure --vault <path> [--client <name>] [--tier basic|hybrid|hybrid-live] [--name <n>] [--http]` **prints a ready-to-paste MCP config for THIS vault** and is strictly **non-destructive** (writes nothing). Emits the correct per-client shape (Claude Code `claude mcp add`; Claude Desktop/Cursor/Windsurf `mcpServers` JSON; VS Code `.vscode/mcp.json` `servers`+`type:"stdio"`; Codex `~/.codex/config.toml` `[mcp_servers.<name>]` TOML; remote HTTP `serve-http`+bearer+URL) with a tier-aware preflight hint (`setup`+`doctor` for hybrid). New PURE leaf **`src/mcp-config.ts`** (no fs/process) carries all format logic → exhaustively unit-tested without spawning; the CLI action is a thin absolute-path-resolving wrapper. Client formats web-verified against each vendor's live docs (VS Code's `servers`/`type` + Codex TOML changed from older conventions). **+17 tests (15 pure w/ per-client parse asserts + NEGATIVE controls + 2 new cli.test E2E).** Docs: README quick-start, api.md subcommand table + channel line, AGENTS module map, cli.ts docstring. Additive, no existing-path behavior change. **Lesson: the highest-ROI post-audit move wasn't more hardening — it was removing onboarding friction; a pure generator leaf makes a multi-client config surface fully testable, and verifying each client's CURRENT format against its live docs (not memory) is the correctness gate for an activation feature.**
- **v3.11.6-rc.3 (in flight on `@rc`) — external-audit LOW batch (Codex/GPT-5 from-scratch audit on `9671b33`: 0 CRIT/0 HIGH/0 MED/5 LOW, every finding independently re-verified against the code, 0 hallucinations).** **G-1 (novel — test-harness target confusion):** no-arg `scripts/smoke.mjs` fell back to the real `~/Documents/Obsidian Vault` and printed its note metadata → now builds a throwaway synthetic vault via the newly-exported `createSyntheticVault()` (`tests/smoke-default-vault-invariant.test.ts`: POSITIVE unit + behavioral NEGATIVE control = the auditor's harness inverted, sentinel real-vault never read). **G-2/G-3 (claimed-guarantee-vs-code-guard doc drift):** `ocr.ts` header claimed a runtime trained-data download (impl is offline-enforced) + `http-transport.ts` header said stateful sessions "deferred to v2.7+" (shipped v2.14.0) → both headers corrected + `tests/module-header-claim-invariant.test.ts` (POSITIVE + NEGATIVE controls firing on the exact pre-fix strings). **G-4 (North-Star currency, highest-value):** the `CLAUDE.md` top header a session reads FIRST had drifted ~2 minor lines (v3.10.x/v3.9-in-flight, deferring the shipped `install-ocr-lang`) → rewritten to an accurate current-state header + `check-version-consistency.mjs` now pins a `<!-- current header; \`@rc\`=X -->` marker to `package.json` (mirrors the roll-up guard — the header can't silently lag again). **G-5 (backlog truth):** `ROADMAP.md` truth-reset — marked the mechanically-done-but-unchecked items done (queryable-graph tools `find_path`+`get_note_neighbors`; "grounded, not extracted" messaging; COMPARISON head-to-head) + corrected the **falsified** "no Obsidian MCP has a published number" premise (peer `flowing-abyss/obsidian-hybrid-search` publishes LongMemEval-S nDCG@5 0.895 — web-reproduced) → LongMemEval goal reframed to a fair head-to-head. **+3 test files (8 tests); zero runtime `src/` behavior change** _(count corrected rc.12 — the T-11 probe file was folded in)_ (docs/headers/harness only). The audit's one open gate (serve-mode cache-miss zero-outbound e2e, INCONCLUSIVE) is tracked as a promotion blocker, not a shipped bug; its strategic half (treadmill→activation, positioning, multi-vault RFC, published benchmark) lives in the planning note under `docs/audits/`. **Lesson: even a project branded on anti-drift rigor shipped a 2-version-stale North-Star header — the durable close is to pin the header to `package.json` like every other version surface; and re-verify every external finding against the code before acting (all 5 confirmed real here, and the load-bearing competitor claim was independently reproduced, not trusted).**
- **v3.11.6-rc.2 (in flight on `@rc`) — canonical parser STRUCTURE ACCESSORS, phase 1 (zero-behavior-change read-path unification).** The meta-audit's ROOT-CAUSE fix for the fence/parser-desync class: `src/parser.ts` had no canonical line-structure accessor, so 5 tools hand-rolled the identical `splitLines`+`advanceFence`+heading-parse loop (each able to drift). New leaf **`src/structure.ts`** is the single authority — `iterateBodyLines`/`iterateContentLines` (fence-aware `StructLine` iterators: `text`/`end`/`line`/`inFence`/`isFenceDelimiter`/`heading?`/`breadcrumb`) + `noteHeadings` — on ONE `advanceFence` walk + ONE heading parse. Migrated the 3 read-path walkers onto it: **fts5 `computeBreadcrumbsByLine`** (`.map(l => l.breadcrumb.join(" > "))`), **read `extractHeadings` → `noteHeadings(parsed)`**, **meta `getOpenQuestions`** (`for (l of iterateBodyLines(parsed))`), each proven **byte-identical** by its own existing behavioral test as a differential oracle (full suite + canonical-parser-agreement/parser-desync/fence-toggle guards green). **FREE FUNCTIONS not methods/getters on `ParsedNote`** — load-bearing: the disk cache JSON-rehydrates a PLAIN object (a method would be lost on reload; a new enumerable field would force a cache-version bump invalidating every user's cache) → `ParsedNote`'s 6 fields stay byte-identical, **zero cache impact**. **Structural wins:** `crlf-heading` guard upgraded from a fixed 3-walker `SITES` list to an **all-`src/` dynamic inventory** (any heading-exec skipping `stripTrailingLineEnds` fails CI — the `\b`-anchored detector matches both the inline literal and the named `HEADING_RE`, excluding unrelated `CHAT_HEADING_RE`); `fence-toggle` walker invariant now accepts `structure.ts` delegation as the canonical fence-aware form. `tests/structure.test.ts` (9 tests) pins the contract (body/content line-origin, code-fence exclusion, degenerate `# ###` per-walker semantics, breadcrumb parity, terminator round-trip, plain-rehydrated-object safety). **+9 source tests (1481 → 1490).** **This is phase 1 of the structure-accessor design (Workflow `w8wnxwiqt`, ~7 independently-shippable PRs); it deliberately holds the write-path terminator rewriters + the behavior-changing `stripCodeAndInline`-onto-the-walk residual close (which finally makes the fence/parser class impossible-by-construction) for their own later RCs, each with a from-scratch re-sweep.** **Lesson: the durable close for a recurring behavioral class is a shared PRIMITIVE that every consumer routes through (one fence authority, one heading parse) — proven safe by migrating one walker at a time with its existing test as the byte-identical oracle, and shrinking the drift-guards from fixed lists to dynamic inventories as the surface consolidates.**
- **v3.11.6-rc.1 (in flight on `@rc`) — closes the 3 non-blocking test-hardening items the v3.11.5 pre-promotion re-sweep tracked (the fence/parser-desync cascade tail, now structural).** **(1) Parser↔walker reconciliation on the UNCLOSED-fence shape (the only real divergence, empirically confirmed):** rc.6's generative net emitted only SELF-CLOSING fences, so the shape where `stripCodeAndInline` genuinely diverged from the char-aware walkers — an unclosed ` ``` ` at EOF (paired-fence regex requires a close, so it LEAKED the body as phantom links/tags; the walkers correctly treat it as code-to-EOF per CommonMark) — was outside the corpus (rc.25/rc.36 generator-blind-spot). `stripCodeAndInline` now shares the fence primitive via `dropUnclosedBlockFence(advanceFence)` (uses `splitLines`, terminator-aware, satisfies the rc.23 inventory) so the parser-based always-on tools (`buildWikilinkGraph`/`queryBase`/`validateNoteProposal`) stop surfacing phantom fenced content; `unclosed*` shapes added to the generative net. **Mutation-verified: reverting the parser fix fails the net PRECISELY on the unclosed shape across exactly those 3 parser-based tools, while the 3 walker-based tools (readNote map / getOpenQuestions / fts5 breadcrumb) already suppressed it** — proving the fix targets the parser side and the new shapes are non-vacuous. Well-formed notes byte-unchanged (verified across every other shape). **(2) `parser-desync-invariant` widened to `bases.ts collectTags`** (its own inline-tag regex escaped checks (1)+(2); the `fm: Type, body: Type` DEFINITION can't match, only CALL sites) + NEGATIVE control. **(3) `fence-toggle-invariant` broadened** to the ternary self-toggle (`fenceMarker = fenceMarker ? … : …`, missed by the rc.5 negation-only regex) + a DYNAMIC walker inventory (any src file declaring a fence-state var — `let …fence…` or `: FenceChar` — must use `advanceFence`), closing the "fixed 4-file list" blind spot so a future 5th-file walker is caught regardless of var name. **+2 source tests (1479 → 1481);** pure test-hardening + a zero-regression parser reconciliation. **Lesson: the pre-promotion re-sweep's "non-blocking" tail was worth closing structurally — item (1) was a real (if adversarial, pre-existing) parser↔walker divergence, and the durable close is to make the parser SHARE the walker's fence primitive (single source of truth) rather than patch the regex; the mutation-verify precisely localized which tools depended on the parser vs the walker.**
- **v3.11.5 STABLE — promoted `@rc` → `@latest` (maintainer "го").** A 6-RC internal-audit hardening cascade on stable 3.11.4 that closes the **fence/parser-desync class** at its root: a note-structure element (heading `#`, wikilink `[[…]]`, tag `#…`, question `Q:`) that appears ONLY inside a code fence must never be surfaced by an always-on tool — it must match the canonical parser (`stripCodeAndInline`/`parseNote`) + Obsidian. **rc.1** full from-scratch 12-lens project audit (1 MED write-path fence classifier that mis-read a line-leading inline `` ```span``` `` as a block-fence open → silently dropped `rename_note`/`replace_in_notes` edits; +2 LOW +1 INFO), **rc.2/rc.3/rc.4** three MANDATORY post-merge re-sweeps, each finding real siblings/residuals of the prior fix (rc.2 the 2 read-path fence-toggle siblings — `readNote(map)` heading-drop + fts5 breadcrumb-freeze → shared `src/fence.ts` `opensBlockFence` leaf + inventory invariant; rc.3 the PARSER-DESYNC class one layer deeper — 4 always-on tools re-extracting wikilinks/tags/questions from the RAW body → exported `stripCodeAndInline` routed through all + `parser-desync-invariant`; rc.4 the 2 tail LOWs), **rc.5** a root-cause META-AUDIT finding a **char-BLIND fence toggle** all 4 RCs + 30+ prior audits missed (`inFence = !inFence` can't tell a ``` delimiter from a `~~~`; live in `write.ts` ×2 data-affecting + `read.ts` + `meta.ts`) → canonical char-aware `advanceFence()` shared by all 5 walkers + the fence-toggle invariant upgraded presence→CORRECTNESS, **rc.6** the GENERATIVE `tests/canonical-parser-agreement.test.ts` net (every fence shape the cascade fixed × every always-on extractor, mutation-verified) — the "one net catches the whole class" defense. **13 confirmed findings, each reproduced against `dist/` + closed with a behavioral test, most with an inventory invariant.** The MANDATORY pre-promotion re-sweep (regression · currency · invariant-soundness, 3-skeptic adversarial verify) recommended PROMOTE: all 5 fence walkers verified byte-identical rc.4↔rc.5 across 100k realistic notes; the sole divergence is an adversarial unclosed-fence+mismatched-inner shape where rc.5 is the CommonMark-correct side (pre-existing, INFO). Folded into the promotion PR: `CITATION.cff` bump (drift-guard pins it to the latest stable heading) + a version-agnostic `docs/QUICKSTART.md` `--version` example (was a stale `3.10.x`). **Tracked for the next `@rc` line (genuine but non-blocking test-hardening, no shipped bug, all backstopped today): extend the rc.6 generative net's generator to unclosed/nested/frontmatter-adjacent shapes (+ reconcile `stripCodeAndInline`'s unclosed-fence leniency with the CommonMark-correct walkers); widen `parser-desync-invariant` to `bases.ts collectTags`; broaden the `fence-toggle-invariant` detector to non-`fence`-named vars + the ternary toggle form.** CI publishes `@latest` + SLSA L2 provenance + OIDC MCP-registry sync (stable-only). **46 tools · 19 prompts · 1479 tests · 11 languages.** **Lesson: the mandatory post-merge re-sweep is the non-negotiable gate this whole line proves out — each of rc.2/rc.3/rc.4 caught a real sibling/residual the drift/claim CI was blind to, and rc.5's fresh root-cause meta-audit found the char-blind toggle the cascade's own re-sweeps couldn't, because the cascade fixed fence DETECTION but never questioned the char-BLIND TOGGLE it fed; the durable close is always a shared primitive + a behavioral/inventory invariant, and rc.6's generative net converts "did we handle every fence shape?" into a self-checking CI gate.**
- **v3.11.5-rc.6 (in flight on `@rc`) — the meta-audit's highest-leverage hardening: a GENERATIVE canonical-parser-agreement net (tests-only, no src change) that would have caught the ENTIRE rc.1→rc.5 fence/parser class in one shot.** `tests/canonical-parser-agreement.test.ts` is a MATRIX over every fence shape the cascade had to fix (backtick, tilde, ≤3-space-indented, mismatched-inner-char ~~~-in-```, line-start inline span) × every always-on extractor (readNote map, buildWikilinkGraph, queryBase tag==, getOpenQuestions, validateNoteProposal, fts5 computeBreadcrumbsByLine): each plants a unique decoy element inside a fence + a real control outside, asserts the tool surfaces real + NOT decoy (oracle = parseNote/stripCodeAndInline). Mutation-verified: re-introducing the rc.5 char-blind toggle fails the net precisely on the mismatchedInner shape across 3 tools. +6 tests (30 runtime assertions; 1473→1479). The remaining meta-audit rec — canonical STRUCTURE ACCESSORS on src/parser.ts (fence-aware line iterator + headings on ParsedNote) so tools stop hand-rolling walkers — is a deliberate API change tracked SEPARATELY, not bolted onto this session; the generative net already makes the class fail-CI-on-divergence, the safety property that matters. **Lesson: the durable close for a recurring behavioral class is a GENERATIVE net whose GENERATOR covers every shape the class manifested in (same transform as the rc.25 ReDoS fuzz / rc.36 resource-bound manifest) — convert 'did we handle every fence shape?' (recursion-prone) into 'does any shape diverge from the parser oracle?' (empirical, self-checking).**
- **v3.11.5-rc.5 (in flight on `@rc`) — a root-cause META-AUDIT (4-lens Workflow `w1359rqw9`) of the whole rc.1→rc.4 cascade found a real MED class all 4 RCs + 30+ prior audits MISSED: a CHAR-BLIND fence toggle.** `opensBlockFence(line)` returns a bool, so callers doing `inFence = !inFence` couldn't tell a ``` delimiter from a `~~~` — a `~~~` line inside a ``` block (or vice versa) flipped the state, surfacing content inside code + dropping the real content after the block. Confirmed live (reproduced vs dist/) in **write.ts ×2 (data-affecting: rename_note/replace_in_notes rewrote a `[[link]]` inside the block AND skipped the real one after), read.ts extractHeadings, meta.ts getOpenQuestions (always-on)**; only fts5.ts was correct (it alone tracked the fence-marker char). **Class fix:** canonical char-aware `advanceFence(line, marker)` in fence.ts (returns the open-fence char + matching-char delimiter); all 5 walkers share it (mismatched inner fence = literal code, matching CommonMark + the parser). **Structural defenses:** fence-toggle-invariant upgraded presence→CORRECTNESS check (flags any live `= !…fence…` char-blind toggle; asserts walkers use advanceFence); parser-desync-invariant BROADENED from the 2 wrappers to also cover the `scanWikilinkInners`/`INLINE_TAG_RE` primitives validateNoteProposal uses (rc.3 scope-too-narrow gap). **+4 tests (1469→1473, mutation-verified). META-AUDIT CONCLUSIONS (why the cascade + prior audits missed it): (1) DEEPEST cause = architectural — src/parser.ts exposes only {frontmatter,body,wikilinks,embeds,tags}, NO canonical 'note structure' accessor (no fence-aware line iterator, no headings), so any tool needing those hand-rolls a walker that can diverge; (2) ~85% of the CI apparatus is claim/drift-driven or static-grep — NOT ONE gate ever fed a code-fenced note to an always-on tool and asserted output; that behavioral surface had zero coverage until rc.2/rc.3, and even those invariants are static-signature detectors with blind spots (char-awareness was one). rc.6 (planned) = the 2 highest-leverage hardenings the audit designed: a GENERATIVE canonical-parser-agreement behavioral net (oracle = parseNote/stripCodeAndInline) + the missing parser structure accessors, to make the class impossible-by-construction.** **Lesson: a fresh ROOT-CAUSE meta-audit AFTER a multi-RC cascade is worth it — it found a MED class the cascade's own re-sweeps couldn't, because the cascade fixed the fence DETECTION (opensBlockFence, per rc.2/rc.3/rc.4) but never questioned the char-BLIND TOGGLE logic the detection fed; the durable close is a shared char-aware primitive + a correctness (not presence) invariant.**
- **v3.11.5-rc.4 (in flight on `@rc`) — CLOSES the post-rc.2 re-sweep: the 2 independent tail LOWs.** (1) **fts5 indented-fence residual of my OWN rc.2 change**: `computeBreadcrumbsByLine` gated on `/^(```|~~~)/` (col-0 anchored), so an INDENTED fence (≤3 spaces, valid CommonMark) wasn't detected → its `#` lines leaked into the breadcrumb, while read.ts extractHeadings (via opensBlockFence) is indent-tolerant → now `/^\s*(`{3,}|~{3,})/` + fence-CHAR close-matching, consistent with read.ts. (2) **CRL-1 sibling `--reranker-top-n`**: validated only in buildMcpServer (one frame later) which stdio `serve` invokes AFTER prepareServerDeps acquired FTS5/watcher/embed-db/HNSW → `serve --enable-reranker --reranker-top-n 0` leaked them; hoisted into prepareServerDeps' fail-fast block (serve-http already validated early). **+2 tests (1467→1469, both mutation-verified).** **This CLOSES the entire v3.11.5-rc.1→rc.4 audit+re-sweep cascade: full 12-lens project audit (rc.1: 4) → 3 mandatory post-merge re-sweeps (rc.2 fence read-path siblings, rc.3 parser-desync class, rc.4 tail), 12 confirmed findings, every one reproduced against dist/ + closed with a behavioral test, most with an inventory invariant.** **Lesson: the re-sweep cascade did exactly its job — each post-merge re-sweep found real siblings/residuals of the prior fix (rc.2 found rc.1's read-path siblings; rc.3 found the class one layer deeper across 4 tools; rc.4 found a residual of rc.2's OWN fts5 change + a 4th validator of the CRL-1 class). The drift/claim CI gates were blind to every one; only fresh state-driven multi-lens reads comparing each site against the canonical parser/lifecycle found them. Closed the tail cleanly rather than carry it open.**
- **v3.11.5-rc.3 (in flight on `@rc`) — the post-rc.2 re-sweep (fresh 3-lens adversarial Workflow, `w8tq2r4cq`) found a BROADER class the rc.1/rc.2 audits missed: 4 always-on tools re-extract wikilinks/tags/questions from the RAW note body, disagreeing with the parser's canonical fence-stripped output (parseNote → stripCodeAndInline).** Same fence-vs-parser desync as rc.1/rc.2 but at the link/tag/question layer: **`buildWikilinkGraph`** (get_communities) → a `[[link]]` inside a ``` fence/frontmatter created a PHANTOM graph edge that skewed clustering + modularity (Q 0.5→0.42 on one fenced link) [MED]; **`queryBase`** → `tag ==`/`linksTo()` matched a note whose only `#tag`/`[[link]]` is fenced [MED]; **`getOpenQuestions`** → fenced `Q:`/`TODO?` surfaced as real + `#`-in-fence mis-set heading [MED]; **`validateNoteProposal`** scanned fm-stripped-but-not-fence-stripped body [LOW]. **Class fix (not 4 edits):** exported the canonical `stripCodeAndInline`; communities → `parseNote(body).wikilinks`, bases + validateNoteProposal → `stripCodeAndInline` before extraction, getOpenQuestions (line-walker w/ line numbers) → shared `opensBlockFence` fence toggle. **Structural defense:** `tests/parser-desync-invariant.test.ts` — behavioral POSITIVE/NEGATIVE for all 4 tools (all mutation-verified: 5/6 fail when reverted) + an inventory invariant that every `extractWikilinks`/`extractInlineTags` call outside parser.ts is on a `stripCodeAndInline`-sanitized arg. Every finding reproduced against `dist/` + adversarially severity-verified (finders' self-rated HIGHs correctly downgraded to MED — false-positive query results, not data-loss/security). **+6 tests (1461 → 1467).** The 2 tail LOWs (fts5 indented-fence residual of the rc.2 change; `--reranker-top-n` validate-after-acquire handle leak = CRL-1 sibling) → rc.4. **Lesson: the re-sweep pattern paid off exactly as the discipline predicts — rc.1/rc.2 fixed the fence-vs-parser desync at the heading/backlink layer; the mandatory fresh state-driven re-sweep found the SAME class alive at the wikilink/tag/question layer in 4 always-on tools, invisible to every drift/claim gate, found only by comparing each extractor against the canonical parser. The durable close is a shared sanitizer + an inventory invariant, not 4 edits.**
- **v3.11.5-rc.2 (in flight on `@rc`) — MANDATORY post-rc.1 re-sweep: the WRITE-FENCE-TOGGLE class had 2 live read-path siblings (2 LOW).** rc.1 fixed the write-path fence-classifier MED; the re-sweep (grep the `inFence` inventory across `src/`) found the SAME naive line-toggle in two more walkers — the project's signature 'instance fixed, sibling missed': **`read.ts` `extractHeadings`** mistook a line-leading self-contained inline `` ```span``` `` for a block-fence open → `readNote(format:"map")` DROPPED every following heading (reproduced: `[]`); **`fts5.ts` `computeBreadcrumbsByLine`** same phantom fence FROZE the breadcrumb for every subsequent line (search-context degradation). **Class fix:** extracted `opensBlockFence` → a shared leaf `src/fence.ts`; all 3 walkers (write ×2, read, fts5) route through it. **Structural defense:** `tests/fence-toggle-invariant.test.ts` — an inventory invariant asserting every `inFence` walker in `src/` uses `opensBlockFence` (a future walker can't drift back to a naive regex) + behavioral POSITIVE/NEGATIVE tests for both siblings, mutation-verified (both POSITIVEs fail when reverted to the naive regex). **+6 tests (1455 → 1461).** **Lesson: the post-merge re-sweep is non-negotiable and worked exactly as designed — rc.1's audit fix left same-class siblings in adjacent read-path code (heading map + breadcrumb), invisible to every drift/claim gate, found only by state-driven grep of the `inFence` inventory; the durable close is a shared primitive + an inventory invariant, not three edits.**
- **v3.11.5-rc.1 (in flight on `@rc`) — full from-scratch 12-lens project audit of the shipped v3.11.4 → 4 confirmed (1 MED + 2 LOW + 1 INFO), 0 CRIT / 0 HIGH, 0 false positives.** A state-driven multi-lens Workflow audit (adversarially 3-skeptic verified) read every `src/` module as-is (re-run once, `w61qs22lt`→`w5gar65ea`, after the first launch hit transient API errors on nearly every agent). **MED (WRITE-FENCE-TOGGLE-INLINE-SPAN):** the write-path code-fence classifier used a bare `/^\s*(```|~~~)/` line-toggle that DISAGREED with the parser's `stripCodeAndInline` — a line STARTING with a self-contained inline `` ```span``` `` flipped the in-fence flag with no bare closing-fence line to flip it back, so every following line was treated as in-fence and `rename_note` backlink rewrites / `replace_in_notes` edits were **silently dropped** (`rename_note` still reported success). Fixed with `opensBlockFence()` (recognizes a leading fence run CLOSED inline on the same line as an inline span, NOT a block toggle) at both toggle sites in `write.ts`. **2 LOW:** (CRL-1) `prepareServerDeps` validated `--feedback-weight`/`--recency-weight`/`--stale-days` only AFTER opening the vault cache / FTS5 handle / watcher / embed-db / HNSW → a typo'd weight leaked those handles for the process lifetime; both validators hoisted to fail-fast at the top before any acquire (guarded by a structural source-order test + NEGATIVE controls — `server.ts` is un-importable by tests per `no-internal-imports`, mirroring the rc.62 leaf-module split). (CLI-QUANT-NORM-1) stdio `serve` called `parseQuantizationMode()` for validation but DISCARDED the normalized result and forwarded the raw `--quantize-embeddings` alias (`q8`/`float32`/`none`) downstream where `server.ts` exact-matches `int8`/`f32` — silently degrading to the default; mirrored serve-http (capture + forward the normalized mode) + cli-parity guard. **INFO (MCP-INFO-1):** `tool-manifest.ts` always-on-read comment `(33 entries)`→`(34 entries)`. **+9 tests (1446 → 1455).** **Lesson: the MED is the drift/claim-gate blind spot writ large — a write-path fence classifier that silently disagreed with the parser on inline spans, invisible to every numeric/currency gate and only found by a state-driven read of the module; the two LOWs are siblings of already-fixed classes (reserve-before-try leak, serve/serve-http parity). Every fix ships a BEHAVIORAL (not grep-only) regression test.**
- **v3.11.4 STABLE — promoted `@rc` → `@latest` (maintainer "го"; the mandatory pre-promotion re-sweep ran and caught a real recursion before flipping).** Roll-up of rc.1→rc.3: a fresh from-scratch 8-lens post-v3.11.3 audit (`wfadmdten`) returned 4 confirmed (1 MED CRLF-writeback regression + 3 LOW) with 0 CRIT/0 HIGH/0 false positives; the 4 open dependabot PRs (js-yaml/pdfjs-dist/canvas/biome/@types-node) were bundled + behaviorally re-verified; the pre-promotion re-sweep (`w9dphl0ub`) then caught the audit's OWN parser-cap fix bleeding through the codebase's DOMINANT multi-line schema shape (58/160 fields) — reproduced empirically, fixed, re-verified against the identical mutation. Test count 1441 → **1446**. **46 tools · 19 prompts · 1446 tests · 11 languages.** CI publishes `@latest` + SLSA L2 provenance + OIDC MCP-registry sync. **Lesson: this release is the clearest proof yet of the session's central discipline — the full audit found real findings, and the mandatory re-sweep then found one of the audit's OWN fixes recursing its own class. Neither gate alone would have caught it.**
- **v3.11.4-rc.3 (shipped on `@rc`) — the MANDATORY pre-promotion re-sweep caught rc.2's OWN fix recursing its own class.** Before flipping v3.11.4 → `@latest`, a 3-lens Workflow re-sweep (`w9dphl0ub`) of the rc.1+rc.2 diff found rc.2's `fieldSlice` (the PARSERCAP-FIELD-1 fix) required `field: z.` (dot immediately after `z`), so it recognized ONLY the single-line schema shape and silently bled through EVERY multi-line `field: z\n .string()...` chain — **58 of 160 fields in `tool-registry.ts`, the codebase's DOMINANT convention** — into the next field's slice. Reproduced empirically: removing the real `.max(MAX_QUERY_LEN)` cap from `obsidian_hyde_search`'s `query` field still passed the invariant (it found the sibling `hypothetical_answer`'s cap instead — the EXACT false-negative class the fix claimed to close). Fixed: `z\b` (word-boundary, not a literal dot) recognizes both shapes; re-ran the identical mutation → now correctly fails; a matching multi-line NEGATIVE control added. Also closed the re-sweep's one minor: a dedicated behavioral regression test for `getNoteNeighbors`'s NEIGHBORS-OUTBOUND-CAP-2 fix (an outbound neighbor past the display cap must never leak into `tag_siblings`) — previously verified only ad hoc by the auditor, not permanently CI-enforced. Test count 1445 → **1446**. No behavior change beyond the two test-infra fixes. **Lesson (the session's signature pattern, once more): an audit-driven fix's own synthetic test corpus only covers the shape the fixer happened to write — it must be re-probed against the shape the codebase ACTUALLY uses; the pre-promotion re-sweep is what catches an audit fix recursing its own class before it reaches `@latest`.**
- **v3.11.4-rc.2 (shipped on `@rc`) — full-audit response: 4 confirmed findings (1 MED + 3 LOW), 0 CRIT / 0 HIGH, 0 false positives across 8 lenses.** A from-scratch 8-lens Workflow audit (`wfadmdten`: code-core · code-recent · security/DoS/ReDoS · privacy/leak · concurrency · supply-chain · docs-currency · test-infra), each finding adversarially refuted-or-confirmed (4/4 confirmed, 0 refuted). **MED CRLF-WRITEBACK-1 (regression):** `replace_in_notes`/`rename_note` silently flattened CRLF/CR/U+2028/U+2029 → LF across the WHOLE file — the rc.23 `splitLines` migration made the SPLIT terminator-aware but left the rejoin a hard-coded `out.join("\n")` (write-gated, not remotely reachable, but real whole-file line-ending mutation on Windows/CR vaults). Fixed via new `splitLinesWithEnds` (capturing split → preserves each line's own terminator) + 4 behavioral round-trip tests w/ NEGATIVE control (the inventory invariant forbade raw `split("\n")` but never checked the JOIN leg — the durable guard is the round-trip, not the split-only grep). **LOW NEIGHBORS-OUTBOUND-CAP-2:** an outbound neighbor past the display cap (20) was mis-classed as a tag_sibling; `seenOut` now tracks ALL resolved outbound dests (O(1)) while only the readNote/display is capped. **LOW DOCS-TESTCOUNT-I18N-1:** the 9 translation tests badges/code-blocks were stale at 1440 (the rc.1 bump synced only en/fr); bumped + a language-neutral **badge guard** (every README `tests-N` badge === actual). **LOW PARSERCAP-FIELD-1:** `fieldHasCap` did a block-wide token search → a per-field cap drop on a multi-field tool could hide behind a sibling's cap; tightened to bind the cap to the field's own schema slice + per-field NEGATIVE control. Test count 1441 → **1445** (+4 CRLF round-trips). No CRIT/HIGH — the codebase audited exceptionally clean. **Lesson: an audit-driven fix can leave an UN-MIGRATED LEG live for many RCs (rc.23 fixed the split, not the join) — the structural defense for a transform must be a behavioral round-trip, not a one-sided inventory grep; and a detector must bind its property to its SUBJECT (the field), not search the whole block.**
- **v3.11.4-rc.1 (shipped on `@rc`) — safe dependency-bundle: the 4 open dependabot PRs evaluated, bundled, behaviorally re-verified.** All in-range minors/patches, all already green on full CI: **js-yaml 5.1→5.2** (behavioral-area — re-verified the frontmatter scalar-resolution contracts hold: 26 tests; minor, not the rc.6 major), **pdfjs-dist 6.0.227→6.1.200** (PDF+OCR suites pass: 50 tests), **@napi-rs/canvas 1.0.0→1.0.2** (optional OCR canvas, in-range patch), dev **@biomejs/biome 2.5.0→2.5.1** + **@types/node 26.0.0→26.0.1**. Bundled in ONE branch so `package-lock` re-resolves once (the 4-way lock-conflict avoidance — dependabot PRs each touch the lock); the live-advisory-DB re-resolve surfaced **no new advisories** (`check-audit` clean). The biome minor required `biome migrate` (`$schema` 2.5.0→2.5.1, no rule-set change — the rc.78 class, caught by the lint gate). **No `src/` change; 1441 tests unchanged.** The 4 dependabot PRs (#315/#316/#317/#318) auto-close on merge. **Lesson (reinforced): a dep bump's `npm install` re-resolves the WHOLE tree against the live advisory DB — run the scoped-audit gate + the BEHAVIORAL suite for any behavioral dep (js-yaml→frontmatter, pdfjs→PDF/OCR), and a `^`-ranged dev tool (biome) needs its config `$schema` kept in lockstep; bundling the group in one branch is the clean way to dodge the 4-way lock conflict.**
- **v3.11.3 STABLE — promoted `@rc` → `@latest` (maintainer "го").** A docs/i18n release (zero `src/` behavior change) on the fully-audited v3.11.2: README now ships in **11 languages, all at full content-parity** (added 한국어 + Deutsch in rc.1; brought zh/es/hi/ar from abbreviated → full section+content parity in rc.2). **MANDATORY pre-promotion re-sweep** (3-lens Workflow `w3boz7wnz`: i18n-correctness · overclaim/currency · invariant-soundness+hygiene) was run BEFORE flipping `@latest` and CAUGHT A REAL BLOCKER: the rc.1 `v3.10` (`@rc`)→`v3.10` stable relabel was an INSTANCE fix — it left **fr/ru/pt/ja + llms.txt** still advertising the now-stable v3.10 line as a pre-release in the Releases reel (a currency class that recurred 3× this line). Fixed across all 11 surfaces (each file's own "stable" word) + added a **currency regression guard** (`docs-consistency`: no shipped-stable release may be paired with `@rc` in any README/llms reel, non-vacuous + half/full-width-paren-aware) so the class is structurally closed. Lens 3 (invariant-soundness + diff/artifact hygiene) returned CLEAN; the one lens-1 minor (es FAQ drops a single corroborating sentence) accepted. Test count 1440 → **1441** (+1 the currency guard); CITATION.cff bumped to 3.11.3 (stable-pinned drift-guard). CI publishes `@latest` + SLSA L2 provenance + OIDC MCP-registry sync. **46 tools · 19 prompts · 1441 tests · 11 languages.** **Lesson (session signature, reinforced at the promotion gate): the pre-promotion re-sweep is non-negotiable — a state-driven pass over EVERY language surface (not just the rc's diff) is what caught the instance-fix residual in 5 files that all the change-driven gates were green on; an "instance fix that claims class-closure" is this project's signature recursion, and the durable close is always the structural guard, here a currency invariant.**
- **v3.11.3-rc.2 (shipped on `@rc`) — i18n completeness: the 4 oldest translations (zh/es/hi/ar) brought to FULL section-parity + a structural section-parity guard.** The rc.1 multi-lens audit found zh/es/hi/ar were ABBREVIATED (13 H2 / 1 H3 vs the canonical 15 / 2) — accurate but missing the `Set up in your AI agent` (per-client copy-paste prompts + the nested `Example queries` H3) and `API reference` sections, with condensed Trust/Comparison tables and the pre-v3.11.2 differentiator-#3 wording. A 4-agent expand → 4-agent verify Workflow (`wxvv4re62`) restored every missing section + table row + updated differentiator #3 to the current "on-device model, one-time ~110 MB local download, air-gap-safe by default ([enforced](./SECURITY.md), not aspirational)" wording — each re-verified at 15 H2 / 2 H3, anchors resolving, numbers + 11-way switcher intact. **New structural defense:** `docs-consistency` **section-parity invariant** — every translated README must carry the SAME H2 (`## `) and H3 (`### `) count as `README.md` (+ a non-vacuous guard that the source itself has >10 H2). The numeric + anchor + switcher gates check CLAIMS and LINKS but were structurally blind to section COMPLETENESS — which is exactly how 4 translations drifted incomplete across ~15 versions while staying green. **All 10 translations now complete.** Test count 1439 → **1440** (+1 the parity invariant); exact-count surfaces (README.md/fr + llms.txt + AGENTS + ROADMAP + COMPARISON + package.json desc) bumped, the "N+" lower-bound translations stay valid. No `src/` change. **Lesson: a green numeric/anchor gate is NOT a completeness gate — "all 11 languages" hid that 4 were materially abbreviated; the durable close is a structural count-parity invariant (the same convert-undecidable-judgment-into-a-self-checking-count transform as the rc.25 ReDoS fuzz / rc.36 resource-bound manifest), and a multi-lens audit of EVERY artifact, not just the newest, is what surfaced the long-standing drift.**
- **v3.11.3-rc.1 (shipped on `@rc`) — i18n expansion: README now ships in 11 languages (added 한국어 + Deutsch).** Korean + German are large, active Cursor/Claude Code communities not covered by the existing 9. Both translations are COMPLETE + structurally faithful (all 17 H2/H3 sections, the 7-tier retrieval table, the full 46-tool table, the 19-prompt list, the mermaid diagram) — produced + audited by a parallel translation/audit Workflow (`wza7k30ft`, 13 agents: 2 translate → write the files with the dictated stat-fragments + the 11-way switcher + github-slugger-correct localized anchors; 11 audit each README vs the canonical `README.md`). ko/de returned 0 critical (sectionsMatchEnglish ✓, switcherComplete ✓, numbers ✓). Integration: 11-way switcher wired across ALL 11 READMEs (current language bolded, self un-linked), `package.json` `files[]` += ko/de, and **three i18n invariants extended in the SAME PR** — `docs-consistency` per-language numeric (`langs[]` += ko `도구 46개`/`MCP 프롬프트 19개`/`단위 테스트 1439+개` + de `46 Tools`/`19 MCP-Prompts`/`1439+ Unit-Tests`), the switcher cross-link guard ("9"→"11" + readmes[] += ko/de), and the anchor-integrity title (auto-discovers via readdir). **State-driven finding fixed (the audit's one real currency nit):** `README.md`'s Releases highlight reel labelled `v3.10` as `(`@rc`)` though it's promoted to stable (the `@latest = v3.11.x` line right below already said so) — relabelled `v3.10` stable in en/ko/de. **No `src/` change** (docs + files[] + invariants only); test count 1439 unchanged → README badge stays exact. **Known follow-up (stage 2, tracked NOT silently skipped):** the audit confirmed **zh/es/hi/ar are abbreviated** translations missing 3 sections each (`Set up in your AI agent` copy-paste prompts / `Example queries that work well` / `API reference`) + condensed Trust/Comparison tables + the older differentiator-#3 wording (pre-v3.11.2 rewrite, no "enforced, not aspirational") — accurate but INCOMPLETE vs the complete ru/pt/fr/ja/ko/de; a parity pass + a structural section-parity invariant (every translation contains all canon H2/H3) are sequenced as a separate rc so the completeness gap can't keep accruing silently. **Lesson: a new docs surface ships its invariant coverage in the SAME PR (the rc.14 rule), and a multi-lens state-driven audit of EVERY language — not just the 2 new ones — surfaces both the currency nit in the canonical file and the long-standing completeness drift in the 4 oldest translations that the numeric/anchor gates (correctly green on them) are blind to.**
- **v3.11.2-rc.1 (shipped on `@rc`) — competitive-study response (DeusData/codebase-memory-mcp, 21k★): eval rigor + number-led positioning, strictly inside the no-overclaim rule.** A 3-lens Workflow (`ws83kfx9r`) studied that project's positioning playbook (its stars are driven by ONE measured hook — "120× fewer tokens / Linux kernel in 3 min" — + a frictionless single-binary install, NOT feature depth) and produced borrowable, **niche-correct** moves. **(a)** `bench:context` — a context-savings harness (`scripts/bench-context.mjs` + test): per question it measures the `obsidian_context_pack` budget-capped bundle vs the full bodies of the top-K hits an agent would otherwise read in full, over the same hybrid retrieval, and reports the token ratio (our niche analog of their "Nx fewer tokens"). Deterministic synthetic vault; pure helpers `estimateTokens`/`savingsRatio` with NEGATIVE controls; **NO headline figure published** (a representative-vault run + sign-off gate, same as LongMemEval). **(b)** `docs/EVALUATION_PLAN.md` — a pre-registered methodology (no scores by design), committed before the headline runs, codifying the measured/reproducible/reviewed + retrieval-not-QA + zero-result=FAIL + weakness-disclosure bar. **(c)** README hero now LEADS with the already-measured reranker delta (**+15.5 NDCG@10 / +24.7 MRR**, reproducible 60-query ablation) the way they lead with a falsifiable number; the install weight re-narrated as the privacy/grounded MOAT ("the on-device model is WHY recall is private — enforced, not aspirational"); npm keywords broadened (windsurf/zed/aider/gemini-cli/opencode/kilocode). **Explicitly NOT copied (niche guard, documented in CHANGELOG):** their single-binary/zero-dep rewrite (our on-device model IS the moat), any code-domain feature (tree-sitter AST/Hybrid LSP/CALLS edges/dead-code/git-diff), 3D graph-viz as a hero (documented non-goal), the SLSA-3 badge (we are guarded at L2), multi-agent auto-config / hosted team-graph (single-user local-first), and any of their metrics restated as ours. Deferred follow-ups: GitHub-topic broadening (the topic set is live-GitHub-checked by an invariant) + an OpenSSF Scorecard workflow (needs a maintainer web-UI merge). **1434 → 1439 source tests.** No `src/` behavior change. **Lesson: a 21k★ competitor in an ADJACENT domain (code, not notes) is a POSITIONING teacher, not a feature template — borrow the discipline (lead with one MEASURED number, re-narrate constraints as moats, broaden agent-SEO) but never the domain or an unmeasured claim; the eval-plan + the no-publish bench gate are what keep "lead with a number" honest.**
- **v3.11.1-rc.2 (shipped on `@rc`) — the MANDATORY pre-promotion re-sweep caught a THIRD instance of the rc.1 fold class before `@latest` flipped.** Before promoting v3.11.1 → `@latest` I ran the required pre-promotion re-sweep (3-lens Workflow `wom27daqi`): lens 1 verified the rc.1 `foldForMatch` fix regression-free over a 10,368-case differential (vs the old per-char impl extracted from `f78253b^`: 0 unexplained divergences — 36 Greek-sigma + 24 astral wins, rc.18 İ length-change preserved); lens 3 confirmed the diff is tight. **Lens 2 (sibling sweep of all 59 `.toLowerCase()` sites) found a genuine untouched instance:** `obsidian_search_text` (`searchText`) folds each query token whole-string (`tokens.map((t) => t.toLowerCase())` → Greek final `Σ→ς`) while the body folds per code point via `foldWithMap` (medial `σ`), and there `lower.indexOf(needle)` is the SOLE matcher → a final-Σ token scored 0 → the whole note was SILENTLY DROPPED (`totalScore === 0 → return null`) — a RECALL miss, materially worse than the documented-accept `semanticSearch` snippet-centring sibling (cosmetic, scoring runs upstream). Pre-existing (NOT an rc.1 regression), but a sibling of the EXACT class rc.1 fixed → closed BEFORE promotion: route the needle through the shared `foldForMatch` (per code point) so it folds identically to the haystack. Reproduced through `dist/` (`foldWithMap("…ΟΔΟΣ…")`=`"…οδοσ…"`; whole-string `"οδος"`→`indexOf` -1). +1 test (Greek POSITIVE + NEGATIVE control). **1433 → 1434 source tests.** `--diagnostic-search-tools`-gated. **Lesson (session signature, reinforced): the pre-promotion re-sweep is non-negotiable — my rc.1 anti-anchoring sweep traced the read path only as far as the cosmetic `semanticSearch` snippet and never reached `searchText`, where the same fold IS the matcher; the dedicated re-sweep before flipping `@latest` is what caught the more-severe sibling. 3rd instance of the case-fold-asymmetry class this line (rc.1 write `replace_in_notes` + `semanticSearch` accept + rc.2 `searchText`).**
- **v3.11.1-rc.1 (shipped on `@rc`) — v3.11.0-STABLE external-audit response (2 independent auditors: Cursor 4.3/ship + Goose 4.4/ship, both 0 CRIT/0 HIGH/0 MED — the cleanest external round of the line).** Both auditors re-verified the rc.17→rc.25 hardening (hostile-FS symlink, offline cache-miss, dense-`[[a]]` quadratic, the rc.23/25 line-terminator class) HOLDS through `dist/`. **Cursor's 2 LOWs (`SCHEMA-validate-then-write-gap`: `create_note`/`append_to_note` content/frontmatter/separator uncapped at the MCP boundary) → REASONED-REJECTED** (documented per the v3.5.14 rule): a verify+sweep+adversarial Workflow (`wg1onf4t3`, 2 skeptics) confirmed the schema IS uncapped but the sink is strictly LINEAR (no parser/scan amplification), double-bounded already (serve-http `deriveHttpBodyCap` 7.5 MB + `writeNote`/`appendNote` `maxFileBytes` 5 MB), and the project's `parser-input-cap-invariant` deliberately scopes to superlinear inputs — a 1 MB content cap (the auditor's parity with `validate_note_proposal`, whose 1 MB exists for ITS superlinear wikilink scan) sits BELOW the real 5 MB write limit and would REJECT legitimate large notes (a regression). Documented in the invariant's scope comment so it isn't re-litigated. **Goose's only finding (NEL/VT/FF not split) → confirmed NON-DEFECT** (correct per CommonMark; the auditor itself recommended no fix). **The shippable fix came from the Workflow's ANTI-ANCHORING lens — a real LOW NEITHER auditor caught:** `replace_in_notes` (`replaceStringOutsideCodeFences`) folded the NEEDLE whole-string (`search.toLowerCase()` → Greek word-final `Σ→ς`) while `replaceLineOnce` folded the line per code unit (`Σ→σ`), so a case-insensitive replace of a Greek term ending in capital Σ SILENTLY matched nothing (reproduced: `"ΟΔΟΣ"` → count 0). A sibling of the rc.18/rc.21/rc.46 case-fold-asymmetry class. **Fix:** new shared `foldForMatch` (per-code-point, context-free) in `wildcard-match.ts`; the needle + `replaceLineOnce`'s fold loop (now per code point, astral-correct, keeps the rc.18 İ map) both route through it → both sides fold identically. The read-path snippet-centring sibling (whole-string token vs per-code-point `foldWithMap`) is COSMETIC (scoring is unaffected — query+doc TF-IDF tokens fold identically) and DOCUMENT-ACCEPTED (a clean fix needs a TF-IDF-token-pipeline rework, disproportionate). +6 tests (Greek POSITIVE + NEGATIVE control + `foldForMatch` unit incl. astral Deseret + read-path contract). **1427 → 1433 source tests.** **Lesson: the auditor's "validate-then-write gap" was a FALSE EQUIVALENCE (validate caps for its superlinear scan, not because write-content must be capped); two independent adversarial skeptics caught that capping would be a regression — so the round's real value was the anti-anchoring lens finding a genuine correctness bug both external auditors missed, in a class the project keeps re-fighting.**
- **v3.11.0-rc.1 (in flight on `@rc`) — closed-loop retrieval feedback ("Karpathy loop"), the 46th tool.** New opt-in `obsidian_mark_useful` (gated by `--feedback-weight <0..1>`, default 0 = OFF, no tool + provable no-op boost — mirrors the rc.5 recency pattern): an agent records which recalled notes actually helped a query; the per-note usefulness tally (`useful/(useful+notUseful+1)`, Laplace-smoothed) blends into the `obsidian_search` order `(1-w)*relevanceRank + w*feedbackScore`, applied AFTER recency. New leaf `src/feedback.ts` (`FeedbackStore` + `defaultFeedbackFile` + `feedbackScore`); state in a per-vault cache sidecar `<hash>.feedback.json` (relative paths + integer counts + ISO ts ONLY — NO note content, NO query text; 0600; atomic tmp+rename; capped at MAX_FEEDBACK_ENTRIES=100k). Loaded once in `prepareServerDeps` (shared across HTTP sessions → in-session closed loop); wired via `ServerDeps.feedbackStore` + `registerFeedbackTool` + a `feedbackContext` arg to `registerReadTools`. **Privacy/erasure:** the sidecar matches `ENQUIRE_CACHE_ARTIFACT` so cross-vault `prune` erases it (right-to-erasure; preserved across `clear-cache` as user data) — pinned by the erasure-invariant prune-coverage manifest. **K-3:** annotated `...WRITE` (honest `readOnlyHint:false`, consistent with additive `append_to_note`) via a named `markUseful` handler added to `KNOWN_WRITE_HANDLERS` (a feedback-store mutator, NOT a vault writer → gated by `--feedback-weight`, not `--enable-write`). **Count cascade 45→46** across TOOL_MANIFEST (new `feedback` kind + `--feedback-weight` gating) + STABILITY (+Feedback section) + api.md (table + flag + math sentence w/ new `+1 opt-in feedback` term) + README ×5 (incl. all 4 translations) + llms.txt + AGENTS + COMPARISON + ROADMAP, with the docs-consistency api.md-math + STABILITY-gating + llms-breakdown regexes extended for the feedback term. +18 tests (`tests/feedback.test.ts` 16 + 1 erasure-family + cli-parity 14). **1313 → 1331 source tests.** SECURITY.md gains a "Closed-loop feedback store: data-at-rest posture" section. Ships `@rc`; promotion to `@latest` is maintainer-gated (the v3.11.0 line). **Lesson:** the 46th tool is mostly *cascade* — the actual feature is ~250 lines, but the count touches ~13 invariant-guarded surfaces; the structural gates (docs-consistency, K-3, smoke gating-mirror, erasure-invariant, cli-parity, scope-completeness) each had to be taught the new `feedback` category, which is exactly what makes the addition safe.
- **v3.11.0-rc.14 shipped** (`@rc`): **CodeQL HIGH ReDoS — a polynomial-regex class the project had been WRONGLY dismissing as "false positive" at 6+ sites.** The GitHub code-scanning page had one open `js/polynomial-redos` (HIGH) at `fts5.ts:526` (`opts.folder.replace(/\/+$/, "")`) with an in-code "false positive" comment. **The comment's reasoning was wrong + empirically so:** it only considered all-slash input (O(n)); the polynomial trigger is `/`×n + a NON-slash → the anchored `\/+$` retries from every slash position → **O(n²)** (measured `"/"×160k+"x"` = 10.4s; `folder` is uncapped + bearer-reachable → minutes-long hang at a few MB). The **dismissed**-alert list showed the SAME pattern was mis-dismissed as "false positive" at `embed-db.ts:564` + heading strips `fts5.ts:794/797` — a systematically mis-triaged class. **Fix (class-wide, rc.39 "bound the sink" rule):** linear non-backtracking strips in `wildcard-match.ts` (`stripTrailingSlashes`/`stripSurroundingSlashes`/`stripTrailingNewlines`/`stripTrailingHashes`, `charCodeAt` loop, O(n)) replace the `replace(/<class>+$/, "")` idiom at all 9 sites (fts5 ×2, embed-db, search ×2, write ×2, periodic, read). Adversarially re-verified: fixed strip on `"/"×4M+"x"` = 0.60ms, byte-identical to the old regex. Open alert auto-resolves next scan. **1374 → 1379 tests** (+5: timing-bound POSITIVE + quadratic-old-regex NEGATIVE control + static guard + its NEGATIVE). **Lesson: a ReDoS "false positive" justified by REASONING about the regex (the `$`-anchor argument) is exactly what the project's own rule forbids — you EMPIRICALLY time the worst-case shape (`<class>×n + one non-class char`); a CodeQL dismissal is a claim to be empirically proven, not waved through. CodeQL caught what 13 RCs + 2 external audits this line had missed — the static-analysis gate earns its keep precisely on the class human reasoning keeps mis-calling. NEW rule: never dismiss a `js/polynomial-redos` (or any ReDoS) alert on reasoning alone — time the adversarial `class×n + non-class` shape first; if it's >linear, it's real.**
- **v3.11.0-rc.13 shipped** (`@rc`): **dual external rc.12 audit response — 2 independent auditors, all 6 confirmed findings fixed (1 a SECURITY regression I introduced in rc.12).** Two independent audits graded `5fd3cda`: a state-driven re-verification (1 LOW) + a runtime-probe pass (2 MED + 3 LOW). I re-verified all 6 against the code — **all real, 0 hallucinations** — and the pair is the textbook ≥2-auditor case: the runtime-probe auditor found **AUD-01**, a symlink-escape regression my own rc.12 L-7 atomic-write fix introduced, which the re-verification auditor had explicitly "verified clean (a–f)" and MISSED. **Fixed:** **AUD-01 [MED, security]** — `writeNote` overwrite wrote a deterministic `${abs}.tmp` with plain `writeFile` (follows a symlink) + only lstat-checked the final target → a pre-planted `victim.md.tmp` symlink redirected the write out of vault + replaced the note with a symlink; fixed with a **random nonce tmp opened `wx` (O_EXCL)** (adversarially re-verified: external file untouched, note stays regular). **AUD-04 [MED, DoS]** — `frontmatter_search`/`get`/`filter_frontmatter`/`set` keys uncapped → multi-MB key × vault scan × `nfcLower` (~9.5s/4MB); added `MAX_FRONTMATTER_KEY_LEN=256` + parser-input-cap entries. **AUD-03 [LOW] + embed-title sibling [LOW]** — the frontmatter-key PRODUCER fold class (rc.10/rc.12 folded consumers; producers read `fm.tags ?? fm.tag` / `frontmatter?.title` raw → `Tags:`/`Title:` invisible); new shared `lookupFoldedAny` at all 5 producers + producer-fold guard. **AUD-05 [LOW]** — `frontmatter_set` couldn't add a literal `__proto__` key; null-proto maps + `Object.hasOwn` + `defineProperty`. **AUD-02 [LOW, CI-integrity]** — `coverage→OIA` was RED on clean checkout (embeddings.ts comment 29.41% vs rc.12's actual 35.29%; rc.12 passed CI only because cold-CI oia skips Check 6 sans coverage artifact) → synced; CI-wiring is a maintainer workflow follow-up. **1365 → 1374 tests** (+9). **Lesson: my rc.12 L-7 write-fidelity fix shipped a fresh SECURITY regression (symlink-escape) — the signature "audit-driven fix recurs an adjacent class" — and the re-verification auditor blessed it while the runtime-probe auditor (who pre-planted a hostile symlink) caught it. ≥2 auditors with DIFFERENT methodologies was the difference between a symlink escape reaching `@latest` or not. New rule: any fix touching the FS write path must be re-probed against a HOSTILE pre-existing FS state (a security fix needs an adversarial behavioral repro, not just structural re-verification).**
- **v3.11.0-rc.12 shipped** (`@rc`): **external re-audit (rc.11 report) response — 4 confirmed fixes, rest reasoned-rejected.** A user-supplied external auditor graded `5953225` (0 CRIT / 1 HIGH / 0 MED / 7 LOW / 8 INFO) and, to its credit, DOWNGRADED its own prior-pass H-1 (claimed abs-path leak → confirmed it echoes USER input, not host path; no leak). I re-verified every finding adversarially (workflow `w5fd5cs8x`, verify + 3-skeptic per ACCEPT/contested). **Fixed:** **H-2 [LOW, not the auditor's HIGH] — 7th frontmatter-key-lookup site:** `lint_vault_wiki` stale pass read `frontmatter.last_reviewed` RAW (meta.ts never imported `lookupFoldedKey`), so a case-variant `Last_Reviewed` fell back to mtime → wrong stale flag — a genuine sibling of the rc.10 H1 NFC-key class; now folds both `last_reviewed`/`last-reviewed` + behavioral test + narrow regression guard. **L-7 [MED, write-fidelity] — `writeNote` overwrite non-atomic** → `.tmp`+`rename(2)` (same-dir/atomic, preserves the dest's existing mode; the auditor's suggested `0o600` was corrected — it'd lock down user notes); `wx`/symlink/privacy unchanged, `--enable-write`-gated. **L-2** export `applyOfflineEnv` + mock-mod wire-up test (closes the flag-set-but-not-wired gap). **I-1** `.max(MAX_QUERY_LEN)` on `replace_in_notes` search/replace. **1359 → 1365 tests** (+6). **Rejected/reasoned:** L-1 → FALSE POSITIVE (the `tag_filters` array field the auditor cited does NOT exist anywhere — hallucinated); L-5 → KNOWN-WAI (1365 is the source-`it()` count gated by docs-consistency, not the ~1462 runtime expansion — same class as the rc.35-rejected Mavis "overclaim" HIGH); L-6 → ACCEPTED-as-is (auditor's own re-analysis confirms zero leak; a 14-tool PathEscapeError refactor is consistency-only); L-3/L-4/M-1 → DOCUMENT-ACCEPT (rc.4 already mutation-verified; broadening detectors adds false-positive surface for zero current risk, per the rc.39 don't-chase-EDA rule). All 4 auditor POSITIVE claims (NFC producers, K-3 markUseful, offline both paths, SHA-pinned actions) independently re-confirmed. **Lesson: the auditor's headline HIGH was a real sibling-of-a-known-class catch but severity-inflated, and 1 of 7 LOWs was a hallucinated field — per-item verify+skeptic separates the genuine find from noise, and the skeptic caught a harmful detail in the auditor's own proposed fix (the `0o600` perm change).**
- **v3.11.0-rc.11 shipped** (`@rc`): **rc.9-audit LOW/INFO tail — CLOSES the rc.9 external-audit cascade (3 LOW + every other finding reasoned-to-verdict).** After rc.10 shipped the 2 HIGHs, this RC clears the rest. **L1 (LOW, defense-in-depth):** free-form `query`/`tag` inputs were uncapped on `obsidian_search`/`obsidian_context_pack`/`obsidian_search_text` (`query`) + `obsidian_paper_audit` (`tag`) — the HTTP body cap bounds them upstream, but no per-field `.max()` meant an absurd string hit a per-note tokenize/`.toLowerCase()` scan before any bound. Added `MAX_QUERY_LEN=4096` / `MAX_TAG_ARG_LEN=256` at the schema boundary + **extended `parser-input-cap-invariant`** with 3 always-on inventory entries (a future query-fed tool added without a cap fails CI). **L2 (LOW):** `searchText` whole-vault scan got `capScanEntries` (parity with `findSimilar`; the tool is `--diagnostic-search-tools`-gated → defense-in-depth). **L4 (LOW, write-linter UX):** `validate_note_proposal` now surfaces `yaml.coerced` + a `frontmatter-non-mapping` warning (a valid-YAML-non-mapping frontmatter parses green but `frontmatter_set` refuses it per rc.64 — don't surprise the agent after a clean validate). **1358 → 1359 tests** (+1 L4 behavioral with NEGATIVE control). No API changes. **Reasoned-to-verdict (documented, no code):** L3 chmod-multi-user → ACCEPTED (0600 + per-write chmod, single-user threat model); L5 feedback-concurrency-test → ALREADY_OK (rc.4 mutation-verified); I6 name-fold detector inverse shape → WON'T-FIX (the inverse `nfcLower(strip)` is CORRECT; flagging it = false positive, per the rc.39 don't-chase-EDA rule); M2/M3/I2/I3 → ALREADY_OK (confirmed in rc.10 re-verification). **Lesson: the DoS-cap LOWs were genuine-but-bounded, so the proportionate fix is a declarative `.max()` + the inventory invariant — NOT a behavioral rewrite; and an ALREADY_OK LOW still earns a written verdict so the next audit doesn't re-litigate it.**
- **v3.11.0-rc.10 shipped** (`@rc`): **rc.9-audit response — NFC class TRULY closed across the PRODUCER + KEY surfaces (2 HIGH).** A SECOND external audit (on the rc.9 commit) was re-verified by a 32-agent adversarial Workflow (`wtw2wovcb`, 3 skeptics/finding); it confirmed all 3 rc.9 downgrades and correctly DISPUTED the rc.9 L-TAG-1 "closed" claim as a partial fix — rc.9 closed the tag CONSUMER (compare) side, but the PRODUCER (extraction) + frontmatter KEY sides were uncovered siblings (the exact ingest→store→compare data-flow gap the rc.9 scope-correction flagged). **M1 (HIGH):** the inline-tag PRODUCER regexes dropped `\p{M}` combining marks — on macOS APFS an NFD `#café` was captured as `cafe` BEFORE the rc.9 `nfc()` ran (normalize-after-extraction is too late), corrupting the PERSISTED FTS5 tag column. 3 producers (`parser.ts:170`, `meta.ts:221` byte-identical copy, `bases.ts:652` ASCII-only → dropped ALL non-Latin tags) + the `search.ts` TF-IDF tokenizer. Fixed by NFC-normalizing the text BEFORE matching at every producer (composes the mark into the base letter; recovers ANY future mark) + dedupe into a shared exported `INLINE_TAG_RE` + Unicode-ify the Bases regex. **H1 (HIGH):** frontmatter KEY lookups (`frontmatter[key]`/`key in frontmatter`) stayed exact-string at 6 sites (search/dql/bases ×2 + `read.ts` `frontmatter_get`/`frontmatter_search` — the last two the auditor itself MISSED) while rc.9 folded only VALUES → `Status` filter missed `status`. Fixed with shared `lookupFoldedKey` (fold at LOOKUP time: exact-wins, first-own-key-wins on collision; never destructive at parse → write fidelity kept). **Structural defenses (the improve-the-audits ask):** producer-completeness invariant (behavioral NFD/non-Latin extraction + static no-`#[A-Za-z]`-ASCII-tag-regex gate) + key-lookup invariant — closing the why-missed (the rc.9 invariant patrolled fold-HELPER usage at compare sites; blind to the producer regex char-class AND the key-lookup path). **Re-verified ALREADY_OK (auditor wrong):** M2 (rename backlink already folds via findBestMatch/foldName), M3 (`clear-cache` already erases `.tmp` since rc.36). DoS-cap LOWs (L1/L2) → rc.11. **1350 → 1358 tests** (+8). No API changes. **Lesson: an audit-driven fix must verify the WHOLE data path (ingest→store→compare), not just the compare sites — rc.9 fixed compare + claimed the class closed, but producer-extraction (where the data is actually lost) + the parallel KEY surface were untouched; and the behavioral test must GENERATE the failing ingest shape (an NFD INLINE tag), which mine didn't (generator-blindspot, the rc.25/rc.36 pattern). The external auditor's value is exactly this: STATE-DRIVEN data-path reading + feeding real malformed inputs, which the change-driven/claim-driven internal gates structurally cannot do.**
- **v3.11.0-rc.9 shipped** (`@rc`): **external re-audit response (rc.5 report, re-verified against HEAD) — NFC-tag class + uncovered value siblings, closed structurally.** A user-supplied external audit graded the OLD rc.5 commit (0 CRIT / 0 HIGH / 1 MED / 1 LOW / 3 INFO); per the per-item re-verification discipline I re-verified every finding against current HEAD (rc.8) with a 4-lens adversarial Workflow (`wgr7vbn9z` — re-verify + concurrency/full-NFC/semantic-fallback sweeps, 3 skeptics/verdict, 20 agents). **L-TAG-1 (LOW, CONFIRMED + expanded):** tag comparisons lowercased WITHOUT NFC across ~13 sites (the rc.46 NFC name-fold class was never generalized to the parallel TAG identity surface). Closed via shared `foldTag`/`nfcLower`/`nfc` in `name-fold.ts` + NFC at the parser producer (fixes `collectTags` dedup) + the **name-fold inventory invariant extended with the tag signature** (the why-missed root cause — its detector matched only the `.md`-strip shape) + behavioral NFD→NFC nets. **4 NEW siblings the auditor missed** (fresh-lens sweep): DQL `FROM #tag` (rc.8 fixed `WHERE`-values but not the source path, same file), `obsidian_search` `filter_frontmatter` value match, `bases.ts` frontmatter `==`/`contains` (rc.8 value-fix siblings on other surfaces), stale `peek*Meta` "throws only" TSDocs. **Auditor findings DOWNGRADED with reasons (documented per the v3.5.14 rule):** **T-MED-1 (MED → FALSE POSITIVE, 3/3 skeptics)** — `applyDiff`/`syncHnswForFile` are fully SYNCHRONOUS, so cross-file watcher interleave is impossible (JS run-to-completion); the auditor mis-modeled JS as preemptive + their proposed mutation queue would be redundant. Instead pinned the REAL property: `tests/hnsw-sync-critical-section.test.ts` (no `await` in the critical section) + a TSDoc contract — a better defense than proposed. **I-COV-1 (INFO, mechanism FALSE)** — the spawned `dist/` child isn't coverage-instrumented (vitest v8 uses the inspector, not `NODE_V8_COVERAGE`); but re-verification found a REAL adjacent risk — the coverage CI job's `timeout-minutes:5` vs the test job's 10 while doing more work → bumped to 10. **I-HNSW-1 (INFO → WAI)** — `capacity()` has zero production callers; made the fallback an honest `Infinity` sentinel anyway. **1340 → 1350 tests** (+10). No API changes. **Lesson: re-verify EVERY auditor finding against HEAD + severity — this round a MED was a false positive (synchronous critical section), an INFO's mechanism was wrong but surfaced a real sibling, and the one real LOW was 2× bigger than reported with 4 siblings the auditor's own sibling-analysis missed; the durable close for an inventory-invariant blind spot (rc.46 name-fold detector blind to the tag shape) is to extend the detector's signature, exactly as the auditor's own meta-analysis recommended.** **⚠️ SCOPE CORRECTION (rc.9 external audit, confirmed by repro): this closed only the CONSUMER side of the tag class. The PRODUCER regex (`parser.ts`/`meta.ts`/`bases.ts` tag-extraction) drops `\p{M}` combining marks → an NFD inline `#café` is captured as `cafe` BEFORE the rc.9 `nfc()` runs (my producer-NFC was applied AFTER the regex capture — wrong leg of ingest→store→compare), and frontmatter KEYS stay NFC/case-blind (H1, HIGH). So the "closed" claim is an overclaim scoped to compare-time. Full producer-side + key-side closure → rc.10. Lesson reinforced: an audit-driven fix must verify the WHOLE data path (ingest→store→compare), and the behavioral test must GENERATE the failing ingest shape (an NFD inline tag) — mine only fed the frontmatter path, the same generator-blindspot as the rc.25/rc.36 fuzz gaps.**
- **v3.11.0-rc.8 shipped** (`@rc`): **pre-promotion audit response — 5 confirmed (1 MED + 4 LOW), 0 CRIT / 0 HIGH.** Before commissioning the external v3.11.0 → `@latest` audit I ran a from-scratch state-driven full-project audit (7 lenses, 3-skeptic adversarial verify, 25 agents, `whpz3lfup`) on the rc.7 commit `610429c`. Exceptionally clean — but the one MED validated running it (a remotely-triggerable bug in the freshest v3.11.0 surface that the drift/claim CI gates are structurally blind to). **MED [`src/feedback.ts`] — prototype-pollution via `obsidian_mark_useful`:** `FeedbackStore.record()` wrote agent-supplied path strings as keys of a PLAIN-object `entries` map, so `paths:["__proto__"]` (bearer-reachable on serve-http when `--feedback-weight > 0`; the zod `z.array(z.string())` doesn't sanitize VALUES — the unique tool where agent strings become object keys without `z.record()` key-stripping) resolved `entries["__proto__"]` to `Object.prototype` and mutated it process-wide. Fixed → **null-prototype map** (`Object.create(null)`) at both the default-data site + `open()`'s rebuild loop (`__proto__`/`constructor` become harmless own keys; a note literally named `__proto__.md` still round-trips) + NEGATIVE control. **4 LOW:** (1) DQL frontmatter-value `=`/`!=`/`contains` weren't NFC-folded (rc.69 NFC class's last uncovered sibling — the VALUE surface, not the name surface its inventory invariant patrols; new `nfcLower` on the shared `looseEq`/`contains` sinks + regression); (2) `renameNote`'s rc.60 WRITE-1 dest-exclusion was case-sensitive (`e.absPath === toAbsCheck` — a case-variant overwrite dest reopened the data-loss on a case-insensitive FS; now also matches the realpath-canonical `canonicalToRel` + macOS-CI-gated regression); (3) `CITATION.cff` stale at v3.9.1 across TWO stable promotions (deliberately excluded from `check-version-consistency.mjs`, no invariant pinned it) → bumped to 3.10.1/2026-06-22 + a new `docs-consistency` **drift guard** pins it to the latest STABLE (non-rc) CHANGELOG heading; (4) `docs/COMPARISON.md` tied the 46-tool count to "v3.8.x stable" (which had 44 — the NUMBER was gated, the version LABEL wasn't) → decoupled. **1336 → 1340 tests** (+4). No API changes. AUDIT-REQUEST doc committed under `docs/audits/`. **Lesson: a fresh from-scratch audit BEFORE a promotion earns its keep on the NEWEST code — the prototype-pollution lived in the rc.1 feedback feature, behavioral and invisible to every drift/claim gate; and 3 of the 4 LOWs were uncovered SIBLINGS of already-closed classes (NFC-value, WRITE-1-case, version-drift) — the inventory invariants caught the shapes they were written for but not the adjacent surface, the session's signature pattern.**
- **v3.11.0-rc.7 shipped** (`@rc`): **CI-guard flake hardening** (closes the spawn-task flagged after the rc.6 publish-recovery). The rc.6 ship was BLOCKED by a transient: `github-metadata-invariant.test.ts`'s "CI GUARD — gh is actually authenticated" flaked because `gh auth status` makes a network token-validation call that blipped on the main-push run (the IDENTICAL commit passed on the PR run) → main CI red → `release.yml`'s "assert CI green on main" guard correctly refused to publish → recovered by re-running CI then the release workflow. Same flake-blocks-a-release class as rc.20 (`npm ci`). Fix: a generic `retryUntil(attempt, ok, attempts, backoffMs, sleep)` wraps the network-y `gh auth status` (`ghIsAvailable`) + `gh api` (`fetchRepoMeta`) calls — 3 attempts / 750ms backoff, gated on a CI/token context (`CI || GH_TOKEN || GITHUB_TOKEN`) so pure local dev still fails fast (1 attempt, no backoff penalty). The CI-GUARD reuses the once-computed `available` (no double retry) + fails only AFTER the retry is exhausted. **Crucially the guard still fails LOUDLY on a genuine no-auth** — every attempt must fail for `false` (NEGATIVE control: a runner that always fails returns false + was called exactly 3×; POSITIVE: fail-twice-then-succeed recovers to true, first-try-success probes once). Synchronous backoff via `Atomics.wait`. Test-infra only, **1335 → 1336 tests.** **Lesson: a network-dependent CI-GUARD is itself a flake-blocks-a-release surface — bound a retry that tolerates a transient blip while STILL failing on a real failure (every attempt fails), and gate the backoff on a CI/token context so local runs aren't penalized; the negative control (genuine failure still → false, with a call-count assertion proving it retried) is what keeps the retry from silently masking a real auth break.**
- **v3.11.0-rc.6 shipped** (`@rc`): **js-yaml 4 → 5 migration** (#275 — the deferred major from rc.5, done deliberately; closes the last open dependabot major). js-yaml@5 is a BEHAVIORAL major in the frontmatter/`.base` area, so a real migration not a bump: (1) v5 stops coercing YAML timestamps to `Date` → bare dates/timestamps load as **strings** that round-trip FAITHFULLY → **root-fixes the rc.58 date-mutation bug** (no more silent time-appending) + dissolves the rc.66 midnight collision; (2) v5 **`load("")` THROWS** → `bases.ts parseBase` guards an empty `.base` to `{}`; (3) v5 **drops merge-key (`<<`) resolution** = why GHSA-h67p is gone at the ROOT in v5 (not patched); (4) removed the now-redundant `@types/js-yaml` devDep (v5 bundles types — `tsc` clean). Scalar contracts (octal/sexagesimal/underscore — rc.54) are IDENTICAL on @4/@5, re-verified on the installed **5.1.0** with a fresh v-old-vs-v-new differential BEFORE any code change. `normalizeDateOnly` is now DEFENSIVE-ONLY (parse yields strings, not Dates); rc.66 test re-derived to "explicit timestamp preserved verbatim" + a direct-Date defensive unit + a new merge-key-not-resolved security contract test. Full `js-yaml@4`→`@5` reference sweep (SECURITY.md threat-model now "merge keys removed in v5", README ×5, api.md, read/write/bases TSDoc, test comments). check-audit clean (allowlist still empty). **1333 → 1335 tests.** **Lesson: a behavioral-dep major's verification IS the v-old-vs-v-new differential run BEFORE editing (it told me exactly the 3 deltas to handle: Date, load(""), merge-key) + re-derive the contract tests to the NEW behavior rather than force the old workaround; v5 actually DELETES the rc.58 Date-mutation root cause + the merge-key advisory at the source, so the migration is a net simplification, not just a version bump.** A blanket `@4`→`@5` sweep ALSO clobbered two of my own freshly-written HISTORICAL `js-yaml@4` references (caught + reverted) — do the version sweep BEFORE writing new prose, or exclude the file you're hand-editing.
- **v3.11.0-rc.5 shipped** (`@rc`): dependency majors, after per-PR **isolated-worktree evaluation** (the user authorized the GO merges; majors are normally his call). **Merged:** `@types/node` 25.9.3→**26.0.0** (#273, types-only — `tsc` strict + 1427 tests clean) + `actions/checkout` 6.0.2→**7.0.0** (#271, 13 pins → the verified v7.0.0 SHA `9c091bb2…`; v7's `node24` runtime OK on GitHub-hosted runners; **also fixed the `# v6`→`# v7` comment dependabot left stale on all 13**). **Deferred NO-GO:** `js-yaml` 4→**5** (#275) — a behavioral major in the frontmatter/bases area: v5 drops `Date` coercion (rc.58/rc.66 date-collapse contract fails) + merge-key resolution (which is *why* GHSA-h67p is gone in v5 — root-fixed) + makes `load("")` THROW (breaks `parseBase`'s `load()??{}`) + leaves `@types/js-yaml@4` redundant; 2 pinned tests fail under v5. Worth a deliberate migration RC, NOT an auto-bump. Deps/CI-config only, **1333 tests unchanged.** **Lesson: a dependabot major's safety is decided by an isolated-worktree full-gate run + (for a parser dep) a v-old-vs-v-new differential — `@types/node`/`checkout` were clean, `js-yaml@5` broke 2 contracts the bare bump would have shipped; dependabot also routinely leaves the `# vN` SHA-comment stale on a major (the #271 `# v6`-on-a-v7-SHA drift).**
- **v3.11.0-rc.2/rc.3/rc.4 shipped** (`@rc`): **rc.2** — README in 9 languages (added ru/pt/fr/ja to en/zh/es/hi/ar) + 9-way switcher + per-language numeric invariants. **rc.3** — safe dep-bundle (better-sqlite3 12.11.1, vitest/coverage-v8 4.1.9, sharp 0.35.2; markdown-it transitive via typedoc). **rc.4** — **post-rc.1–rc.3 11-lens adversarial audit response (0 CRIT / 0 HIGH-code; feedback feature re-swept CLEAN).** Real findings all in the 9-language README surface: (1) **6 broken in-file anchors / 4 READMEs** — incl. a PRE-EXISTING canonical `README.md` bug (tests badge `#trust` never resolved — "🛡️ Trust" slugs to `️-trust` with a leading VS-16) + ru's 3 un-localized nav/badge anchors + fr/pt `#trust`; fixed to exact github-slugger slugs + new **`tests/readme-anchor-invariant.test.ts`** (resolves every in-file `(#anchor)` in all 9 READMEs vs heading slugs, github-slugger devDep, + NEGATIVE control). (2) **test-count drift** — rc.2 translations born at `1329`, zh/es/hi/ar/AGENTS stale `1311+` → all synced to canonical **1333**. (3) rc.3 CHANGELOG markdown-it = transitive, not direct dev bump. (4) `prune --help` omitted `.feedback.json`. (5) feedback concurrency test was **vacuous** → reworked to assert zero tmp-rename collisions (mutation-verified to discriminate). +2 INFO `feedback.ts` hardenings (per-write `chmod 0600`; `record()` count hoist). **1331 → 1333 tests.** **Lesson:** the i18n surface is the new drift hotspot — auto-translation localizes headings but leaves English anchors, and a lower-bound test-count invariant won't catch a translation born BELOW canonical; the anchor-integrity invariant + the count sync close both. The canonical `README.md`'s own `#trust` had been dead since the Trust section shipped — NO gate checked link TARGETS (only numeric CLAIMS), the exact state-driven blind spot.
- **v3.9.0-rc.35 shipped**: Full **from-scratch** external audit (Mavis, 3 methodologies, on the **pinned rc.34 commit** `7a479bb`) re-verification + 2 genuinely-new LOW code fixes. The clean-slate audit I commissioned graded the CORRECT pinned commit (no staleness this time), confirmed all rc.32→rc.34 P-fixes closed, and **recommends promoting v3.9.0 → `@latest`**. Its one **HIGH** ("1024 tests overclaim, runtime 1088") is **REJECTED** — methodology error: canonical metric is *source `it()` = 1024* by design; runtime expansion is data-driven `for(…) it(…)` loops the auditor mis-claimed not to find (same item the prior round raised as "L-11", already ruled WAI). Fixed the 2 real LOW gaps it surfaced (our own sweeps missed both): **L-3** (`bench.mjs`/`bench-search.mjs` imported pre-split `../dist/tools.js` — only resolved via a stale gitignored `dist/tools.js`; fixed → `dist/tools/index.js` + **OIA Check 12** `STALE-DIST-TOOLS-IMPORT` structural guard) + **AS#5/R-B** (`buildWikilinkGraph` no node cap → DoS; added `MAX_GRAPH_NODES=50_000`, mirroring rc.34 `find_path` R-5). M-DOC-1 (npm `@latest` stale `slsa-3` keyword) + M-DOC-2 (short published `@rc` description) = registry-side, HEAD clean, reconcile on next publish — maintainer-gated. OIA count 11→12. **1024 → 1026 tests.** **Milestone**: this is the **2nd independent external auditor with a different methodology** on the v3.9.0 line (rc.32 deep-audit was the 1st) → the v3.6.1 ≥2-auditor gate for `@latest` is **substantively met** (maintainer's promotion call). **Lesson**: even an on-target auditor mis-rates severity (HIGH on a WAI metric) — re-verify validity AND severity per-item against current code. Full verdict in `docs/audits/v3.9.0-rc.34-external-fromscratch-reverification-2026-06-01.md`.
- **v3.9.0-rc.34 shipped**: Deep-audit (Mavis 10-track on rc.32) re-verification + the genuinely-new privacy/DoS fixes. A much deeper Mavis audit (type-system, supply-chain, STRIDE, privacy/GDPR, MCP-compliance) was supplied; re-verified every claim. Its 2 top non-governance findings (H-3 extractPdfText, SC-1 mcp-publisher) were **already shipped in rc.33** (it graded the older rc.32 commit) — but its **new deep tracks surfaced 3 real, previously-unflagged issues**: **P-2** (`clear-embeddings` left HNSW `.hnsw.bin`/`.meta.json` sidecars — `.meta.json` carries `text_preview` raw text → right-to-erasure gap; now `clearOnDisk` erases them), **P-3** (`embeddings_search` "index not found" error echoed abs vault+embed paths to MCP clients → fingerprinting on serve-http; now path-free), **R-5** (`find_path` BFS no visited cap → unbounded I/O on pathological graph; now `MAX_VISITED=50_000`). Plus **P-1** SECURITY.md content-at-rest honesty. **Rejected** SC-4 (the optional `@huggingface/transformers` is how end users get embeddings — removing it regresses consumers; auditor misread "lockfile dev-only") + M-2 (WeakMap, same misread as v1). **1022 → 1024 tests.** **Lesson**: best round yet — its NEW deep dimensions (privacy/STRIDE) found P-2/P-3/R-5 that all prior rounds + my own sweeps missed, while simultaneously STALE on code tracks (re-flagged rc.33 fixes). Per-item re-verification separates real new findings from carry-over noise.
- **v3.9.0-rc.33 shipped**: External-audit (Mavis on rc.32) re-verification + the 2 genuinely-actionable fixes. A fresh Mavis audit on rc.32 was supplied; I re-verified every claim against the actual code. **Verdict**: broad, mostly-accurate *health* audit (correctly confirms production-ready + concurrency-class-closed) but **materially STALE** — it carried the rc.24 report forward and re-flagged **4 findings rc.28 already closed** (MAX_EMBED_CHARS, peekCache LRU, main() TSDoc, bench p99→max), plus count errors ("14-check OIA"→11, "22 catch{}"→49/zero-empty, "17 overclaims"→18, "10 floors"→11). Fixed the 2 real ones: **H-3** (`extractPdfText` silently returned `pages:[]` on inverted `pageRange` → now throws, parity with OCR's `resolveOcrPageRange`; auditor said HIGH, re-verified LOW — caller-error→empty, not data loss) + **M-9** (`mcp-publisher` downloaded from `releases/latest` → pinned to tag `v1.7.9`). **Rejected** M-2 (TF-IDF "unbounded" — it's a `WeakMap<Vault>`, one entry per live vault, GC'd; auditor misread) + M-3 (catch-count, same as rc.24). H-1/H-2/H-4 branch-protection TRUE but maintainer-only. Full per-finding verdict in `docs/audits/v3.9.0-rc.32-external-mavis-reverification-2026-06-01.md`. **1020 → 1022 tests.** **Lesson**: mirror-image of the rc.24 round — there the auditor MISSED a live CRITICAL; here it went STALE (re-flagged fixes 4 RCs old). Both are why v3.6.1 wants ≥2 independent auditors + per-item re-verification against current code.
- **v3.9.0-rc.32 shipped**: Automate MCP Registry publishing + drift backstop (closes the **registry-staleness class**). A promotion-channel check found the canonical MCP Registry stuck at **3.8.4** vs npm `@latest` **3.8.8** (~7 behind); since Glama/mcp.so/smithery auto-sync from the registry, the stale entry propagated an outdated "current version" across the directory ecosystem. Root cause: registry was published manually after each stable → drifted when skipped. **Fixed structurally:** `release.yml` now auto-publishes **stable-only** to the registry via GitHub Actions **OIDC** (no secret — existing `id-token: write` suffices; gated `dist_tag=='latest'` so RCs never advertise as `isLatest`); new **OIA Check 11** (`MCP-REGISTRY-VERSION-DRIFT`) is a **non-fatal advisory** (remediation is maintainer-gated → can't hard-fail a PR's `oia` gate) that surfaces registry-vs-npm drift (detection verified on the live 3.8.4-vs-3.8.8). OIA count 10→11 (header + AGENTS ×2 + ROADMAP). Same shape as rc.31 (a promotion surface OUTSIDE repo files drifting because its publish path was manual + unwatched → automate publish + add state-driven detector). **1020 tests unchanged** (workflow/script/docs only). Advisory reconciles at the next stable (v3.9.0 → `@latest`) when the OIDC step first runs. Glama "claim" + interactive `mcp-publisher login` remain maintainer-gated (auth).
- **v3.9.0-rc.31 shipped**: Repo-page SLSA overclaim fix + structural guard (**residual of overclaim #15**). A state-driven check of the GitHub repo page found the **About description still said "SLSA-3"** — the unenforced claim rc.7 downgraded to "SLSA L2" across all files and rc.18 fixed on the social card. The About string lives ONLY on GitHub (no file → outside OIA Check 4d's file-scoped guard), so it survived ~23 RCs. Fixed the live About via `gh repo edit` → "SLSA L2"; added `findSlsaOverclaim` analyzer + live assertion to `tests/github-metadata-invariant.test.ts` (already pulls live About/Topics) so it fails on SLSA-3/L3/L4 going forward (positive + NEGATIVE + false-positive controls; detection-power verified: old "SLSA-3" flagged, "SLSA L2" clean). Root shape: a defense scoped to one surface type (files) missed a sibling surface (GitHub metadata) — same class as the recurring scope-too-narrow pattern. **1019 → 1020 tests** (+1); tests/docs only, zero `src/`. Branch-protection (docs+oia not enforced) remains maintainer-only.
- **v3.9.0-rc.30 shipped**: Correction patch — **overclaim instance #18**. A state-driven post-ship audit (after the rc.29 sandbox outage) caught that rc.29's CHANGELOG + CLAUDE.md cited social-card asset sizes from the *first design attempt the EPERM outage ate*, not the shipped files: SVG "9.7→11.8 KB" (real 7.3 KB), PNG "188→49.5 KB" (real 205 KB). No gate scans KB annotations in prose → only a state-driven read found it. Corrected to size-agnostic (kept verified `1280×640`, dropped drift-prone KB). Root cause: drafted the rc.29 entry before the outage forced a re-render, shipped the draft's numbers without re-measuring. Claim-vs-reality class (#15/#16/#17). **Lesson**: after any re-render/re-build, re-measure every quantitative claim in the same commit; asset byte-sizes are deliberately not a tracked claim going forward. **Docs-only — zero `src/`/asset, 1019 tests unchanged.**
- **v3.9.0-rc.29 shipped**: Social card redesign (brand/visual). `assets/social-preview.svg` (+ re-rendered `.png`) — the GitHub social-preview, the most-shared visual of the repo — completely redesigned for a professional, conversion-oriented look at the user's request ("сделай профессиональной и продающей"). Premium dark treatment (gradient + radial glow + dot matrix), SVG logomark (no emoji), value-prop hero "Long-term memory for your AI agents", the "Grounded in the notes you actually wrote — cited, auditable, editable" selling line, qualitative capability chips, `claude mcp add` install CTA, honest "MIT · SLSA L2 · Claude/Cursor/ChatGPT/Codex/OpenClaw" trust line, and a vault→knowledge-graph motif. **Deliberately dropped the old hardcoded count claims (44 tools / 19 prompts) → card is no longer a numeric-drift surface** (sidesteps the rc.18-deferred stat-pill-invariant concern). PNG re-rendered 2× density → 1280×640. OIA Check 4d (SLSA scan of the card) stays green. **Assets only — zero `src/`, 1019 tests unchanged.** _(Shipped through a multi-hour sandbox outage mid-task — env recovered, state re-verified clean before ship.)_
- **v3.9.0-rc.28 shipped**: External-audit re-verification response. A "Mavis" external audit (on rc.24 / `d564eb5`) was supplied; I re-verified every claim against the actual code (treating the report as untrusted). **Verdict**: competent breadth audit, but it **MISSED a live CRITICAL** — the ReDoS we independently found + fixed in rc.25 — so its "no critical findings" was false for the commit it graded; plus factual errors ("14-check OIA"→10; "22 bare `catch {}`"→49 and **zero** truly-empty; "12 floors"→11 files). Fixed the audit's legitimate code findings: **M-2** (`buildEmbedText` `MAX_EMBED_CHARS=8000` clamp — default path unaffected), **M-6** (`peekCache` LRU-bounded via new pure `lruMapSet`), **M-4** (`main()` + `addAdvancedRetrievalOptions` TSDoc), **L-4 residual** (`bench.mjs:4` "p99"→"max" comment). **Rejected** H-4 (style nit, miscounted, all fail-soft-with-body — documented) and **deferred** M-3 (2 new CLI flags = disproportionate surface — documented). **H-1/H-2/H-3 branch-protection are MAINTAINER-ONLY** (verified real via `gh api`: 7 enforced checks not 9, `enforce_admins:false`, 0 reviews — but modifying repo security settings is out of scope for the agent; exact `gh api` command left in the re-verification doc). Full per-finding verdict recorded in `docs/audits/v3.9.0-rc.24-external-mavis-reverification-2026-05-29.md`. **1014 → 1019 tests** (+5: clamp + LRU, each with NEGATIVE controls); zero behavior change. **Lesson**: a single external auditor — even a strong one — can miss the one finding that matters most (a remote-DoS CRITICAL); the v3.6.1 ≥2-independent-auditors gate stands.
- **v3.9.0-rc.27 shipped**: Positioning + discoverability (rc.18-deferred repo-page work + ROADMAP's #1 messaging item). "**Grounded, not extracted**" framing added to README + llms.txt + COMPARISON — the category distinction vs conversation-memory tools (mem0/Zep/Supermemory extract facts from chat logs into an opaque store; enquire recalls the markdown you authored, cited + auditable + editable). `claude mcp add` one-liner promoted into the README hero. Docs only — zero `src/`, **1014 tests unchanged**, all gates green (version×7, docs-consistency, OIA, lint). Deferred (noted in CHANGELOG): social-preview stat-pill redesign (new numeric-drift surface → needs same-PR invariant per the rc.18 rule) + server.json categories (schema re-verify). **This concludes the autonomously-shippable v3.9.0-line backlog** — audit fully closed at rc.26, positioning at rc.27. Remaining items are maintainer-gated: v3.9.0→@latest (fresh external audit, v3.6.1 rule), published LongMemEval score (reference-hardware run), and the v3.10 feature line (forgetting-aware staleness — a sequencing decision: start now vs after v3.9.0 stable).
- **v3.9.0-rc.26 shipped**: Pre-stable audit response, batches 2/3 (test-infra) + 3/3 (docs) — **CLOSES the pre-stable audit**. Same 3-agent audit as rc.25. Two HIGH defense-integrity gaps, both the signature incomplete-class-sweep: (HIGH-1) the **META-invariant** (enforcer of "every invariant has a real NEGATIVE control") was itself partly vacuous — accepted a COMMENTED-OUT `it(` and an EMPTY-BODY `it("NEGATIVE",()=>{})`; rc.23 claimed this fixed but only moved vacuity from "token anywhere" → "token in a title". Fixed: strip comments first + require an ASSERTION in the matched test's callback body (balanced-brace scan) + 3 new controls. (HIGH-2) `cli.test.ts` (22 tests incl. bearer-auth ≥16 + K-1 FTS5-preservation) used silent `return` skips with NO CI-GUARD — rc.23's CI-GUARD sweep missed this file; added the tripwire + converted all 22 to visible `ctx.skip()`. MED/LOW: github-metadata CI-GUARD (MED-1), scope-completeness control now drives the REAL `classifyDefenseFile` not a copy (MED-3), `k1-version-stamp`+`jsonld` added to EXTRA_STRUCTURAL_FILES (LOW-1, ≥11), coverage job explicit `npm run build` (LOW-2). Docs: per-file-floor counter under-counted (two-key `{branches,lines}` regex → reported 10 vs 11 real; gate-passes-while-wrong) + AGENTS 10→11 (F1), ROADMAP stale checkboxes/date + registry-404 reword (F2), STABILITY +`install-ocr-lang` (F3). **1009 → 1014 tests** (+5: 3 META controls + 2 CI-GUARDs; 22 skip-conversions + scope-completeness rewrite net-zero). Tests/scripts/workflow/docs only — zero `src/` runtime change. **Pre-stable audit fully closed** (rc.25 security + rc.26 test-infra+docs); `src/` audited exceptionally clean.
- **v3.9.0-rc.25 shipped**: Pre-stable audit response, batch 1/3 (the @latest-gate blocker) — **3rd ReDoS recursion + the fuzz harness that ends the treadmill**. A fresh independent 3-agent audit (code · docs · tests) on the rc.24 commit reproduced a **CRITICAL** the rc.21/rc.24 guard still missed: `(a?b|b)+$` (9 chars) hangs V8 >5s on bearer-auth `serve-http`. Three shapes the leading-atom analysis couldn't see: (A) **optional leading atom** (`a?b` can start with `a` OR `b`) — fixed by replacing single-token `leadingAtomToken` with a precise leading-**SET** (`leadingAtomSet`) + set-intersection ambiguity check (also removes the over-flag on disjoint `(a?b|c)+`); (B) **nullable body** (`(a?){25}`, `(\s*)*`) — new `branchIsNullable` (recurses into nested groups); (C) **variable-length body** (`(a{2,5})+`, `(\w[ba]{0,3})+`) — new `bodyHasVariableQuantifier`, gated on the OUTER quantifier being unbounded so bounded `(.+){2,5}` stays accepted. **Overclaim #18** (rc.21's "never under-flags" was STILL false post-rc.24, for quantifier shapes). The real fix is the **durable structural defense**: `tests/redos-fuzz.test.ts` runs every SAFE-classified pattern from a 2000-pattern corpus through a real timed `exec` in a worker (with a re-confirm to avoid load-flake) — so the NEXT missed shape fails CI empirically. Dev fuzz at 5000 candidates found 0 under-flags after the fix. `decodeEscapedChar` refactored to `{char,length}` (single source of truth for escape spans) + exported + directly tested. **1002 → 1009 tests** (+7 source `it()`: 5 decode-helper + 2 fuzz; rest array entries). meta.ts branches 76.1%→78.51%. **Lesson**: when a heuristic security detector's class recurs (3× here), STOP enumerating shapes — add an empirical fuzz comparing the static verdict to real execution; that turns the undecidable "did I catch every shape?" into a CI check. Batches 2/3 (test-infra: meta-invariant vacuity + cli.test.ts CI-GUARD) + 3/3 (docs drift) follow as rc.26/rc.27.
- **v3.9.0-rc.24 shipped**: Re-audit response — **ReDoS recursion fix (CRITICAL ×2)**. An adversarial fresh-eyes re-review of the rc.21–rc.23 diffs reproduced two false-negatives in **rc.21's own ReDoS detector** — the textbook "audit-driven fix ships a fresh instance of the class it fixed" recursion. `isCatastrophicRegex` compared SURFACE SYNTAX, not the matched character: (1) `(a|A)+` — the tool compiles `/i` so `a`/`A` overlap, but `leadingAtomToken` was case-sensitive; (2) `(\x61|a)+` / `(\u{61}|a)+` (= `(a|a)+`) — the helper returned the raw byte after `\` (`"x"`), not the decoded char. Both reproduced ~16s V8 hangs at ≤12 chars on bearer-auth `serve-http`. Fixed: `foldCase` (case-fold literals) + `decodeEscapedChar` (`\xHH`/`\uHHHH`/`\u{}`/control/punctuation → real char; octal/unknown → `LEADING_ANY` over-flag) — so the helper's "never under-flags a real overlap" soundness claim is now TRUE (rc.21 asserted it falsely — claimed-guarantee-vs-reality). Also: de-NUL'd the `LEADING_ANY` sentinel (`"\0ANY"`→`"<<ANY>>"`; the NUL byte made `grep` treat meta.ts as binary), broadened **OIA Check 6** to drift-check rc.23's two-key `{branches,lines}` floors (MED — they'd silently fallen out), LOW message wording. **1002 tests** (+11 redos array cases; canonical count unchanged — array entries not new `it()`). `src/`+tests+audit-script only. **Overclaim instance #17** (rc.21's TSDoc claimed soundness the code lacked) + **new anti-pattern**: a heuristic security detector must analyze the MATCHED SET under the sink's flags (case-folded + escape-resolved), not surface syntax — and you must adversarially FUZZ your own detector (the gates can't catch a missed bypass).
- **v3.9.0-rc.23 shipped**: Full state-driven audit response, batch 3/3 (CLOSES the audit) — **test-infra rigor**. The test auditor found the structural-enforcement apparatus weaker than CLAUDE.md claimed: (HIGH) the META-invariant — enforcer of the "every invariant has a NEGATIVE control" rule — passed if `NEGATIVE` appeared ANYWHERE incl. a TODO comment (reproduced), and its `*-invariant.test.ts` glob silently excluded real structural invariants (`no-internal-imports`, `lint`) + itself. **Meta-recursion**: the enforcer of the no-vacuous-invariant rule was itself partly vacuous. Fixed: path (a) now requires the token in an `it`/`test`/`describe` TITLE (real inline control), scan broadened to a curated `EXTRA_STRUCTURAL_FILES` set; `no-internal-imports` got a real inline NEGATIVE control (extracted pure `restrictedImportViolations`), `lint`+`k1-class` got EXEMPT markers. (MED) silent `return`-skips on security surfaces (`security.test.ts` symlink-escape, `fts5.test.ts` injection-escaping) → CI-GUARD tripwires + visible `ctx.skip()` (rc.8 T1 pattern); +CI-GUARD on `e2e-handlers` (401-no-bearer). (LOW) added `vault.ts` per-file floor (most security-critical, was ungated) + `ocr.ts` lines floor. **997 → 1002 tests** (+5). Tests/scripts only. **Full audit closed**: rc.21 (security ReDoS) + rc.22 (docs drift) + rc.23 (test-infra) — 4 HIGH + 1 security-MED + several LOW, each with a structural defense; the `src/` runtime audited exceptionally clean (only the rc.21 ReDoS).
- **v3.9.0-rc.22 shipped**: Full state-driven audit response, batch 2/3 — **docs-drift + structural guards**. 2 claim-vs-reality drifts the gates missed: (HIGH) `STABILITY.md` said `--reranker-model` default = `rerank-multilingual` but code is `rerank-bge` (3rd instance of the α-class fixed in rc.15 TSDoc + rc.16 CLI help — now on a *packaged semver-contract doc*); (MED) `ROADMAP.md` "8 OIA checks" vs canonical 10. Both fixed + each gets a `docs-consistency.test.ts` structural guard: reranker-default pinned to `DEFAULT_RERANKER_ALIAS` (asserts STABILITY names it + does NOT present rerank-multilingual); OIA-count derived from oia-walk.mjs's self-declared `canonical count is "N"` + asserted across AGENTS ×2 + ROADMAP. **995 → 997 tests** (+2 guards). Docs/tests only. **Batch 3 (rc.23)**: meta-invariant comment-bypass + glob-miss (HIGH×2) + silent-skip→ctx.skip+CI-GUARD + vault.ts/ocr.ts FLOORS.
- **v3.9.0-rc.21 shipped**: Full state-driven audit response, batch 1/3 — **SECURITY**. A fresh 3-agent audit (code · docs · tests) on a clean 10-gate baseline reproduced ONE genuine exploit: `obsidian_open_questions`'s rc.9 `isCatastrophicRegex` caught nested quantifiers but NOT overlapping-alternation (`(a|a)+`) — auditor hung V8 >8s with a 200-char-cap-legal 7-char pattern; always-registered tool → remote DoS on `serve-http`. Guard now also rejects **unbounded-quantified AMBIGUOUS alternations** (new dependency-free `alternationBodyAmbiguous`/`splitTopLevelAlternation`/`leadingAtomToken` — leading-atom overlap, a sound over-approximation; ambiguity bubbles through nesting `((a|a))+`). DISJOINT alternations (`(a|b|c)+`, `(cat|dog)+`) + the unquantified default-pattern shape stay accepted (regression-guarded). **993 → 995 tests** (+2 integration + 13 detector cases via data-driven loops). Otherwise the `src/` audit was exceptionally clean (no other CRITICAL/HIGH). **Batch 2 (rc.22)**: STABILITY.md reranker-default α-drift (HIGH, 3rd instance) + ROADMAP OIA-count 8→10 + structural guards. **Batch 3 (rc.23)**: meta-invariant comment-bypass + glob-miss (HIGH×2) + silent-skip→ctx.skip+CI-GUARD propagation + vault.ts/ocr.ts FLOORS.
- **v3.9.0-rc.20 shipped**: Sprint RC 12 — CI hardening (kill the recurring `npm ci` flake that just FAILED a release). The rc.19 release failed at `release.yml`'s assert-CI gate because the squash-merge commit's `test (24)` leg flaked: `npm ci` → `onnxruntime-node` postinstall → CDN `ETIMEDOUT` (same flake as rc.9; the rc.19 PR was all-green — only the main-push re-run flaked). Re-running published rc.19, but a transient blip must never fail a release. All **10 `npm ci` steps** (ci.yml ×8, release.yml, publish-docs.yml) wrapped in a **dependency-free bash retry** (3× / 15s — no marketplace action, nothing new to SHA-pin per rc.14). New **OIA Check 10** (`NPM-CI-NOT-RETRY-WRAPPED`) fails CI on any bare `- run: npm ci` (detection-power verified: inject → flags, wrapped → silent). OIA count 9→10 (header + AGENTS ×2). First time this known flake *blocked a publish* (not just a PR check) → fixed the class + structural guard, not the instance. **993 tests unchanged** (workflows/audit-script/docs only). **This concludes the autonomous v3.9.0-rc sprint (rc.15→rc.20).** Maintainer-gated next: published LongMemEval score, v3.9.0→@latest (fresh external audit), v3.10 staleness feature.
- **v3.9.0-rc.19 shipped**: Sprint RC 11 — LongMemEval **retrieval** harness (the v3.10 credibility-lever engineering half). New `scripts/bench-longmemeval.mjs`: per question, materialize the haystack sessions → one note each in a temp vault → `syncFtsIndex` → `searchHybrid` → score **`recall@k`/`MRR`/`NDCG@k` of the answer-bearing session(s)** (reusing `src/eval.ts`), aggregated per `question_type`; abstention (`*_abs`) counted separately. Measures **retrieval quality, NOT end-to-end QA accuracy** (enquire is a retriever — a QA number would be an overclaim). Pure helpers exported (CLI guarded by `isEntrypoint`); `tests/longmemeval-harness.test.ts` (+11, positive + NEGATIVE incl. path-traversal/abstention/empty controls). Dataset NOT committed (`.gitignore` guard); **headline numbers intentionally NOT published** — maintainer-gated (full reference-hardware run + review, per "measured, reproducible, reviewed — never a placeholder"). `docs/benchmarks.md` "LongMemEval retrieval" section + `npm run bench:longmemeval`. **982 → 993 tests.** Scripts/tests/docs only. **Still v3.10 (maintainer-gated)**: the published score, forgetting-aware staleness, "grounded not extracted" messaging.
- **v3.9.0-rc.18 shipped**: Sprint RC 10 — brand-integrity (social card SLSA overclaim). State-driven read of `assets/social-preview.svg` (the GitHub social card — most-shared visual of the repo) caught a stale **`SLSA-3`** trust badge: a **residual instance of overclaim #15** (rc.7 downgraded SLSA-3→SLSA Build L2 everywhere because `release.yml` only does `npm publish --provenance`=L2; L3 needs the isolated `slsa-framework/slsa-github-generator`). rc.7's sweep + OIA Check 4d's original scope BOTH missed the SVG → the card lied for 11 RCs. Fixed badge → `SLSA L2`, re-rendered the PNG, and **extended Check 4d `claimFiles` to include `assets/social-preview.svg`** (structural class-closer; detection-power verified: injected `SLSA-3` → Check 4d flags `social-preview.svg:137`, clean after fix). Textbook change-driven-misses-state-driven catch; fixed the defense-scope gap, not just the symptom. **982 tests unchanged** (assets+script only). **Deferred**: social-preview stat-pill redesign (new numeric-drift surface — needs same-PR invariant), README hero one-liner, server.json categories (verify schema); then **v3.10 LongMemEval** (#1 lever).
- **v3.9.0-rc.17 shipped**: Sprint RC 9 — AI-search discoverability (Schema.org structured data). `scripts/inject-jsonld.mjs` upgraded from a lone `SoftwareApplication` node to a Schema.org **`@graph`** with 3 cross-linked nodes: enriched SoftwareApplication (`featureList`+`maintainer`), SoftwareSourceCode (`targetProduct`→app), and **FAQPage** (the README 6 Q&A — highest AI-citation type). Builder refactored to a pure exported `buildJsonLdGraph(pkg)` + `FAQ_ENTRIES` (deterministic → unit-testable; CLI guarded by `isEntrypoint`). New `glama.json` (`maintainers: [oomkapwn]`) so Glama.ai attributes + indexes the server. `tests/jsonld.test.ts` (+7: structure + cross-ref + NEGATIVE empty-answer control + README-FAQ-count drift guard). **975 → 982 tests.** Docs/scripts/config only. **Deferred to rc.18**: social-preview regen, README hero one-liner, server.json categories; then **v3.10 LongMemEval** (#1 credibility lever).
- **v3.9.0-rc.16 shipped**: Sprint RC 8 — correctness batch 2 (clears rc.15-deferred backlog + rc.15 post-ship self-audit). (1) **doctor P2-12**: privacy filter was claimed but never wired → `RunDoctorOptions` accepts `excludeGlobs`/`readPaths`, CLI exposes `--exclude-glob`/`--read-paths`, count honest, new `privacy` check (fail-soft on bad glob). (2) **eval**: errored queries conflated with zero-relevance hits → `query_errors` count + per-query `error` flag + banner warning. (3) **http-transport**: stateless `res.on("close")` cleanup was wired AFTER `connect()` (leak on connect failure) → wired before, idempotent + error-safe (parity w/ stateful P2-10). (4) **`--ocr-pdfs`**: warns instead of silent no-op (no `--watch` / no embed-db). rc.15 post-ship self-audit: `communities.converged` surfaced to MCP callers + description fixed; bases.ts HN-2 comment α-class fixed; CLI `--enable-reranker` undersell `+5-10`→measured `+15.5/+24.7`. `tools/search.ts` "citation mis-attribution" **investigated → non-issue** (consistent `bm25 ?? embeddings ?? tfidf` precedence). **970 → 975 tests** (+5). _(CLAUDE.md status entry for rc.16 was backfilled in rc.17 — the rc.16 commit missed it.)_
- **v3.9.0-rc.15 shipped**: Sprint RC 7 — correctness cleanup. 3 MEDIUM/LOW audit findings: (1) `bases.ts` `warnedUnknownPredicates` Set grew unbounded on a stream of distinct malformed predicates (slow leak on long-lived `serve`) → new exported `boundedSetAdd(set, value, max)` caps at `MAX_WARNED_PREDICATES`=1000; (2) `communities.ts` `detectCommunities` gave no convergence signal → `CommunityResult` gains `converged: boolean` (true = Louvain reached a stable partition; false = exited on `MAX_PASSES` cap), edgeless short-circuit reports `converged:true, iterations:0`; (3) `embeddings.ts` `loadReranker` TSDoc `@param` said default `rerank-multilingual` but `DEFAULT_RERANKER_ALIAS` is `rerank-bge` (α-class drift — published TypeDoc was lying). **966 → 970 tests** (+4: bases `boundedSetAdd` describe with NEGATIVE cap-control + communities `converged` assertions). **Deferred to rc.16** (correctness batch 2, heavier integration setup): `tools/search.ts` citation mis-attribution, `eval.ts` query_errors count, `doctor` privacy-glob flags (P2-12), `http-transport.ts` stateless cleanup parity, `server.ts` `--ocr-pdfs`-no-embed-db warning. Next: rc.16 correctness batch 2 + discoverability (FAQPage/SoftwareSourceCode JSON-LD, social-preview regen, glama.json, COMPARISON Obsidian-MCP table, TDQS tool-description pass), then v3.10 LongMemEval harness.
- **v3.9.0-rc.14 shipped**: Sprint RC 6 — supply-chain. SHA-pinned all **28 GitHub Action refs across the 4 workflows** (`actions/checkout@v6` etc. → exact 40-hex commit SHA + `# vN` comment; resolved via `gh api repos/<x>/commits/<tag>`; identical behavior, immune to tag-moving attacks). New **OIA Check 9** fails CI if any third-party action uses a floating tag — makes the pin self-enforcing (verified non-vacuous: all 28 pass silently; + detection power: a `@v6`/`@main` would flag). OIA count synced 8→9 (header + AGENTS + CLAUDE). **966 tests unchanged** (workflows + audit-script + docs only). **Deferred to a follow-up supply-chain RC**: OpenSSF Scorecard workflow + `dependency-review-action` (additive new workflows, each itself SHA-pinned). Next: rc.15 correctness cleanup (bases Set leak, search citation, eval errors, doctor globs, stateless-HTTP), then discoverability (JSON-LD/FAQ, social-preview, glama.json, COMPARISON) + v3.10 LongMemEval.
- **v3.9.0-rc.13 shipped**: Sprint RC 5 — state-driven docs hygiene (cleared the rc.12-deferred backlog). CITATION.cff model names (was `bge-multilingual-gemma2` — not in catalog — → `paraphrase-multilingual-MiniLM-L12-v2` + `bge-reranker-base`); retracted-Cursor-audit comment in `check-version-consistency.mjs` → M-REG-1; AGENTS.md "5 surfaces"→7 (×4) + phantom `bench` subcommand → `install-ocr-lang`; **broken packaged-doc links → absolute GitHub URLs** (README/api.md/benchmarks.md linked `../tests/`, `../src/`, `../bench/`, `./AGENTS.md`, `./ROADMAP.md`, `./llms.txt`, `.github/…` — all 404 in the npm tarball since they're not in `files`); rc.7 CHANGELOG forward-claim ("#16→rc.8, H1→rc.9") got an inline re-sequence note (actually shipped rc.10/rc.11). **966 tests unchanged** (docs/metadata/script only). **Still deferred**: ROADMAP/AGENTS into scope-completeness AUDIT_FILES + OIA Check 3 → AGENTS (needs coordinated docs-consistency change) → later structural RC; SHA-pin Actions + OpenSSF Scorecard → rc.14; correctness cleanup (bases/search/eval/doctor/stateless-HTTP) → rc.15.
- **v3.9.0-rc.12 shipped**: Sprint RC 4 — claim-accuracy. Second structural fix for the "stale-claim defense gap" root-cause theme (first was rc.10's OIA Check 4e for OCR): OIA **Check 7 RC-level sub-check** — it only compared major.minor (so `v3.9.0-rc.3` read as current since `3.9==3.9`), letting pinned "currently vX.Y.Z-rc.N" drift every release. New full-version compare (tombstone-verb skip; bare "as of vX, X ships" excluded as a *since* claim). **Detection-power verified**: flagged README:280/api.md:5/benchmarks:3 (rc.3/rc.6) → all rephrased version-agnostic. Also closed the **reranker undersell** (4 sites said generic "+5-10 NDCG@10" — rc.7's "corrected everywhere" sweep missed them — → measured **+15.5 NDCG@10 / +24.7 MRR**; benchmarks.md:396 BEIR-literature cite left). **966 tests unchanged** (docs + audit-script only). **Deferred to rc.13**: CITATION model names, Cursor-comment, AGENTS 5→7 + `bench` phantom, packaged-doc absolute links, CHANGELOG rc.7↔rc.8 note, ROADMAP/AGENTS into scope-completeness AUDIT_FILES, **SHA-pin Actions + OpenSSF Scorecard**.
- **v3.9.0-rc.11 shipped**: Sprint RC 3 — watcher/HNSW live-update correctness. **H1 (HIGH race)**: fire-and-forget file handler let concurrent same-file saves interleave embed-db upsert + HNSW `applyDiff` + shared `rowsByLabel` mutation → silent ghost-label drift (live in HNSW, absent from embed-db → stale hits). Fixed with a per-absPath promise queue (`fileQueues`) serializing same-file events + `close()` draining in-flight handlers before flush. **`-1` sentinel corruption (HIGH)**: `newIds[i] ?? -1` zip inserted a vector under label -1 on length mismatch, corrupting index + rowsByLabel + sidecar; new exported `zipHnswAddPoints` throws fail-closed. **M1**: `hnsw.ts saveTo` persists live `getCurrentCount()` not the stale build-time `size`. **L2**: correct `kind` on PDF unlink. **959 → 966 tests** (+7: `tests/zip-hnsw-points.test.ts` new + M1 in hnsw.test.ts + H1 invariant in watcher.test.ts; positive + NEGATIVE). chokidar's 250ms awaitWriteFinish coalesces writes so the H1 test asserts the serialization/drain INVARIANT, not a forced race. Next: rc.12 structural defenses (RC-currency check + ROADMAP/AGENTS into audit scope) + state-driven docs + SHA-pin Actions.
- **v3.9.0-rc.10 shipped**: Sprint RC 2 — **closes overclaim #16 (OCR offline enforcement, CRITICAL)** + the OCR canvas-OOM DoS. The TSDoc/CLI-help/SECURITY.md claimed `serve` makes "zero outbound network calls" / "throws if a lang isn't installed" + referenced an `install-ocr-lang` subcommand, but the code did NONE of it (`createWorker` silently CDN-fetched; subcommand didn't exist). Built the real guards: **`assertOcrLangsInstalled`** pre-flight throw (fail-closed, BEFORE any optional dep loads), real **`install-ocr-lang <code>`** subcommand (downloads into `resolveTessdataDir()`, `^[a-z0-9_]+$`-validated), worker pinned `cacheMethod:"readOnly"` to the local cache. **Canvas-OOM (HIGH)**: `clampOcrScale` bounds absolute pixels (`MAX_OCR_CANVAS_DIM`=5000) — the `scale` clamp was a false guard for 14400pt MediaBoxes. **`resolveOcrPageRange`** throws on inverted ranges. **Structural class-closer: OIA Check 4e** (mirrors rc.8 Check 4d for SLSA) fails CI if docs claim the offline guarantee while a code guard is absent — verified non-vacuous (3 guards present → silent for right reason) + detection-power (would flag 4+ lines if a guard removed). SECURITY.md OCR section rewritten to the enforced reality (was "Deferred"/"only outbound call" — both now false) + stable anchor; api.md anchor + subcommand row + `--ocr-pdfs`/`--ocr-langs` help corrected. **944 → 959 tests** (+15 in `tests/ocr-offline.test.ts`, all positive + NEGATIVE, CI-runnable without OCR optional deps). Next: rc.11 watcher/HNSW (H1 + `-1` label).
- **v3.9.0-rc.9 shipped**: First RC of the post-audit sprint — input-validation security. A SECOND five-agent comprehensive audit ran on rc.8 (core-retrieval code · server/transport/CLI code · docs/workflows/config · competitor landscape · repo-page/discoverability); `ROADMAP.md` rewritten around its findings + the competitive read (capability-ahead of every Obsidian-MCP peer; gap = published benchmarks + discoverability). This RC ships the **P0 input-validation** findings: (1) **ReDoS guard** on `obsidian_open_questions` — `tools/meta.ts` compiled a caller-supplied `pattern` (unconstrained zod) straight into V8's backtracking engine and ran it per-line across the whole vault; always-registered → remote DoS on `serve-http`. New dependency-free `isCatastrophicRegex` (star-height ≥ 2 rejection, char-class/escape-aware) + `MAX_QUESTION_PATTERN_LEN`=200 cap, before compile; safe default regression-guarded. (2) **DQL `like` cap** (`MAX_LIKE_PATTERN_LEN`=512) — defensive CPU bound only; `likeToRegex` is catastrophic-safe by construction (emits only `.*`). (3) **bearer ≥16 reconciliation** — `cli.ts` now enforces the length the transport already threw on, with the friendly `gen-token` hint. **927 → 944 tests** (+17, all positive + NEGATIVE controls: `tests/redos-guard.test.ts` new + dql + cli spawn). Sprint sequence: rc.10 OCR offline #16 + canvas-OOM → rc.11 watcher/HNSW (H1 + `-1` label) → rc.12 structural defenses + docs + supply-chain → rc.13 correctness → rc.14 discoverability. Audit after each RC.
- **v3.9.0-rc.8 shipped**: Integrity-batch #2 from the exhaustive file-by-file audit (Opus 4.8 1M re-read of every `src/` module, doc, workflow, script). 9 state-driven fixes, all docs/tests/scripts/config (zero `src/` runtime change). **S2** — new OIA **Check 4d**, the FIRST structural defense for the rc.7 "claimed-guarantee vs code-guard" class: statically derives the SLSA Build Level `release.yml` actually earns (`--provenance` = L2; L3 needs `slsa-framework/slsa-github-generator`) and fails CI if any claim surface (README/package.json/llms.txt/COMPARISON/STABILITY) asserts a higher level, with a roadmap-context skip; **negative-control verified** (injected `SLSA-3` into STABILITY.md → `SLSA-LEVEL-OVERCLAIM` fires + exit 1, reverted clean). **S1** — bench "p99" on 5 samples was unconditionally `samples[4]` = the max; relabeled `max` (value never moved, label lied). **M3** — bench write-path tag `#new-tag-${Date.now()}` → `#new-tag-stable` (run-to-run determinism). **T1** — 6 security-critical privacy tests used a SILENT `return` when the build / better-sqlite3 was absent (green-passed the privacy-at-indexing-time surface with ZERO assertions and no skip count); converted to visible `ctx.skip()` + one **CI-GUARD tripwire** that hard-fails in CI if the native-dep preconditions vanish (transitively protects every native-dep soft-skip; this is the +1 test). **W1** — 2 stale "Memory layer"/"v3.6.3 hype" `it()` titles in github-metadata-invariant realigned to their `ABOUT_LEADS_WITH` assertions (α-class drift in test descriptions). **S4** — benchmarks.md +25/+16 → precise +24.7/+15.5. **C1** — biome 2.4.14 binary / 2.4.15 schema → 2.4.16 unified. bug_report Node placeholder v20.11.0 → v22.13.0 (below `engines` floor). **927 tests** (+1 CI tripwire); coverage 89.37% lines / 81.26% functions; OIA clean (8 checks incl. new 4d). #16 OCR offline enforcement → rc.9; H1 watcher per-file serialization → rc.10.
- **v3.8.x stable post-promotion backlog status** (post v3.9.0-rc.4): All v3.6.x → v3.8.0 round-N findings closed in their respective RCs (see CHANGELOG history). **External audit blocker per v3.6.1 rule re-evaluated as MET** (see header note + `docs/audits/v3.8.0-rc.15-external-2026-05-25.md`). v3.8.5 closed T-2/T-3/T-4 E2E. v3.8.7 closed P2-10/P2-11. v3.9.0-rc.1 closed OCR'd PDF watcher embed-sync. v3.9.0-rc.2 closed HNSW in-memory live update. v3.9.0-rc.3 closed R-10 adaptive HNSW refill. v3.9.0-rc.4 closed full state-driven self-audit (3 HIGH + 4 MEDIUM docs findings + 2 META extensions). Remaining v3.9.x items: serve-http parity residual (P1-3), OCR concurrency/timeout, search underfill (P2-7), FTS5/embedding chunking parity (P2-8), doctor privacy filters (P2-12), npm package broken links (P2-18), canonical CLI docs (P2-20), setup-snippet mkdir (P3-29), HNSW filter-during-search architectural (v3.9.x+), HNSW disk persistence on live update (v3.9.0-rc.5+).
- **v3.7+ deferred** (no audit pressure, parked for v3.8+):
- E2E preservation tests for `setup` / `eval` / `build-embeddings` (currently only `index` has E2E preservation+forced-rebuild pair from v3.6.4).
- Strengthen K-1 invariant via TypeScript AST: enforce that peek result is CONSUMED in the constructor's `modelAlias` / `tokenize` arg, not just present in scope.
- Recursive `SRC_DIRS` scan in `tests/k1-class-invariant.test.ts` (currently hardcoded `["src", "src/tools"]`).
- Cache peek result in `prepareServerDeps` to avoid hot-path SQLite open+close on every `embeddingsSearch` call (~5-10ms × N searches).
- Re-run `npm run bench:retrieval` post-v3.6.4 and republish `docs/benchmarks.md` (ensure K-1 fix doesn't impact retrieval numbers).
- Marketing positioning permeation into `docs/api.md`, `docs/QUICKSTART.md`, `docs/COMPARISON.md` opening paragraphs (still framed as "MCP server", not "memory layer").
- Per-file branch-coverage thresholds for security-critical modules (`http-transport.ts` 67%, `tools/search.ts` 68%, `tools/meta.ts` 68%, `tools/media.ts` 68%). Global 75.4% hides these dips.
- GitHub repo metadata invariant test (`About` + `Topics` drift caught by no CI today).
- **Method lessons accumulated through the v3.6.x cascade**:
1. **Every minor/major needs ≥2 independent external auditors with DIFFERENT methodologies.** Internal multi-layer audits = breadth + speed but NOT a substitute for fresh external perspective. See `~/.claude/.../memory/method_full_system_audit.md`.
2. **Class fix ≠ instance fix; structural enforcement > CHANGELOG promises.** When a methodological bug recurs in two consecutive releases, the fix is a test gate, not another patch. (See `tests/k1-class-invariant.test.ts`.)
3. **Caller-pattern coverage ≠ helper-pattern coverage.** Unit tests for utility functions don't catch callers forgetting to use them. Negative-control test pins the bad behavior.
4. **Audit BEFORE ship, not after.** Reactive post-ship patches create release churn and overclaim risk. Audit during code review; after a CRITICAL/retroactive patch, allow 24h of main dogfooding before the next patch.
5. **Marketing positioning is continuous calibration.** Same capabilities, evolving search vocabulary — Claude Memory (Oct 2025) and Skills (Nov 2025) shifted "memory for AI agents" into mainstream developer-discovery terms.
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

