# Changelog

All notable changes to claude-obsidian. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).

## [1.9.2] - 2026-05-27 (prompt-cache hardening + path-handling robustness)

Ports Anthropic prompt-caching best practices into the **one** place the plugin calls the Anthropic API directly: tier-1 contextual-prefix generation in `scripts/contextual-prefix.py`. Verified by full-repo sweep that `cache_control` and the Anthropic API surface exist nowhere else (incl. `claude-canvas/`). No change to retrieval output — API payload shape + observability only.

### Changed

- **Cache only above the Haiku floor** (`scripts/contextual-prefix.py`). The page-body `cache_control` marker is now attached only when the body clears the Haiku 4.5 minimum cacheable size (`HAIKU_CACHE_MIN_CHARS = 16384`, ~4096 tokens × 4 chars/token). Below the floor the Anthropic API silently ignores the marker, so the prior unconditional marker was a no-op that misled the reader. Extracted as the pure, unit-tested `cache_control_for()`.
- `.claude-plugin/plugin.json` + `marketplace.json` version 1.9.1 → 1.9.2.

### Added

- **Cache telemetry** (`scripts/contextual-prefix.py`). The tier-1 path now logs `cache: wrote=<N> read=<N> tok` from the response `usage` fields — integers only, never page content, preserving the v1.7.1 data-egress posture. Implements the docs' "monitor cache hit rates" guidance and reveals whether the body cache is actually firing given the floor.
- **Sequential-invariant note** at the chunk loop (`process_page`), documenting that cache reads depend on chunk 0's response landing before chunk 1 is sent (Anthropic prompt-caching concurrency rule). Guards against a future parallelization silently zeroing every cache read.
- `tests/test_contextual_prefix.py` — hermetic coverage of the `cache_control_for()` floor decision (below / at / above floor, empty body, floor constant matches the documented Haiku minimum). Wired into `make test` (now 9 suites) + `make test-contextual`.

### Documented

- Tier-1 "prompt-cached" claim now states the ~16 KB Haiku floor in the `contextual-prefix.py` module docstring and the ingest diagrams in `docs/compound-vault-guide.md` and `skills/wiki-retrieve/SKILL.md`, so docs match runtime behavior.

### Fixed

- **Explicit missing or out-of-vault page paths now fail cleanly** (`scripts/contextual-prefix.py`). A single explicit path that does not exist exits 3 (was: silent exit 0, swallowed by the `is_file()` filter in `main()`); a path resolving outside the vault exits 2 with a message (was: a raw `ValueError` traceback from `relative_to()`). `--all` runs are unaffected.
- Removed the dead `EXIT_NO_ADDRESS` (exit 5) constant and its docstring entry — it was defined and documented but never raised. Renamed the shadowed `prefix` progress-label variable in `process_page` to `progress`.

### Verification

- `make test`: 9 hermetic suites green; the contextual suite now mocks the API payload to assert `cache_control` attaches only above the floor and the model reply is truncated to one line. No regressions.
- Explicit-path exit codes confirmed by run: missing in-vault path → 3, out-of-vault path → 2, valid page → 0, `--all` unaffected.
- `python3 scripts/contextual-prefix.py wiki/getting-started.md --peek`: tier selection unchanged (synthetic without `--allow-egress`).
- Live tier-1 telemetry leg (sends page bodies off-machine) requires explicit `--allow-egress` and is left to operator verification per the repo's consent design.

## [1.9.1] - 2026-05-18 (v1.9.0 audit hardening)

Patch release closing **6 of 6 remaining HIGH/MEDIUM** findings from the v1.9.0 pre-public-promotion audit ([`docs/audits/v1.9.0-pre-public-promotion-audit-2026-05-18.md`](docs/audits/v1.9.0-pre-public-promotion-audit-2026-05-18.md)) plus 3 LOW hardening items. Composite score moves from 91.6 to ~94 raw average. Public-promotion ship verdict remains GREEN.

### Fixed (defensive hardening)

- **H4 — stale-lock reaper wiring** (`hooks/hooks.json`). New SessionStart command runs `bash scripts/wiki-lock.sh clear-stale --max-age 3600` at every session resume/startup. Locks orphaned by a crashed batch ingest get reaped automatically on the next session, not just on operator demand.
- **S2 — opt-out gate for PostToolUse auto-commit** (`hooks/hooks.json`). Hook now exits early if `.vault-meta/auto-commit.disabled` exists. Default behavior unchanged for existing users; per-vault opt-out is one `touch` away. Useful for shared repos, CI runs, or any scenario where the operator wants to commit manually.
- **Data M3 — symlink canonicalization** (`scripts/wiki-lock.sh:110-142`). `validate_path()` previously rejected literal `..` segments but did not canonicalize symlinks. A symlink inside `wiki/` resolving outside `VAULT_ROOT` could escape. Now resolves via `python3 os.path.realpath` and rejects any path whose canonical form is outside `commonpath(VAULT_ROOT, target)`. Cross-platform (GNU coreutils + macOS BSD); no realpath flag dependency.
- **Data M4 — `.vault-meta/locks/.gitkeep`** + gitignore pattern change. `.vault-meta/locks/*` (not the dir itself) is now ignored; `.gitkeep` is whitelisted so the directory ships on fresh clone. Runtime behavior unchanged (`ensure_dirs()` always created it lazily); this just removes the first-acquire side-effect on directory presence.
- **Data M1 — rerank.py warning routes to hook.log** (`scripts/rerank.py:158-176`). When the embed-cache lock is unavailable after 3 tries, the WARN line now also appends to `.vault-meta/hook.log` with a timestamp, so users see the event via `wiki lint` or by tailing the log. stderr-only was invisible to most callers.
- **S4 — ollama-localhost assert** (`bin/setup-retrieve.sh:97-117`). If `OLLAMA_URL` env var points off-localhost, refuse to probe unless `--allow-remote-ollama` is passed. Mirrors the existing `scripts/tiling-check.py:351` gate. Closes a defense-in-depth gap where a malicious env var could redirect probes to an attacker-controlled endpoint.

### Documented

- **H2 + S3 — multi-tenant threat model** (`SECURITY.md`). New "Threat model: single-tenant vault" section documents three intentional design choices (cross-process lock release, auto-commit hook scope, filesystem-permission trust boundary) and the mitigations for shared-host deployments. Closes the documentation gap the audit flagged: the design choices are correct for single-tenant, but operators in shared-host scenarios deserve to know what changes for them.

### Changed

- `.claude-plugin/plugin.json` + `marketplace.json` version 1.9.0 → 1.9.1.

### Deferred to v1.9.2 (not in this release)

- **Data M2 — embed-cache + chunk-orphan GC**: new `--gc` subcommand for `bm25-index.py` + `contextual-prefix.py` that prunes entries whose backing pages no longer exist. Requires hermetic tests covering edge cases; scoped for a dedicated release.
- **W1 — wiki/meta/ release-session note relocation**: 12 files with incoming wikilinks need a coordinated graph update. Bundled with References M1+M2 (17 dead wikilink targets) for one-pass wiki cleanup.
- **Security S1 — Excalidraw checksum pin**: requires upstream release hash verification + maintenance burden for future releases. Tracked separately.
- **GROW note from v1.9.0 audit — 7th always-check cut in `agents/verifier.md`**: cross-file string consistency check (catches the class of bug where a single-file fix should have been a multi-file cascade, like the 548d294 + F1-F5 sequence).

### Verification

- `make test`: 8 hermetic suites green (~1234 assertions). No regressions.
- Live symlink-escape test on `wiki-lock.sh validate_path`: `ln -s /tmp/foo ./escape && bash scripts/wiki-lock.sh acquire escape/x.md` correctly rejects with "path resolves outside vault via symlink".
- Both plugin manifests parse as JSON.
- Pre-push verifier agent dispatch on staged diff: SHIP, 0 BLOCKER, 0 HIGH.

## [1.9.0] - 2026-05-18 (10-principle thinking framework)

Minor release adding the **10-principle thinking framework** as a first-class layer of the plugin. The framework (OBSERVE-OBSERVE-LISTEN-THINK-CONNECT-CONNECT-FEEL-ACCEPT-CREATE-GROW) is integrated at three levels: as the new `/think` skill, as an appendix on every existing SKILL.md, and as the methodology spine for the v1.8.0 pre-push audit (which used it as its phase structure). Skill count: 14 → **15**.

### Added

- **`skills/think/SKILL.md`** — new skill #15. Canonical source for the 10-principle thinking loop. Walks Claude through external observation, metacognition (the often-skipped one), active listening, first-principles analysis (six-cut kernel lives here), lateral connection, system orchestration, intuition, intellectual humility (anti-sycophancy enforcement), generative output, and iterative growth. Triggers on "think this through", "10-principle review", "/think", "OBSERVE LISTEN THINK", "deep think", "systematic thinking", "structured reasoning". Allowed tools: `Read, Grep, Glob, Bash` (read-only; loads structure and discipline, not mutation). ~250 lines covering the 10 principles with one-paragraph definitions, when-to-invoke guidance, stage-by-stage prompts (5 questions per stage), anti-patterns (the common skips), and composition notes with `/best-practices`, `/save`, `/wiki-lint`, `agents/verifier.md`, and `/autoresearch`.

### Changed

- **All 14 existing SKILL.md files** — each receives a unique "## How to think (10-principle mapping)" appendix at the end. Per-skill 10-row table mapping each principle to that skill's specific work. Examples: wiki-mode's OBSERVE-internal is "audit the assumption that mode=generic is the default"; autoresearch's OBSERVE-internal is "am I steering the search toward what I already expect to find? confirmation bias kills research"; wiki-lint's FEEL is "a lint report should empower, not shame". Each appendix is non-trivial and skill-specific (not a template stub), satisfying the plan's verification rule #12.

- **`.claude-plugin/plugin.json` + `marketplace.json`** — version 1.8.2 → 1.9.0. Description unchanged (the framework is additive to the v1.8.0 + v1.8.2 surface).

### Repo hygiene (first-public-release prep)

- **`CONTRIBUTING.md`** (NEW) — workflow, six-cut self-review checklist, commit conventions, test requirements.
- **`CODE_OF_CONDUCT.md`** (NEW) — adopts Contributor Covenant v2.1 by reference.
- **`SECURITY.md`** (NEW) — private disclosure policy, response SLA, scope, credit policy.
- **`.github/ISSUE_TEMPLATE/bug_report.md`** (NEW) — reproduction + environment + skill-affected fields.
- **`.github/ISSUE_TEMPLATE/feature_request.md`** (NEW) — scope + compatibility + testing fields.
- **`.github/pull_request_template.md`** (NEW) — six-cut self-review + test results + verifier verdict + CHANGELOG reminder.
- **`.github/workflows/test.yml`** (NEW) — CI runs `make test` on push/PR + validates SKILL.md frontmatter + agents `tools:` declaration + plugin manifest JSON validity.
- **Manifest + doc URL updates** — `plugin.json`, `marketplace.json`, and 18 other files updated to reference the canonical repository URL.

### Why this release

Per the v1.8.0 pre-push audit's GROW notes (`docs/audits/v1.8.0-pre-push-audit-2026-05-18.md` §10), the 10-principle framework proved its value as the AUDIT's mental spine — OBSERVE-internal forced explicit bias documentation; GROW forced a feedback-loop section. Shipping it as a first-class skill (and as appendices on all existing skills) makes the discipline available to every future invocation, not just to one-off audits. This is the GROW step of the audit itself, embodied in code.

### Compass axis status after v1.9.0

| Axis | v1.8.0 | v1.9.0 |
|---|---|---|
| Compounding wiki primitive | #1 | #1 |
| Multi-writer safety | #1 | #1 |
| Retrieval architecture (free tier) | #1 | #1 |
| License / openness | #1 | #1 |
| Methodology support | #1 | #1 (deepened by framework integration) |
| Derivative outputs | NO | NO (v2.0 scope) |
| GUI / install ergonomics | NO | NO (v2.5+ scope) |

5 of 7 axes #1 (unchanged count, but methodology axis deepens — claude-obsidian is now the only Claude+Obsidian plugin shipping methodology modes AND a first-class thinking-loop framework AND per-skill thinking guidance).

### Migration notes

- v1.8.x vaults: no action needed. All existing skills work identically; the "How to think" appendix is additive guidance, not behavioral change.
- `/think` is invocable from any project that has this plugin installed; it does not require a vault.
- The framework can be invoked explicitly (`/think <problem>`) or applied implicitly when an existing skill's appendix references it.

### Composition

- `/think` + `/save` = the canonical compounding loop. Apply the 10 principles to a problem; when done, save the insights worth not re-deriving.
- `/think` + `/best-practices` = engineering discipline at the THINK stage of the framework. The six-cut kernel IS the inside of stage 4.
- `/think` + `agents/verifier.md` = an OBSERVE-internal substitute for solo work — fresh-context reviewer that catches biases the chair missed.

## [1.8.2] - 2026-05-18 (pre-push audit closure)

Patch release closing **all 4 HIGH findings + 1 leaked BLOCKER-class hardening** from the v1.8.0 pre-push audit. Audit: [`docs/audits/v1.8.0-pre-push-audit-2026-05-18.md`](docs/audits/v1.8.0-pre-push-audit-2026-05-18.md). Per the audit's strict push gate (any BLOCKER halts), v1.8.2 takes the release from YELLOW (0 BLOCKER / 4 HIGH) to GREEN (0 BLOCKER / 0 HIGH).

### Fixed

- **H1 — `scripts/detect-transport.sh`: implement `manual_override`.** The documented escape hatch for MCP-only users (`"manual_override": true` in `transport.json`) was documented at `wiki/references/transport-fallback.md` but never honored by the script. v1.8.2 parses the existing snapshot BEFORE auto-detection; when `manual_override: true`, the user's `preferred` and `fallback_chain` are preserved across normal cycles AND `--force` refreshes. Auto-detection still runs to refresh the `available.cli.*` informational fields. Field round-trips through the JSON schema (new `"manual_override": <bool>` in every snapshot). Verified by smoke test: a pinned `"preferred": "mcp-obsidian"` survived a `--force` refresh.
- **H2 — `agents/wiki-ingest.md`: add `Bash` to tools + add `## Mode awareness (v1.8+)` section.** The parallel batch-ingest sub-agent was missing `Bash` in its `tools:` frontmatter despite mandating `bash scripts/wiki-lock.sh acquire/release` in its body — this defeated the v1.7 multi-writer safety guarantee. Sub-agent was also missing v1.8 mode-routing awareness, so batch-ingest in LYT/PARA/Zettelkasten vaults filed to v1.7 generic paths. Both gaps closed.
- **H3 — `skills/autoresearch/SKILL.md`: new `## Web egress hygiene (v1.8.2+)` section.** Four sub-policies: (1) URL validation (reject `file://`, `javascript:`, RFC1918), (2) content sanitization before write (strip `<script>`/`<iframe>`, escape `[[/]]` injections, reject embedded frontmatter delimiters, 50KB truncation), (3) per-loop cost expectation (~45 WebFetch calls per run), (4) failure mode (log to `wiki/log.md`, never silently swallow). The router's `safe_name()` is the FILENAME guard; this is the BODY-content guard.
- **H4 — `skills/save/SKILL.md`: prepend `Step 0: Decide the destination root`.** The skill body assumed project-local `wiki/` filing while the global `~/.claude/CLAUDE.md` `/save` rule mandates personal-vault filing for cross-project saves. Step 0 makes the routing decision explicit with three rules in order: user-explicit override → project/global CLAUDE.md `/save` override → project-local `wiki/` default. Path sanitization still applies regardless of destination root. Workflow step 5 now requires a collision check (ASK before overwrite).
- **wiki/references/transport-fallback.md:** §Manual override now describes the actual v1.8.2 behavior (preserves `preferred`+`fallback_chain` while still refreshing `available.cli.*`; field round-trips through snapshot).

### Changed

- **`.claude-plugin/plugin.json` + `marketplace.json`:** version 1.8.0 → 1.8.2.
- **`docs/audits/v1.8.0-pre-push-audit-2026-05-18.md`** (NEW): full pre-push audit report. 14 per-skill scores (average 84.6/100), per-tier finding ledger, v1.8.2 fix replay, hook safety, manifest accuracy, push-gate decision tree. Audit methodology used the 10-principle thinking spine (OBSERVE-OBSERVE-LISTEN-THINK-CONNECT-CONNECT-FEEL-ACCEPT-CREATE-GROW) as audit phases.

### Verification

- All 8 hermetic test suites green (~1234 assertions): allocate-address, tiling-check, boundary-score, bm25-index, retrieve, wiki-lock, concurrent-write, wiki-mode.
- v1.8.2 manual_override smoke test: pinned `preferred="mcp-obsidian"` survived `--force` refresh.
- All path-traversal attack vectors against `route_path()` confirmed sanitized inside vault root (6 dedicated assertions).
- mkstemp atomic write yields 0600 perms (verified via `stat`).
- `--mode` preview is non-mutating (verified via mtime).

### Known issues at v1.8.2 (deferred to v1.9 or later)

The pre-push audit also surfaced 14 MEDIUM and 45 LOW findings. None block release. Triage in audit §12. Highlights:
- `wiki-cli` mcp-obsidian + mcpvault tiers documented as fallback positions 2/3 but unreachable from auto-detection (deferred — manual_override is the workaround until MCP auto-detect ships in v1.7.x patch).
- No `tests/test_detect_transport.sh` yet for v1.7+ transport script (planned v1.8.3).
- 7 skills declare incomplete `allowed-tools` lists (missing `Bash` despite shelling out) — works in practice (harness default-allows), but convention violation (planned v1.8.3).
- `tests/__init__.py` missing → `python3 -m unittest tests.X` form fails (direct invocation works) (planned v1.8.3).
- Various doc/reality drift around the v1.8 ID format change (14-digit → 20-digit), v1.8 `--mode` flag not yet referenced in consumer docs (deferred LOW polish PR).

## [1.8.0] - 2026-05-17 (methodology modes — closes compass priority gap 5)

Minor release closing the **5th and final priority gap** from the May 2026 compass artifact: methodology support. Adds the **`wiki-mode`** skill with first-class support for four organizational styles (LYT / PARA / Zettelkasten / Generic). After this release, claude-obsidian is **#1 on 5 of 7 axes per compass framework** — up from 4/7 in v1.7. Full guide: [`docs/methodology-modes-guide.md`](docs/methodology-modes-guide.md).

### Added

- **`skills/wiki-mode/SKILL.md`** — new skill (skill #14). Reads `.vault-meta/mode.json`; defaults to `generic` (v1.7 behavior) when absent. Triggers on "set vault mode", "switch to PARA", "use LYT", "zettelkasten setup", etc.
- **`scripts/wiki-mode.py`** — pure-stdlib router + config helper. Subcommands: `get` (current mode), `config` (full JSON), `route <type> <name>` (suggested path), `set <mode>` (write mode.json), `id` (mint Zettel timestamp ID), `templates` (list per-mode templates).
- **`bin/setup-mode.sh`** — interactive setup with `--mode <name>` non-interactive flag. Idempotent; safe to re-run to switch modes. Optionally seeds template folders for the chosen mode.
- **6 per-mode templates** under `skills/wiki-mode/templates/`:
  - `lyt/moc-template.md`, `lyt/atomic-template.md`
  - `para/project-template.md`, `para/area-template.md`, `para/resource-template.md`
  - `zettel/atomic-template.md`
- **`tests/test_wiki_mode.py`** — hermetic test suite (15 assertions covering load/save round-trip, all 4 modes' routing, slugify Unicode handling, Zettel ID format, corrupted-config fallback, CLI subprocess paths). `make test` is now **8 suites**.
- **`docs/methodology-modes-guide.md`** — narrative guide; when-to-use-which decision tree per mode; full schema documentation; migration guidance.

### Changed

- **`skills/wiki-ingest/SKILL.md`** — new "## Mode awareness (v1.8+)" section. Skill now consults `wiki-mode.py route` before filing new source/entity/concept pages. mode=generic preserves v1.7 behavior byte-for-byte.
- **`skills/save/SKILL.md`** — new "## Mode awareness (v1.8+)" section. Session notes now route per active mode (with explicit note that the global `~/Documents/Obsidian Vault/sessions/` rule still applies to cross-project saves).
- **`skills/autoresearch/SKILL.md`** — new "## Mode awareness (v1.8+)" section. Research synthesis output routes per active mode; every new entity/concept page in the research loop also routes via the router.
- **`Makefile`** — new `test-mode` and `setup-mode` targets; `test` aggregate now runs 8 suites; `clean-test-state` removes `.vault-meta/mode.json` + `.vault-meta/hook.log`.
- **`.gitignore`** — `.vault-meta/mode.json` + `.vault-meta/mode.*.tmp` added. Host-specific runtime config by default; `git add -f` to commit if the user wants the mode choice to follow the repo.
- **`CLAUDE.md`** — new "## Methodology Modes (v1.8+)" section + skill table row + plugin-name header reference.
- **`.claude-plugin/plugin.json` + `marketplace.json`** — version 1.7.2 → 1.8.0; descriptions refreshed to mention methodology modes and "5 of 5 priority gaps closed."

### Compass axis status after v1.8.0

| Axis (compass §1 + audit §9) | v1.7.2 | v1.8.0 |
|---|---|---|
| Compounding wiki primitive | #1 | #1 |
| Multi-writer safety | #1 | #1 |
| Retrieval architecture (free tier) | #1 | #1 |
| License / openness | #1 | #1 |
| **Methodology support** | TIE | **#1 ← v1.8.0** |
| Derivative outputs | NO | NO (v2.0 scope) |
| GUI / install ergonomics | NO | NO (v2.5+ scope) |

**5 of 7 axes #1.** Remaining 2 axes require multi-release effort.

### Honest scope acknowledgment

- "Best ever obsidian skill per the priority research" = the 5 priority gaps closed. **v1.8.0 reaches this milestone.**
- "Best ever in the market" requires **v2.0** (derive — audio/quiz/study) + **v2.5+** (Community Plugin GUI fork). These are weeks of work per release; v1.8.0 is the foundation, not the substitute.
- The v1.7.0 audit's 24 findings (1 BLOCKER + 6 HIGH + 10 MEDIUM + 7 LOW) are fully CLOSED-or-DEFERRED as of v1.7.2; v1.8.0 inherits a clean audit ledger.

### Migration notes

- v1.7.x vaults: no action needed. `wiki-mode` defaults to `generic` when `.vault-meta/mode.json` is absent, which is identical to v1.7 behavior. The mode-awareness sections in wiki-ingest/save/autoresearch are no-ops in generic mode.
- To adopt a non-default mode: `bash bin/setup-mode.sh` (or `bash bin/setup-mode.sh --mode <name>`). Interactive prompt picks one of LYT/PARA/Zettelkasten/Generic; optionally seeds template folders.
- Switching modes does NOT auto-migrate existing files. Manual migration via file manager or `git mv`.

## [1.7.2] - 2026-05-17 (SSS+ convergence — closes every audit finding)

Patch release closing **every remaining MEDIUM (M1-M10) and LOW (L1-L7) finding** from the v1.7.0 audit. Combined with v1.7.1 (which closed the 1 BLOCKER + 6 HIGH), this means the v1.7.0 audit's full 24-finding ledger is now CLOSED or formally DEFERRED-with-rationale. Plan: [`docs/audits/v1.7.2-sss-plus-plan.md`](docs/audits/v1.7.2-sss-plus-plan.md).

### Added

- **`agents/verifier.md` two new "always check" cuts** (`83d18ea`): #5 Git hygiene (any new written path NOT in .gitignore → HIGH; would have caught the v1.7.1 `.vault-meta/hook.log` self-pollution bug mechanically); #6 Additive-without-pruning (if `git diff --shortstat main..HEAD` shows >+500 LOC with <50 deletions, flag MEDIUM). Verifier learns from what its prior dispatches missed.
- **Unicode multilingual test** (`8c219fb`): `tests/test_bm25_index.py::test_tokenize_unicode_multilingual` covers Cyrillic, CJK, accented Latin, pure-emoji skip, mixed ASCII+non-ASCII.
- **`--explain` + `--no-rerank` test coverage** (`a80ae61`): `tests/test_retrieve.py` now exercises both flags hermetically (test_explain_flag_adds_diagnostics_block + test_no_rerank_flag_strategy_bm25_only). +6 assertions.
- **Newline + carriage-return path rejection tests** (`d0db354`): `tests/test_wiki_lock.sh` +2 assertions for the new validate_path rejections.
- **`[i/total]` progress prefix on contextual-prefix.py per-page logs** (`59cd7c8`): Stage 1 over 47 pages with tier-2 (claude-cli) takes 5+ min; user now sees position, not just opaque per-page lines.

### Changed (defensive-input + correctness fixes)

- **M2 — Unicode-aware BM25 tokenizer** (`8c219fb`): `[A-Za-z][A-Za-z0-9'\-]*` → `\w[\w'\-]*` with `re.UNICODE`. CJK / Cyrillic / Devanagari / accented Latin vaults previously had content silently dropped at index time. Same regex mirrored in `scripts/baseline-v16.py` so v1.6/v1.7 retrieval comparisons stay apples-to-apples for non-ASCII corpora.
- **M3 — `scripts/rerank.py` off-localhost error rewritten** (`d0db354`): from "pass --allow-remote-ollama" to a 3-option breakdown citing the actual host, concrete recovery steps for each option, and the canonical paths.
- **M4 — `scripts/wiki-lock.sh validate_path()` rejects newlines + carriage returns** (`d0db354`): would have silently corrupted the meta-lock line format.
- **M5 — `scripts/retrieve.py import_sibling()` wraps in try/except** (`d0db354`): ImportError / SyntaxError / AttributeError now produce friendly diagnostics with recovery hints (`bash bin/setup-retrieve.sh --check`) instead of bare Python tracebacks. Pre-checks target file existence.
- **M6 — `scripts/contextual-prefix.py` empty-body WARN** (`d0db354`): frontmatter-only pages now produce explicit `WARN: ... has no chunkable body content` instead of silent `chunks=0`.
- **M7 — `scripts/rerank.py save_cache()` non-blocking lock + 3-attempt retry** (`d0db354`): replaces blocking `fcntl.LOCK_EX` (no timeout) with `LOCK_NB` + 100ms retry; falls back to unlocked write with WARN if all 3 attempts fail. Prevents indefinite hang on NFS / FUSE mounts without lock support. The temp+rename pattern provides write atomicity even unlocked.
- **L2 — Stage 1 progress indicator** (`59cd7c8`): per-page log lines now carry `[i/total]` prefix.
- **L6 — `scripts/wiki-lock.sh` header doc** (`59cd7c8`): explicit distinction between `STALE_AFTER_SEC` (per-acquire, default 60s) and `clear-stale --max-age` (admin reaper, default 3600s). Both time-since-acquire but distinct scopes.
- **L7 — `scripts/bm25-index.py` BM25 divide guard** (`59cd7c8`): `avg_dl_safe = avg_dl or 1.0`. Currently unreachable but invariant-by-construction now rather than emergent-from-reachability.

### Removed (kernel: "delete more than you add")

- **L3 — dead `bm25_score()` function** (`eafd449`): 28 lines, never called, self-documented as "placeholder; real score computed in query()". Removed.
- **L4 — `--rebuild` flag stub** on `scripts/bm25-index.py build` (`eafd449`): declared but never read; was reserved for incremental mode not in v1.7. Per kernel: no abstraction without 3 real callers.
- **L5 — `--no-bm25` flag stub** on `scripts/retrieve.py` (`eafd449`): returned `EXIT_USAGE` with "reserved for v1.7.x vector-only mode." Same kernel principle.

### Honest accounting

- **Net LOC delta `main..HEAD` is `+6009 / -30`**, NOT meeting the plan §1 acceptance criterion (`≤+5000 OR ≥-200`). Per plan §4 failure clause: "Do not invent prunes to game the metric." Honest decomposition: ~5500 LOC across new files alone (4 new scripts + 4 new tests + 2 new skills + 1 new agent + 1 new bin + ~2200 LOC docs). The v1.7 line was net-new feature substrate, not a refactor; v1.6 had no equivalent of a retrieval pipeline, lock primitive, transport detector, or contextual prefix generator to delete. **Kernel-application axis ceilings at ~92-95** for the v1.7 line; the deduction is structural to building substrate, not negligence. Documented in `docs/audits/v1.7.0-audit-2026-05-17.md` §10.2.
- **M9 (bounded-slices: 4 skills touched by both §3.2 and §3.4)** is documented as a process note in audit §10.3; not a code-level fix.
- **M11 (synonym category benchmark tied 60%/60%)** persists post-tokenizer-change; v1.7 hybrid neither helps nor hurts on this category. Filed for v1.7.x rerank threshold tuning.
- **M12 (negative-query precision)** was tied at 40%/40% in v1.7.0; post-Unicode-tokenizer it's 40%/20% (+20pp). Empirically closed by the tokenizer change.
- **L1 (§3.1 substrate +17/-5 no deletion)** documented as defensible process note in audit §10.3.

### Benchmark refresh (full 50-query corpus, v1.7.2 measurement)

Run via `python3 scripts/benchmark-runner.py`:

| Category | N | v17 top-1 | v17 top-5 | v16 top-1 | v16 top-5 | Δ top-1 |
|---|---|---|---|---|---|---|
| cross-page | 10 | 20.0% | 80.0% | 30.0% | 40.0% | -10.0pp |
| derived | 25 | 68.0% | 88.0% | 12.0% | 24.0% | +56.0pp |
| negative | 5 | 40.0% | 80.0% | 20.0% | 80.0% | +20.0pp |
| partial-recall | 5 | 60.0% | 100.0% | 20.0% | 60.0% | +40.0pp |
| synonym | 5 | 60.0% | 100.0% | 60.0% | 100.0% | +0.0pp |
| **TOTAL** | **50** | **54.0%** | **88.0%** | **22.0%** | **44.0%** | **+32.0pp** |

**Error reduction: +41.0%** vs the ≥30% ship gate — PASS.

The +32pp/41% slightly beats the v1.7.0 audit's reported +30pp/+39.5%; the Unicode tokenizer change made baseline-v16.py marginally weaker (−2pp on its top-1) which counts as a delta improvement for v1.7. cross-page top-1 regression vs audit (was +0pp, now −10pp) is within benchmark noise on N=10; cross-page top-5 stayed at 80% which is the synthesis-relevant metric.

### Migration notes

- v1.7.1 vaults: no action needed. All v1.7.2 changes are backward-compatible behavior improvements.
- Re-running `bash bin/setup-retrieve.sh` is recommended (rebuilds BM25 index with the new Unicode tokenizer; multilingual content that was silently dropped will now be indexed).
- The v1.7.0 audit's full 24-finding ledger is now CLOSED-or-formally-DEFERRED. v1.7.2 marks the end of the v1.7 line's audit-debt remediation cycle.

## [1.7.1] - 2026-05-17 (audit-driven patch)

Patch release closing the 1 BLOCKER + 6 HIGH findings from the v1.7.0 audit ([`docs/audits/v1.7.0-audit-2026-05-17.md`](docs/audits/v1.7.0-audit-2026-05-17.md)). All v1.7.0 features remain available; the changes are guard-rails and one new agent.

### Fixed

- **BLOCKER B1 — Data egress without consent** (`ca68bb6`). `scripts/contextual-prefix.py` now requires an explicit `--allow-egress` flag (default off) before selecting tier-1 (Anthropic API) or tier-2 (claude CLI subprocess). Without the flag, `pick_prefix_tier()` returns `"synthetic"` regardless of `ANTHROPIC_API_KEY` or `claude` binary presence. `bin/setup-retrieve.sh` adds a y/N consent prompt before any non-synthetic Stage 1 run. `skills/wiki-retrieve/SKILL.md` adds a Data Privacy callout (also closes H6). Mirror of the existing `scripts/tiling-check.py:351` `--allow-remote-ollama` precedent.
- **H1 — Stage 1 failure had no rollback path** (`4837d4f`). `bin/setup-retrieve.sh` now captures Stage 1's exit code, exits 5 on non-zero, and prints a 3-option recovery hint (incremental resume, full wipe, single-page re-process). Stage 2 only runs after Stage 1 success.
- **H2 — `make clean-test-state` didn't remove v1.7 artifacts** (`7e1f187`). Extended the target to remove `.vault-meta/chunks/`, `.vault-meta/bm25/`, `.vault-meta/locks/`, `.vault-meta/transport.json`, `.vault-meta/.wiki-lock.meta`, and the related `.tmp` files. The Makefile target now matches the v1.7 `.gitignore` set.
- **H3 — PostToolUse hook swallowed lock-check errors** (`7120970`). Restructured `hooks/hooks.json` to capture the wiki-lock script's exit code directly (not via a pipeline), defer the auto-commit on any non-zero rc, and only run `git add` after both the rc check and the non-empty-list check pass.
- **H4 — No verifier-agent pass at workstream gates** (`3ea443f`). Added `agents/verifier.md` — a read-only (`Read`/`Grep`/`Glob`/`Bash` only; no `Write`/`Edit`) pre-commit specialist that reads the staged diff, applies the /best-practices six-cut + agent kernel, and returns findings in four tiers (BLOCKER/HIGH/MEDIUM/LOW). CLAUDE.md "Pre-commit verifier (v1.7.1+)" section references it as the recommended pre-commit step.
- **H5 — `detect-transport.sh` JSON escaping was shell-only** (`722ac97`). Added a `json_escape()` helper that pipes through `python3 -c json.dumps`, applied to `CLI_VERSION` (both `obsidian-cli` and `obsidian`-binary paths). The heredoc now emits `${CLI_VERSION}` without surrounding quotes since the helper produces a pre-quoted JSON string. Defense in depth against pathological upstream version output (backslashes, tabs, newlines, control chars).
- **H6 — `skills/wiki-retrieve/SKILL.md` had no Data Privacy section** (bundled with B1 in `ca68bb6`). New section at the top of the skill body documents the two-layer egress guard (`--allow-egress` flag + setup-retrieve prompt) and points back to the `tiling-check.py` precedent.

### Added

- `agents/verifier.md` — pre-commit specialist; see H4 above.
- `scripts/baseline-v16.py` + `scripts/benchmark-runner.py` — audit instrumentation that ran the 50-query retrieval benchmark documented in `wiki/meta/retrieval-benchmark-v1.7.md`. Result: v1.7 top-1 54.0% vs v1.6 baseline 24.0% (+30pp); error reduction +39.5% vs the ≥30% gate. Future audits can re-run with `python3 scripts/benchmark-runner.py`.
- `docs/audits/v1.7.0-audit-2026-05-17.md` (481 lines) — the full audit report.
- `docs/audits/v1.7.1-fixes-plan.md` — the sequenced 6-commit roadmap this release executes.

### Changed

- Versions bumped to 1.7.1 in `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json` (description fields refreshed to mention the verifier agent and the egress-consent guard).
- `CLAUDE.md` "Pre-commit verifier (v1.7.1+)" section added; "Concurrency (v1.7+)" section retained verbatim.

### Polish (post-fix self-audit refinements)

After the 7 v1.7.1 commits landed, a re-pass with `agents/verifier.md` against the slice surfaced 2 MEDIUM + 3 LOW polish items. All closed in one follow-up commit:

- `scripts/detect-transport.sh` — split `CLI_VERSION_RAW` (human log line) from `CLI_VERSION` (pre-quoted JSON for the heredoc). The JSON-escape fix from H5 made the log line `CLI: obsidian-cli ("1.12.0")` carry visible quotes; the split keeps both paths clean. **Also**: `CLI_VERSION_RAW=""` initialized in the top-of-script init block alongside `CLI_PRESENT`, `CLI_BINARY`, `CLI_VERSION` (defense in depth under `set -euo pipefail`; closes a latent unbound-variable risk that worked today only by bash short-circuit semantics).
- `agents/verifier.md` — `tools:` field kept as CSV (`Read, Grep, Glob, Bash`) to match in-repo precedent (`wiki-ingest.md`, `wiki-lint.md`). Both CSV and YAML list are accepted forms across `~/.claude/agents/` (audit-* use CSV, blog-* and challenge-auditor-* use YAML list), but the three local repo agents are CSV, so consistency-with-siblings is the deciding factor. The polish commit initially converted to YAML list per a prior verifier recommendation that didn't cross-check sibling files; reverted in the follow-up after deeper chair probe.
- `bin/setup-retrieve.sh` — refreshed the header docstring (lines 13-26) to mention `--allow-egress` and the consent gate. Inline comments at line 121 were already correct; the file-top doc was the stale one.
- `scripts/contextual-prefix.py` — docstring on `generate_prefix()` explaining the deliberate asymmetric fallback (api→cli→synthetic, but cli→synthetic only — climbing from cli to api would silently spend money the user did not authorize).
- `hooks/hooks.json` — breadcrumb log to `.vault-meta/hook.log` on the rare non-zero `LOCK_RC` defer path. Verified with concurrency (10 parallel hook fires → 10 atomic lines, no interleaving; line length < `PIPE_BUF`), no format-string injection (printf uses literal format with %s placeholders), filesystem-failure edge cases preserve `exit 0` (defer behavior intact). **`.vault-meta/hook.log`** added to `.gitignore` so the breadcrumb file is never auto-staged by the same PostToolUse hook (closes a self-pollution loop that would have surfaced on the first non-zero `LOCK_RC` event).

### Migration notes

- v1.6 vaults: no action needed. The new components are opt-in or read-only.
- v1.7.0 adopters who provisioned `bin/setup-retrieve.sh` and had `ANTHROPIC_API_KEY` set: the next `bin/setup-retrieve.sh` run will prompt for consent before proceeding with the non-synthetic tier. Decline to keep all data on-machine (tier-3 synthetic), accept to preserve the prior behavior. Existing chunks/ data is unaffected either way.
- Test suite: `make test` continues to run 7 hermetic suites (~1162 assertions). Zero ollama, zero network dependency.

## [1.7.0] - 2026-05-17 (Compound Vault refoundation)

The v1.7 line, codenamed **Compound Vault**, refoundations the plugin around four pillars from the May 2026 gap analysis: substrate alignment with `kepano/obsidian-skills`, Obsidian-CLI-native transport, contextual + hybrid retrieval, and safe multi-writer ingest. v1.6 vaults that never install the new opt-in components see no behavior change. Full design rationale in `docs/compound-vault-guide.md`.

### Added

- **§3.2 Default transport** — `skills/wiki-cli/SKILL.md` (recipe reference for Obsidian CLI), `scripts/detect-transport.sh` (writes `.vault-meta/transport.json` snapshot; auto-stale at 7d), `wiki/references/transport-fallback.md` (canonical decision tree). 5 transport-aware skills (wiki-ingest, wiki-query, save, autoresearch, wiki-lint) gained "## Transport (v1.7+)" sections.
- **§3.3 Hybrid retrieval pipeline (wiki-retrieve, opt-in)** — implements Anthropic's Sept 2024 Contextual Retrieval pattern as agent-skill plumbing. 4 new scripts: `scripts/contextual-prefix.py` (3-tier auto: Anthropic API → claude CLI subprocess → synthetic), `scripts/bm25-index.py` (Okapi BM25, k1=1.5 b=0.75, pure stdlib, flock-guarded), `scripts/rerank.py` (cosine on nomic-embed-text via ollama, embed-cache), `scripts/retrieve.py` (orchestrator: BM25 top-20 → rerank top-5 → page dedupe). `bin/setup-retrieve.sh` opt-in bootstrap. `skills/wiki-retrieve/SKILL.md` documents the architecture and cost ceiling (~$12/1000 docs per Anthropic). Wired into `skills/wiki-query/SKILL.md` via "## Retrieval (v1.7+)" section with graceful exit-10 fallback to the v1.6 hot→index→drill read order.
- **§3.4 Multi-writer safety (wiki-lock, core)** — `scripts/wiki-lock.sh` per-file advisory locking. Age-based staleness (default `STALE_AFTER_SEC=60`), cross-process release allowed by design. 4 skills (wiki-ingest, wiki-fold, save, autoresearch) gained "## Concurrency (v1.7+)" sections with concrete acquire/release recipes. The latent corruption bug from v1.6 — documented but unenforced in `skills/wiki-ingest/SKILL.md:259-264` — is now closed.
- **New skills (2)**: `wiki-cli` (§3.2) and `wiki-retrieve` (§3.3). Total skill count is now 13.
- **New scripts (6)**: `detect-transport.sh`, `contextual-prefix.py`, `bm25-index.py`, `rerank.py`, `retrieve.py`, `wiki-lock.sh`.
- **New tests (4)**: `tests/test_bm25_index.py` (~30 hermetic assertions including BM25 monotonicity and IDF positivity), `tests/test_retrieve.py` (22 hermetic assertions including end-to-end subprocess test), `tests/test_wiki_lock.sh` (14 hermetic assertions including age-based stale reap), `tests/test_concurrent_write.sh` (6 hermetic assertions; the critical multi-writer correctness gate — 10 parallel workers, no losses, no garbled lines). `make test` now runs 7 suites with zero network and zero ollama dependency.
- **New docs**: `docs/compound-vault-guide.md` (omnibus v1.7 guide), `wiki/references/transport-fallback.md` (transport decision tree).
- **Makefile targets**: `test-bm25`, `test-retrieve`, `test-lock`, `test-concurrent`, `setup-retrieve`.

### Changed

- **§3.1 Substrate hard-prefer on `kepano/obsidian-skills`** — `skills/obsidian-markdown/SKILL.md`, `skills/obsidian-bases/SKILL.md`, and `skills/canvas/SKILL.md` upgraded from soft-defer ("if kepano installed, prefer it") to hard-prefer ("this is a fallback; prefer kepano"). Architectural behavior unchanged; signal sharpened. `skills/defuddle/SKILL.md` documented as canonical (kepano does not ship a defuddle skill). `.claude-plugin/marketplace.json` declares `recommendedCompanions: [kepano/obsidian-skills]` with install hint.
- **`hooks/hooks.json` PostToolUse** — added a lock-in-flight check before `git add`. When `bash scripts/wiki-lock.sh list` returns non-zero count, the auto-commit defers. Prevents torn commits during multi-agent ingest. Falls through gracefully if `wiki-lock.sh` is absent.
- **`agents/wiki-ingest.md`** — rewrote the "Sub-agents MUST NOT" section. The prohibition on calling `allocate-address.sh` from sub-agents is preserved (counter monotonicity). A NEW rule is added: sub-agents MAY now write pages, but MUST acquire locks first.
- **Versions** synced to 1.7.0 across `plugin.json` and `marketplace.json`.

### Migration notes

- v1.6 vaults need no action. The new components are opt-in (`bash bin/setup-retrieve.sh` for hybrid retrieval) or universally beneficial (wiki-lock is core; no setup needed). The plugin remains MIT-licensed; no paid tier introduced.
- To install the recommended companion: `claude plugin marketplace add kepano/obsidian-skills`. Existing local fallbacks remain functional without it.
- Estimated upgrade time: 5 minutes (substrate auto-detected; transport auto-detected on first session; retrieval requires explicit `bash bin/setup-retrieve.sh`).

### Out of scope for v1.7 (deferred to v1.8+)

- Methodology modes (LYT / PARA / Zettelkasten / Generic via `wiki-mode` skill) — planned for v1.8.
- NotebookLM-class derivative outputs (audio, quiz, flashcards, study guide via `wiki-derive`) — planned for v1.9 or v2.0.
- Multimodal ingest adapters (YouTube, PDF, EPUB, image OCR via `wiki-ingest-multimodal`) — planned for v1.9.
- Periodic review artifacts (`wiki-review`) — planned for v1.8.

## [1.6.0] - 2026-04-24

### Added (DragonScale Mechanism 4, opt-in)

- **Boundary-first autoresearch**: `scripts/boundary-score.py` computes `(out_degree - in_degree) * recency_weight` across the wikilink graph and emits top-K frontier pages. `/autoresearch` invoked without a topic now offers the top-5 frontier pages as research candidates when the vault has adopted DragonScale.
- `tests/test_boundary_score.py` — 35 unit tests covering frontmatter parsing, recency weight, wikilink extraction (with code-block guard), graph construction, scoring, CLI interface.
- `make test-boundary` target + integration into `make test`.

### Changed

- `skills/autoresearch/SKILL.md` — new Topic Selection section with three paths: explicit (A), boundary-first (B, opt-in), user-ask (C, default without DragonScale).
- `commands/autoresearch.md` — no-topic usage documented for both modes.
- `wiki/concepts/DragonScale Memory.md` — Mechanism 4 flipped from NOT IMPLEMENTED to shipped; exact scoring formula and "what is NOT included" callout added. Version bumped to v0.4.
- Version synced to 1.6.0 across plugin.json and marketplace.json.

## [1.5.1] - 2026-04-24 (Phase 3.6 hardening)

### Fixed

- `scripts/tiling-check.py`: `--report PATH` now resolved against VAULT_ROOT and rejected if it escapes (security: prevents hostile or accidental writes outside the vault).
- `.vault-meta/legacy-pages.txt`: rollout baseline corrected from 2026-04-24 to 2026-04-23 (matches earliest addressed page in the seed vault).
- `AGENTS.md`: wiki-fold listed in the skills table; stale claim that "all skills use only name/description" narrowed to newer skills (older skills still carry allowed-tools for Claude Code compatibility).
- `skills/wiki-ingest/SKILL.md`: resolves the internal contradiction between "immutable .raw/" and "maintain .raw/.manifest.json" — user-dropped source documents remain immutable; only the manifest is wiki-ingest-maintained.
- `docs/install-guide.md`: version 1.2.0 -> 1.5.0 with a DragonScale optional-install callout.

## [1.5.0] - 2026-04-24

### Added (DragonScale Memory extension, opt-in)

- **Mechanism 1 — Fold operator** (`skills/wiki-fold/`): extractive, structurally-idempotent rollups of `wiki/log.md` entries into per-batch meta-pages under `wiki/folds/`. Dry-run via stdout by default (does not trigger PostToolUse auto-commit hook); commit mode explicit.
- **Mechanism 2 — Deterministic page addresses** (opt-in): `scripts/allocate-address.sh` flock-guarded atomic allocator; new `address: c-NNNNNN` frontmatter convention; re-ingest idempotency via `.raw/.manifest.json address_map`. `wiki-ingest` and `wiki-lint` skills feature-detect DragonScale setup.
- **Mechanism 3 — Semantic tiling lint** (opt-in): `scripts/tiling-check.py` uses local `nomic-embed-text` via ollama to flag candidate duplicate pages by cosine similarity. Banded thresholds (error/review/pass) documented as conservative seeds with manual calibration procedure.
- `wiki/concepts/DragonScale Memory.md` — full design spec (v0.3) with four mechanisms, scope boundary, and primary-source citations.
- `bin/setup-dragonscale.sh` — idempotent installer that provisions `.vault-meta/` counter, thresholds, and legacy-pages manifest.
- `tests/` — shell + python test suite for the allocator and tiling-check. Run via `make test`.
- `Makefile` — developer targets (`test`, `setup-dragonscale`, `clean-test-state`).

### Changed

- `hooks/hooks.json` PostToolUse now stages `.vault-meta/` in addition to `wiki/` and `.raw/` so DragonScale runtime state is captured by the auto-commit hook.
- `skills/wiki-ingest/SKILL.md` and `skills/wiki-lint/SKILL.md` gained opt-in DragonScale sections behind feature-detection guards; original behavior unchanged for vaults that have not run `setup-dragonscale.sh`.
- `agents/wiki-ingest.md` explicitly forbids parallel sub-agents from calling the allocator (single-writer rule for address assignment).
- `agents/wiki-lint.md` extended to describe Address Validation and Semantic Tiling checks.
- Stale `allowed-tools` frontmatter removed from `wiki-ingest` and `wiki-lint` SKILL.md (kepano convention: only `name` and `description`).
- Version strings synced across `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, and documentation.

### Security

- `scripts/tiling-check.py` locks `OLLAMA_URL` to localhost by default. Remote endpoints require `--allow-remote-ollama`. Symlinks and vault-root escapes are rejected before any read.

### Not in this release

- **Mechanism 4 — Boundary-first autoresearch**: documented in the spec as a future proposal; no code shipped. `skills/autoresearch/SKILL.md` unchanged.

## [1.4.3] - prior

Previous state. See git log for details.
