From fc15eb9286ff75d82275901b5b8145dbc72c966d Mon Sep 17 00:00:00 2001 From: Oliver Wrede Date: Thu, 2 Jul 2026 12:59:51 +0200 Subject: [PATCH 1/3] fix(indexer): delete sections before chunks in full-mode wipe (#16) `index --full` failed with `FOREIGN KEY constraint failed` on any vault with a populated `sections` table. The full-mode wipe loop deleted chunks while `sections.chunk_id_first/last` (REFERENCES chunks(id), no ON DELETE) still pointed at them. The per-note re-index path already ordered these correctly; only the full-wipe path missed it. Add `sections.deleteByNote(n.id)` before `chunks.deleteByNote(n.id)`, mirroring the per-note path and single.ts step 7. Add a regression test that asserts the wrong order trips the FK and the corrected order does not. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/indexer/indexer.ts | 8 ++++++ src/indexer/sections-hook.test.ts | 41 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/indexer/indexer.ts b/src/indexer/indexer.ts index 2b61b70..eb5805a 100644 --- a/src/indexer/indexer.ts +++ b/src/indexer/indexer.ts @@ -165,6 +165,14 @@ export async function indexVault(vault: Vault, options: IndexerOptions): Promise vault.db.transaction(() => { const allNotes = vault.db.notes.listAll(); for (const n of allNotes) { + // Sections FIRST — sections reference chunks via + // chunk_id_first/last with no ON DELETE cascade, so deleting + // chunks while sections still point at them trips a FOREIGN KEY + // constraint. Mirrors the per-note re-index path below and + // single.ts step 7. (Issue #16: this full-wipe loop omitted the + // sections delete, so `index --full` failed on any vault with a + // populated sections table.) + vault.db.sections.deleteByNote(n.id); vault.db.chunks.deleteByNote(n.id); vault.db.wikilinks.deleteByNote(n.id); // Phase 4 / 04-01 (D-01): dual-write mirror. diff --git a/src/indexer/sections-hook.test.ts b/src/indexer/sections-hook.test.ts index 25cae19..a780311 100644 --- a/src/indexer/sections-hook.test.ts +++ b/src/indexer/sections-hook.test.ts @@ -139,6 +139,47 @@ describe("indexer section hook + status maintenance (03-01 Task 7)", () => { expect(second).toEqual(first); }); + it("full-mode wipe order deletes sections before chunks (Issue #16 FK regression)", () => { + // Reproduce the exact failure: a note with a chunk AND a section whose + // chunk_id_first/last reference that chunk. sections→chunks is a FK with + // no ON DELETE, so deleting the chunk while the section still points at it + // trips FOREIGN KEY constraint failed. The full-mode wipe loop in + // indexer.ts must delete sections first (mirroring the per-note path). + const content = "# Top\n\nbody bytes.\n"; + const nid = seedNote("wipe.md", content); + const [cid] = db.chunks.insertBatch(nid, [ + { + idx: 0, + text: "body bytes.", + headingPath: "# Top", + startOffset: content.indexOf("body bytes."), + endOffset: content.indexOf("body bytes.") + "body bytes.".length, + tokenCount: 2, + }, + ]); + buildSectionsForNote(vault, nid, content, [cid!]); + // Precondition: a section actually references the chunk. + const before = db.sections.getByNote(nid); + expect(before.some((r) => r.chunk_id_first === cid)).toBe(true); + + // WRONG order (chunks before sections) must trip the FK — this is what + // the bug did. Guards against a future refactor "simplifying" the order. + expect(() => + db.transaction(() => { + db.chunks.deleteByNote(nid); + }), + ).toThrow(/FOREIGN KEY/i); + + // CORRECT order (sections first, as the fix does) must succeed. + expect(() => + db.transaction(() => { + db.sections.deleteByNote(nid); + db.chunks.deleteByNote(nid); + }), + ).not.toThrow(); + expect(db.sections.getByNote(nid)).toHaveLength(0); + }); + it("mapChunksToSections bins chunks into the innermost containing section", () => { // 3 ranges: outer covers [0, 100), middle covers [10, 50), inner covers [20, 40). const ranges = [ From 659c0ec6c37c3a5b38160fef0895e934f3efd9db Mon Sep 17 00:00:00 2001 From: Oliver Wrede Date: Thu, 2 Jul 2026 13:05:13 +0200 Subject: [PATCH 2/3] fix(contextfit): cross-process ingest lock with dirty-flag retry (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Concurrent ContextFit KB ingests were not mutually excluded across processes. A CLI `index` running while a serve process fired its debounced re-ingest (or a second stale serve) had both `contextfit ingest` calls `rm` and rewrite the same KB dir; the loser crashed mid-write (`FileNotFoundError: .../chunks/index.json.tmp`), sometimes leaving a half-written KB. The existing `.lock` (brief daemon, held for its whole lifetime) can't serialize this — reusing it would make every ingest skip forever. Add a dedicated per-vault mutex `locks/.ingest.lock` (atomic `wx` create, PID-based steal-on-dead, mirroring brief/lock.ts) wrapped inside `indexVaultWithContextFit`, the single chokepoint for all four call sites (CLI index, serve write-refresh, watcher re-ingest, startup catch-up). Contention policy is SKIP + a persisted `.ingest.dirty` flag: a second-comer marks the vault dirty and returns immediately (no wait, no wasted double-rebuild) with new status "skipped" (treated as success by callers). The lock holder clears the flag before each pass and does one trailing re-ingest if a write set it mid-run, so the latest change is never lost even across processes; MAX_PASSES bounds churn. Startup catch-up honors a leftover dirty flag so a crash-stranded flag is cleaned up at the next boot. Adds ingest-lock.ts + 14 tests (lock primitive: acquire/steal/release, dirty round-trip; orchestration: skip-and-flag, single ingest, trailing re-ingest, lock-released-on-throw, MAX_PASSES backstop). Callers updated to log "skipped" benignly rather than as an error. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../change-feed/obsidian-fs/watcher.ts | 3 + src/adapters/retrieval/contextfit/index.ts | 78 ++++++- .../retrieval/contextfit/ingest-lock.test.ts | 207 ++++++++++++++++++ .../retrieval/contextfit/ingest-lock.ts | 165 ++++++++++++++ src/cli.ts | 6 + src/indexer/catchup.ts | 28 ++- 6 files changed, 469 insertions(+), 18 deletions(-) create mode 100644 src/adapters/retrieval/contextfit/ingest-lock.test.ts create mode 100644 src/adapters/retrieval/contextfit/ingest-lock.ts diff --git a/src/adapters/change-feed/obsidian-fs/watcher.ts b/src/adapters/change-feed/obsidian-fs/watcher.ts index 2cf64ce..0ff06a0 100644 --- a/src/adapters/change-feed/obsidian-fs/watcher.ts +++ b/src/adapters/change-feed/obsidian-fs/watcher.ts @@ -146,6 +146,9 @@ export class VaultWatcher { const r = await indexVaultWithContextFit(this.opts.vault.config, {}); if (r.status === "completed") { this.opts.log(`ContextFit KB refreshed (${r.durationMs}ms)`); + } else if (r.status === "skipped") { + // Issue #17: another process is mid-ingest; it will do a trailing pass. + this.opts.log(`ContextFit KB refresh skipped (another ingest in progress; flagged)`); } else { this.opts.log(`ContextFit KB refresh failed: ${r.error}`); } diff --git a/src/adapters/retrieval/contextfit/index.ts b/src/adapters/retrieval/contextfit/index.ts index e7207c7..257357d 100644 --- a/src/adapters/retrieval/contextfit/index.ts +++ b/src/adapters/retrieval/contextfit/index.ts @@ -25,6 +25,13 @@ import { type ContextFitCliConfig, type ContextFitChunk, } from "./cli.js"; +import { + tryAcquireIngestLock, + releaseIngestLock, + markIngestDirty, + isIngestDirty, + clearIngestDirty, +} from "./ingest-lock.js"; const DEFAULT_COMMAND = "contextfit"; @@ -44,7 +51,13 @@ export function cliConfigForVault(vault: VaultConfig): ContextFitCliConfig { } export interface ContextFitIndexResult { - status: "completed" | "failed"; + /** + * "skipped" (Issue #17): another ingest for this vault was already in flight, + * so this call marked the vault dirty and returned WITHOUT ingesting. The + * in-flight holder does a trailing re-ingest, so the change is not lost. + * Callers treat "skipped" as success (no error), not failure. + */ + status: "completed" | "failed" | "skipped"; /** Human-readable stats line from ContextFit's ingest output. */ stats: string; durationMs: number; @@ -59,14 +72,35 @@ export interface ContextFitIndexResult { */ export async function indexVaultWithContextFit( vault: VaultConfig, - opts: { onProgress?: (msg: string) => void } = {}, + opts: { + onProgress?: (msg: string) => void; + /** Test-only: `~/.vault-memory` root override for the ingest lock. */ + lockRootOverride?: string; + /** + * Test-only dependency injection. Production omits these and the real + * probe/ingest (which spawn the `contextfit` CLI) are used. Tests pass + * fakes to exercise the lock/dirty/trailing-pass orchestration without the + * binary. + */ + _deps?: { + probe?: (cfg: ContextFitCliConfig) => Promise; + ingest?: (cfg: ContextFitCliConfig, source: string) => Promise; + clearKb?: (kbPath: string) => Promise; + }; + } = {}, ): Promise { const log = opts.onProgress ?? (() => {}); const cfg = cliConfigForVault(vault); const start = Date.now(); + const lockOpts = + opts.lockRootOverride !== undefined ? { rootOverride: opts.lockRootOverride } : {}; + const probe = + opts._deps?.probe ?? ((c: ContextFitCliConfig) => contextFitProbe({ command: c.command })); + const ingest = opts._deps?.ingest ?? contextFitIngest; + const clearKb = opts._deps?.clearKb ?? ((p: string) => rm(p, { recursive: true, force: true })); log(`ContextFit: ingesting ${vault.path} → ${cfg.kbPath}`); - const available = await contextFitProbe({ command: cfg.command }); + const available = await probe(cfg); if (!available) { return { status: "failed", @@ -78,18 +112,44 @@ export async function indexVaultWithContextFit( }; } + // Issue #17: serialize ingests cross-process. Second-comer marks the vault + // dirty and skips (no wait, no wasted double-rebuild); the in-flight holder + // does a trailing re-ingest so the latest change is captured. + const lock = await tryAcquireIngestLock(vault.name, lockOpts); + if (!lock.acquired) { + await markIngestDirty(vault.name, lockOpts); + log(`ContextFit: re-ingest already in progress (pid ${lock.ownerPid}); flagged for retry`); + return { status: "skipped", stats: "", durationMs: Date.now() - start }; + } + try { - // ContextFit refuses to ingest into an existing KB (it finds the manifest - // and exits non-zero, demanding --resume or a clean dir). Our index - // semantics are always a FULL rebuild, so clear the KB dir first — this - // makes re-index / live-reindex / write-refresh / catchup idempotent. - await rm(cfg.kbPath, { recursive: true, force: true }); - const stats = await contextFitIngest(cfg, vault.path); + // Loop so a change that lands DURING our ingest triggers exactly one more + // pass. Bounded to avoid an unbounded churn loop under constant writes; the + // watcher's debounce already coalesces bursts, so 1 trailing pass suffices + // in practice and MAX_PASSES is a safety backstop. + const MAX_PASSES = 8; + let stats = ""; + let passes = 0; + do { + // Clear the flag BEFORE ingesting: any write that arrives after this + // point re-sets it and earns another pass; writes before it are already + // captured by the rebuild we are about to do. + await clearIngestDirty(vault.name, lockOpts); + // ContextFit refuses to ingest into an existing KB (it finds the manifest + // and exits non-zero, demanding --resume or a clean dir). Our index + // semantics are always a FULL rebuild, so clear the KB dir first — this + // makes re-index / live-reindex / write-refresh / catchup idempotent. + await clearKb(cfg.kbPath); + stats = await ingest(cfg, vault.path); + passes += 1; + } while (passes < MAX_PASSES && (await isIngestDirty(vault.name, lockOpts))); log(stats.trim().split("\n").slice(-3).join(" · ")); return { status: "completed", stats, durationMs: Date.now() - start }; } catch (err) { const message = err instanceof Error ? err.message : String(err); return { status: "failed", stats: "", durationMs: Date.now() - start, error: message }; + } finally { + await releaseIngestLock(vault.name, lockOpts); } } diff --git a/src/adapters/retrieval/contextfit/ingest-lock.test.ts b/src/adapters/retrieval/contextfit/ingest-lock.test.ts new file mode 100644 index 0000000..f0b1a9b --- /dev/null +++ b/src/adapters/retrieval/contextfit/ingest-lock.test.ts @@ -0,0 +1,207 @@ +/** + * Ingest-lock tests (Issue #17). Exercise the cross-process mutex + dirty-flag + * primitive directly against a temp `~/.vault-memory` root (rootOverride), so + * they run without the contextfit binary. Concurrent-ingest serialization at + * the `indexVaultWithContextFit` level is covered by the live index test + * (gated on contextfit being installed) in ./index.test.ts. + */ + +import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import { promises as fs } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { + tryAcquireIngestLock, + releaseIngestLock, + markIngestDirty, + isIngestDirty, + clearIngestDirty, +} from "./ingest-lock.js"; +import { indexVaultWithContextFit } from "./index.js"; +import type { VaultConfig } from "../../../types.js"; + +describe("ContextFit ingest lock (Issue #17)", () => { + let root = ""; + const V = "vaultA"; + + beforeEach(async () => { + root = await fs.mkdtemp(join(tmpdir(), "vm-ingest-lock-")); + }); + afterEach(async () => { + await fs.rm(root, { recursive: true, force: true }); + }); + + it("acquires an uncontended lock and writes our pid", async () => { + const r = await tryAcquireIngestLock(V, { rootOverride: root }); + expect(r.acquired).toBe(true); + if (r.acquired) { + const pid = parseInt(await fs.readFile(r.path, "utf8"), 10); + expect(pid).toBe(process.pid); + } + }); + + it("second acquire is contended while the first is held", async () => { + const first = await tryAcquireIngestLock(V, { rootOverride: root }); + expect(first.acquired).toBe(true); + const second = await tryAcquireIngestLock(V, { rootOverride: root }); + expect(second.acquired).toBe(false); + if (!second.acquired) expect(second.ownerPid).toBe(process.pid); + }); + + it("re-acquires after release", async () => { + await tryAcquireIngestLock(V, { rootOverride: root }); + await releaseIngestLock(V, { rootOverride: root }); + const again = await tryAcquireIngestLock(V, { rootOverride: root }); + expect(again.acquired).toBe(true); + }); + + it("steals a lock whose recorded pid is dead", async () => { + // Hand-write a lock file owned by a pid that cannot be alive. POSIX pids + // are positive; a huge value is guaranteed dead (ESRCH), so it is stolen. + const dir = join(root, "locks"); + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(join(dir, `${V}.ingest.lock`), "2147483646\n"); + const r = await tryAcquireIngestLock(V, { rootOverride: root }); + expect(r.acquired).toBe(true); + }); + + it("steals a lock with malformed (non-numeric) contents", async () => { + const dir = join(root, "locks"); + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(join(dir, `${V}.ingest.lock`), "not-a-pid\n"); + const r = await tryAcquireIngestLock(V, { rootOverride: root }); + expect(r.acquired).toBe(true); + }); + + it("release is safe when we do not hold the lock", async () => { + await expect(releaseIngestLock(V, { rootOverride: root })).resolves.toBeUndefined(); + }); + + it("dirty flag round-trips: mark → is → clear", async () => { + expect(await isIngestDirty(V, { rootOverride: root })).toBe(false); + await markIngestDirty(V, { rootOverride: root }); + expect(await isIngestDirty(V, { rootOverride: root })).toBe(true); + await clearIngestDirty(V, { rootOverride: root }); + expect(await isIngestDirty(V, { rootOverride: root })).toBe(false); + }); + + it("clear is idempotent when no flag exists", async () => { + await expect(clearIngestDirty(V, { rootOverride: root })).resolves.toBeUndefined(); + expect(await isIngestDirty(V, { rootOverride: root })).toBe(false); + }); + + it("lock and dirty flag are independent per vault", async () => { + await tryAcquireIngestLock("vaultA", { rootOverride: root }); + await markIngestDirty("vaultA", { rootOverride: root }); + // A different vault is unaffected. + const b = await tryAcquireIngestLock("vaultB", { rootOverride: root }); + expect(b.acquired).toBe(true); + expect(await isIngestDirty("vaultB", { rootOverride: root })).toBe(false); + }); +}); + +describe("indexVaultWithContextFit lock orchestration (Issue #17)", () => { + let root = ""; + const vault: VaultConfig = { name: "orch", path: "/tmp/orch-vault", backend: "contextfit" }; + + beforeEach(async () => { + root = await fs.mkdtemp(join(tmpdir(), "vm-ingest-orch-")); + }); + afterEach(async () => { + await fs.rm(root, { recursive: true, force: true }); + }); + + const okDeps = (ingest: (c: unknown, s: string) => Promise) => ({ + probe: async () => true, + ingest: ingest as never, + clearKb: async () => {}, + }); + + it("skips (and flags dirty) when the lock is already held by another process", async () => { + // Simulate an in-flight ingest in another process: hand-hold the lock with + // a live pid (our own — isProcessAlive(process.pid) is true, so not stolen). + await tryAcquireIngestLock(vault.name, { rootOverride: root }); + + let ingestCalls = 0; + const r = await indexVaultWithContextFit(vault, { + lockRootOverride: root, + _deps: okDeps(async () => { + ingestCalls += 1; + return "stats"; + }), + }); + + expect(r.status).toBe("skipped"); + expect(ingestCalls).toBe(0); // did not ingest + expect(await isIngestDirty(vault.name, { rootOverride: root })).toBe(true); // flagged + }); + + it("ingests once, clears the dirty flag, and releases the lock", async () => { + let ingestCalls = 0; + const r = await indexVaultWithContextFit(vault, { + lockRootOverride: root, + _deps: okDeps(async () => { + ingestCalls += 1; + return "stats"; + }), + }); + + expect(r.status).toBe("completed"); + expect(ingestCalls).toBe(1); + expect(await isIngestDirty(vault.name, { rootOverride: root })).toBe(false); + // Lock released → a subsequent acquire succeeds. + const again = await tryAcquireIngestLock(vault.name, { rootOverride: root }); + expect(again.acquired).toBe(true); + }); + + it("does a trailing re-ingest when a change lands DURING the ingest", async () => { + // First ingest sets the flag mid-run (simulating a write arriving during + // the rebuild); the holder must loop exactly once more, then stop. + let ingestCalls = 0; + const r = await indexVaultWithContextFit(vault, { + lockRootOverride: root, + _deps: okDeps(async () => { + ingestCalls += 1; + if (ingestCalls === 1) { + // A concurrent write arrives while pass 1 is running. + await markIngestDirty(vault.name, { rootOverride: root }); + } + return "stats"; + }), + }); + + expect(r.status).toBe("completed"); + expect(ingestCalls).toBe(2); // one trailing pass captured the mid-run change + expect(await isIngestDirty(vault.name, { rootOverride: root })).toBe(false); + }); + + it("releases the lock even when the ingest throws", async () => { + const r = await indexVaultWithContextFit(vault, { + lockRootOverride: root, + _deps: okDeps(async () => { + throw new Error("boom"); + }), + }); + expect(r.status).toBe("failed"); + expect(r.error).toMatch(/boom/); + // finally released the lock despite the throw. + const again = await tryAcquireIngestLock(vault.name, { rootOverride: root }); + expect(again.acquired).toBe(true); + }); + + it("bounds trailing passes so constant writes cannot loop forever", async () => { + // The flag is re-set on every pass → the loop must stop at MAX_PASSES (8), + // not spin indefinitely. + let ingestCalls = 0; + const r = await indexVaultWithContextFit(vault, { + lockRootOverride: root, + _deps: okDeps(async () => { + ingestCalls += 1; + await markIngestDirty(vault.name, { rootOverride: root }); + return "stats"; + }), + }); + expect(r.status).toBe("completed"); + expect(ingestCalls).toBe(8); // MAX_PASSES backstop + }); +}); diff --git a/src/adapters/retrieval/contextfit/ingest-lock.ts b/src/adapters/retrieval/contextfit/ingest-lock.ts new file mode 100644 index 0000000..7eb6141 --- /dev/null +++ b/src/adapters/retrieval/contextfit/ingest-lock.ts @@ -0,0 +1,165 @@ +/** + * Cross-process ingest mutex for the ContextFit KB (Issue #17). + * + * A ContextFit ingest is a FULL KB rebuild: it `rm`s the per-vault KB dir and + * re-runs `contextfit ingest`. Two ingests against the same vault dir race — + * the loser's temp files get clobbered mid-write and it crashes + * (`FileNotFoundError: .../chunks/index.json.tmp`), potentially leaving a + * half-written KB. The four ingest call sites (CLI `index`, serve note-write + * refresh, serve file-watcher re-ingest, serve startup catch-up) live in + * different processes and share no in-memory state, so an in-process guard + * (the watcher's `cfReingestInFlight` boolean) cannot serialize them. + * + * This module provides a dedicated per-vault file lock — + * `~/.vault-memory/locks/.ingest.lock` — held for the duration of one + * ingest. It is DELIBERATELY SEPARATE from `src/brief/lock.ts`'s + * `.lock`, which the staleness daemon holds for its whole lifetime; + * reusing that lock would make every ingest on a serve process see it "held" + * and skip forever. + * + * Contention policy is SKIP + a persisted dirty flag + * (`.ingest.dirty`): a second-comer does not wait — it marks the vault + * dirty and returns. When the lock holder finishes it checks the flag and does + * exactly one trailing re-ingest, so the last change is never silently lost + * even across processes. A dirty flag left behind by a crash is honored on the + * next ingest or server startup. + * + * Lock lifetime is bounded by the ingest itself: ~1–1.5 min typically, and a + * hard ceiling of the ingest spawn timeout (600 s, after which contextfit is + * force-killed and the `finally` releases the lock). A crashed holder never + * strands the lock: it records its PID and the next acquirer steals it when + * that PID is dead (POSIX `kill(pid, 0)` → ESRCH), mirroring brief/lock.ts. + */ + +// vault-memory:claude-ok — process state (~/.vault-memory/locks/), not vault +// content. Same lockfile carve-out as src/brief/lock.ts (ADR-005). + +import { open, readFile, unlink, mkdir, writeFile, stat } from "node:fs/promises"; +import { homedir } from "node:os"; +import { join } from "node:path"; + +/** Test-only override for the ~/.vault-memory root. Production omits it. */ +export interface IngestLockOptions { + rootOverride?: string; +} + +function lockDir(rootOverride?: string): string { + if (rootOverride !== undefined) return join(rootOverride, "locks"); + return join(homedir(), ".vault-memory", "locks"); +} + +function lockPath(vaultName: string, rootOverride?: string): string { + return join(lockDir(rootOverride), `${vaultName}.ingest.lock`); +} + +function dirtyPath(vaultName: string, rootOverride?: string): string { + return join(lockDir(rootOverride), `${vaultName}.ingest.dirty`); +} + +/** POSIX `kill(pid, 0)`: true if the pid is alive, false on ESRCH. */ +function isProcessAlive(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch (err) { + if ((err as NodeJS.ErrnoException).code === "ESRCH") return false; + // EPERM (alive but inaccessible) or anything else → treat as alive so we + // never steal a lock from a live peer. + return true; + } +} + +async function readOwnerPid(path: string): Promise { + try { + const buf = await readFile(path, "utf8"); + const pid = parseInt(buf.trim(), 10); + return Number.isFinite(pid) && pid > 0 ? pid : null; + } catch { + return null; + } +} + +export type IngestLockResult = + | { acquired: true; path: string } + | { acquired: false; ownerPid: number; path: string }; + +/** + * Try to acquire the ingest lock for a vault. Atomic exclusive create via + * `open(path, 'wx')`. On EEXIST: steal if the recorded PID is dead or the file + * is malformed; otherwise return contended. Bounded retries so a racing peer + * cannot loop us forever. + */ +export async function tryAcquireIngestLock( + vaultName: string, + options: IngestLockOptions = {}, +): Promise { + const dir = lockDir(options.rootOverride); + await mkdir(dir, { recursive: true }); + const path = lockPath(vaultName, options.rootOverride); + const MAX_ATTEMPTS = 3; + + const attempt = async (n: number): Promise => { + if (n > MAX_ATTEMPTS) return { acquired: false, ownerPid: -1, path }; + try { + const handle = await open(path, "wx"); + try { + await handle.writeFile(`${process.pid}\n`); + } finally { + await handle.close(); + } + return { acquired: true, path }; + } catch (err) { + if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err; + const ownerPid = await readOwnerPid(path); + if (ownerPid === null || !isProcessAlive(ownerPid)) { + // Stale (dead owner) or malformed: unlink and retry. + await unlink(path).catch(() => undefined); + return attempt(n + 1); + } + return { acquired: false, ownerPid, path }; + } + }; + + return attempt(1); +} + +/** Release the ingest lock. Safe to call even if we don't hold it. */ +export async function releaseIngestLock( + vaultName: string, + options: IngestLockOptions = {}, +): Promise { + await unlink(lockPath(vaultName, options.rootOverride)).catch(() => undefined); +} + +/** Mark a vault as needing a (re-)ingest — set by a skipped second-comer. */ +export async function markIngestDirty( + vaultName: string, + options: IngestLockOptions = {}, +): Promise { + const dir = lockDir(options.rootOverride); + await mkdir(dir, { recursive: true }); + await writeFile(dirtyPath(vaultName, options.rootOverride), `${process.pid}\n`).catch( + () => undefined, + ); +} + +/** True if a dirty flag is present for the vault. */ +export async function isIngestDirty( + vaultName: string, + options: IngestLockOptions = {}, +): Promise { + try { + await stat(dirtyPath(vaultName, options.rootOverride)); + return true; + } catch { + return false; + } +} + +/** Clear the dirty flag — called by the lock holder before it ingests. */ +export async function clearIngestDirty( + vaultName: string, + options: IngestLockOptions = {}, +): Promise { + await unlink(dirtyPath(vaultName, options.rootOverride)).catch(() => undefined); +} diff --git a/src/cli.ts b/src/cli.ts index 588429d..c260f1e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -98,6 +98,12 @@ async function runIndex(rest: string[]): Promise { console.error( `✓ ${vault.config.name}: ${sqlite.notesIndexed} notes (SQLite) + ContextFit KB · ${sqlite.durationMs + cfResult.durationMs}ms`, ); + } else if (cfResult.status === "skipped") { + // Issue #17: another process held the ingest lock. Not an error — the + // holder will do a trailing re-ingest that captures our changes. + console.error( + `↷ ${vault.config.name}: ${sqlite.notesIndexed} notes (SQLite); ContextFit KB re-ingest already in progress in another process — flagged for retry, skipping`, + ); } else { console.error(`✗ ${vault.config.name}: ContextFit KB failed — ${cfResult.error}`); process.exitCode = 1; diff --git a/src/indexer/catchup.ts b/src/indexer/catchup.ts index 124657e..6d30bcb 100644 --- a/src/indexer/catchup.ts +++ b/src/indexer/catchup.ts @@ -84,15 +84,25 @@ export async function catchupVault(options: CatchupOptions): Promise 0 || removed > 0)) { - const { indexVaultWithContextFit } = await import("../adapters/retrieval/contextfit/index.js"); - const r = await indexVaultWithContextFit(vault.config, { onProgress: log }); - log( - r.status === "completed" - ? `catch-up: ContextFit KB rebuilt (${r.durationMs}ms)` - : `catch-up: ContextFit KB rebuild failed: ${r.error}`, - ); + // KB once so retrieval matches the reconciled SQLite layer. Issue #17: also + // rebuild when a dirty flag was left behind (e.g. an ingest was skipped and + // its holder crashed before the trailing pass) so a stranded flag is honored + // at the latest on the next server start. + if (isContextFit) { + const cf = await import("../adapters/retrieval/contextfit/index.js"); + const dirty = await ( + await import("../adapters/retrieval/contextfit/ingest-lock.js") + ).isIngestDirty(vault.config.name); + if (reindexed > 0 || removed > 0 || dirty) { + const r = await cf.indexVaultWithContextFit(vault.config, { onProgress: log }); + log( + r.status === "completed" + ? `catch-up: ContextFit KB rebuilt (${r.durationMs}ms)` + : r.status === "skipped" + ? `catch-up: ContextFit KB re-ingest already in progress; skipping` + : `catch-up: ContextFit KB rebuild failed: ${r.error}`, + ); + } } return { From 11268ae38ecfdadc184dd9f086c16f6558e4fa6d Mon Sep 17 00:00:00 2001 From: Oliver Wrede Date: Thu, 2 Jul 2026 13:05:21 +0200 Subject: [PATCH 3/3] chore(build): rebuild dist for #16 + #17 fixes Co-Authored-By: Claude Opus 4.8 (1M context) --- dist/cli.js | 235 +++++++++++++++++++++++++++++++++++++----------- dist/cli.js.map | 2 +- 2 files changed, 182 insertions(+), 55 deletions(-) diff --git a/dist/cli.js b/dist/cli.js index c0b997c..edc1d26 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -203,13 +203,13 @@ async function addVault(opts) { const cfgFile = opts.configFile ?? configPath(); const binary = opts.binary ?? "vault-memory"; const steps = []; - const stat = await fs.stat(resolvedPath).catch((err) => { + const stat2 = await fs.stat(resolvedPath).catch((err) => { if (err.code === "ENOENT") { throw new Error(`Vault path does not exist: ${resolvedPath}`); } throw err; }); - if (!stat.isDirectory()) { + if (!stat2.isDirectory()) { throw new Error(`Vault path is not a directory: ${resolvedPath}`); } const proposedName = opts.name ?? slugifyVaultName(basename(resolvedPath)); @@ -5122,6 +5122,103 @@ var init_cli = __esm({ } }); +// src/adapters/retrieval/contextfit/ingest-lock.ts +var ingest_lock_exports = {}; +__export(ingest_lock_exports, { + clearIngestDirty: () => clearIngestDirty, + isIngestDirty: () => isIngestDirty, + markIngestDirty: () => markIngestDirty, + releaseIngestLock: () => releaseIngestLock, + tryAcquireIngestLock: () => tryAcquireIngestLock +}); +import { open, readFile as readFile3, unlink, mkdir as mkdir2, writeFile as writeFile2, stat } from "fs/promises"; +import { homedir as homedir4 } from "os"; +import { join as join4 } from "path"; +function lockDir(rootOverride) { + if (rootOverride !== void 0) return join4(rootOverride, "locks"); + return join4(homedir4(), ".vault-memory", "locks"); +} +function lockPath(vaultName, rootOverride) { + return join4(lockDir(rootOverride), `${vaultName}.ingest.lock`); +} +function dirtyPath(vaultName, rootOverride) { + return join4(lockDir(rootOverride), `${vaultName}.ingest.dirty`); +} +function isProcessAlive(pid) { + try { + process.kill(pid, 0); + return true; + } catch (err) { + if (err.code === "ESRCH") return false; + return true; + } +} +async function readOwnerPid(path7) { + try { + const buf = await readFile3(path7, "utf8"); + const pid = parseInt(buf.trim(), 10); + return Number.isFinite(pid) && pid > 0 ? pid : null; + } catch { + return null; + } +} +async function tryAcquireIngestLock(vaultName, options = {}) { + const dir = lockDir(options.rootOverride); + await mkdir2(dir, { recursive: true }); + const path7 = lockPath(vaultName, options.rootOverride); + const MAX_ATTEMPTS = 3; + const attempt = async (n) => { + if (n > MAX_ATTEMPTS) return { acquired: false, ownerPid: -1, path: path7 }; + try { + const handle = await open(path7, "wx"); + try { + await handle.writeFile(`${process.pid} +`); + } finally { + await handle.close(); + } + return { acquired: true, path: path7 }; + } catch (err) { + if (err.code !== "EEXIST") throw err; + const ownerPid = await readOwnerPid(path7); + if (ownerPid === null || !isProcessAlive(ownerPid)) { + await unlink(path7).catch(() => void 0); + return attempt(n + 1); + } + return { acquired: false, ownerPid, path: path7 }; + } + }; + return attempt(1); +} +async function releaseIngestLock(vaultName, options = {}) { + await unlink(lockPath(vaultName, options.rootOverride)).catch(() => void 0); +} +async function markIngestDirty(vaultName, options = {}) { + const dir = lockDir(options.rootOverride); + await mkdir2(dir, { recursive: true }); + await writeFile2(dirtyPath(vaultName, options.rootOverride), `${process.pid} +`).catch( + () => void 0 + ); +} +async function isIngestDirty(vaultName, options = {}) { + try { + await stat(dirtyPath(vaultName, options.rootOverride)); + return true; + } catch { + return false; + } +} +async function clearIngestDirty(vaultName, options = {}) { + await unlink(dirtyPath(vaultName, options.rootOverride)).catch(() => void 0); +} +var init_ingest_lock = __esm({ + "src/adapters/retrieval/contextfit/ingest-lock.ts"() { + "use strict"; + init_esm_shims(); + } +}); + // src/adapters/retrieval/contextfit/index.ts var contextfit_exports = {}; __export(contextfit_exports, { @@ -5131,11 +5228,11 @@ __export(contextfit_exports, { searchVaultWithContextFit: () => searchVaultWithContextFit, sourceToNotePath: () => sourceToNotePath }); -import { homedir as homedir4 } from "os"; +import { homedir as homedir5 } from "os"; import { rm } from "fs/promises"; -import { join as join4, relative, isAbsolute } from "path"; +import { join as join5, relative, isAbsolute } from "path"; function contextFitKbDir(vaultName) { - return join4(homedir4(), ".vault-memory", "contextfit", vaultName); + return join5(homedir5(), ".vault-memory", "contextfit", vaultName); } function cliConfigForVault(vault) { const cfg = { @@ -5150,8 +5247,12 @@ async function indexVaultWithContextFit(vault, opts = {}) { }); const cfg = cliConfigForVault(vault); const start = Date.now(); + const lockOpts = opts.lockRootOverride !== void 0 ? { rootOverride: opts.lockRootOverride } : {}; + const probe = opts._deps?.probe ?? ((c) => contextFitProbe({ command: c.command })); + const ingest = opts._deps?.ingest ?? contextFitIngest; + const clearKb = opts._deps?.clearKb ?? ((p) => rm(p, { recursive: true, force: true })); log(`ContextFit: ingesting ${vault.path} \u2192 ${cfg.kbPath}`); - const available = await contextFitProbe({ command: cfg.command }); + const available = await probe(cfg); if (!available) { return { status: "failed", @@ -5160,14 +5261,29 @@ async function indexVaultWithContextFit(vault, opts = {}) { error: `ContextFit CLI not runnable (tried '${cfg.command}'). Install with \`pipx install contextfit\` or set [[vaults]].contextfit.command.` }; } + const lock = await tryAcquireIngestLock(vault.name, lockOpts); + if (!lock.acquired) { + await markIngestDirty(vault.name, lockOpts); + log(`ContextFit: re-ingest already in progress (pid ${lock.ownerPid}); flagged for retry`); + return { status: "skipped", stats: "", durationMs: Date.now() - start }; + } try { - await rm(cfg.kbPath, { recursive: true, force: true }); - const stats = await contextFitIngest(cfg, vault.path); + const MAX_PASSES = 8; + let stats = ""; + let passes = 0; + do { + await clearIngestDirty(vault.name, lockOpts); + await clearKb(cfg.kbPath); + stats = await ingest(cfg, vault.path); + passes += 1; + } while (passes < MAX_PASSES && await isIngestDirty(vault.name, lockOpts)); log(stats.trim().split("\n").slice(-3).join(" \xB7 ")); return { status: "completed", stats, durationMs: Date.now() - start }; } catch (err) { const message = err instanceof Error ? err.message : String(err); return { status: "failed", stats: "", durationMs: Date.now() - start, error: message }; + } finally { + await releaseIngestLock(vault.name, lockOpts); } } function sourceToNotePath(source, vaultPath) { @@ -5213,6 +5329,7 @@ var init_contextfit = __esm({ "use strict"; init_esm_shims(); init_cli(); + init_ingest_lock(); DEFAULT_COMMAND = "contextfit"; } }); @@ -5344,9 +5461,9 @@ var init_reranker = __esm({ }); // src/rerank/onnx-reranker.ts -import { readFile as readFile3 } from "fs/promises"; +import { readFile as readFile4 } from "fs/promises"; import { existsSync } from "fs"; -import { join as join5 } from "path"; +import { join as join6 } from "path"; function sigmoid(x) { return 1 / (1 + Math.exp(-x)); } @@ -5424,8 +5541,8 @@ var init_onnx_reranker = __esm({ if (this.loaded) return this.loaded; if (this.loading) return this.loading; this.loading = (async () => { - const modelPath = join5(this.modelDir, "model_quantized.onnx"); - const tokenizerPath = join5(this.modelDir, "tokenizer.json"); + const modelPath = join6(this.modelDir, "model_quantized.onnx"); + const tokenizerPath = join6(this.modelDir, "tokenizer.json"); if (!existsSync(modelPath)) { throw new Error( `OnnxReranker: model file not found at ${modelPath}. Run: curl -L https://huggingface.co/onnx-community/bge-reranker-v2-m3-ONNX/resolve/main/onnx/model_quantized.onnx -o ${modelPath}` @@ -5439,7 +5556,7 @@ var init_onnx_reranker = __esm({ const [ort, tokMod, tokJson] = await Promise.all([ import("onnxruntime-node"), import("@huggingface/tokenizers"), - readFile3(tokenizerPath, "utf-8") + readFile4(tokenizerPath, "utf-8") ]); const tokenizerJson = JSON.parse(tokJson); const config = deriveTokenizerConfig(tokenizerJson); @@ -5914,11 +6031,11 @@ function stripDynamicViewBlocks(body) { let i = 0; while (i < lines.length) { const line = lines[i]; - const open2 = FENCE_OPEN_RE.exec(line); - if (open2) { - const indent = open2[1] ?? ""; - const marker = open2[2] ?? ""; - const lang = (open2[3] ?? "").toLowerCase(); + const open3 = FENCE_OPEN_RE.exec(line); + if (open3) { + const indent = open3[1] ?? ""; + const marker = open3[2] ?? ""; + const lang = (open3[3] ?? "").toLowerCase(); const markerChar = marker[0]; const isDynamic = DYNAMIC_VIEW_LANGS.has(lang); let j = i + 1; @@ -6008,7 +6125,7 @@ import * as path3 from "path"; import matter from "gray-matter"; async function parseNote(absolutePath, vaultRoot) { const raw = await fs3.readFile(absolutePath, "utf-8"); - const stat = await fs3.stat(absolutePath); + const stat2 = await fs3.stat(absolutePath); const parsed = matter(raw); const content = parsed.content; const fmData = parsed.data; @@ -6016,7 +6133,7 @@ async function parseNote(absolutePath, vaultRoot) { const title = extractTitle(content) ?? path3.basename(absolutePath, ".md"); const hash = computeNoteHash(content, frontmatter); const bodyHash = computeBodyHash(content); - const mtime = Math.floor(stat.mtimeMs); + const mtime = Math.floor(stat2.mtimeMs); const bodyLinks = extractWikilinks(content); const frontmatterLinks = extractFrontmatterWikilinks(frontmatter); const wikilinks = frontmatterLinks.length === 0 ? bodyLinks : mergeFrontmatterIntoBody(bodyLinks, frontmatterLinks); @@ -6126,8 +6243,8 @@ var init_obsidian_fs = __esm({ for (const abs of files) { if (limit !== void 0 && yielded >= limit) break; const rel = this.toPosix(path4.relative(path4.resolve(this.vault.path), abs)); - const stat = await fs4.stat(abs); - const mtime = Math.floor(stat.mtimeMs); + const stat2 = await fs4.stat(abs); + const mtime = Math.floor(stat2.mtimeMs); if (since !== void 0 && mtime < since) continue; const body = await fs4.readFile(abs, "utf-8"); const hash = computeBodyHash(body); @@ -6150,7 +6267,7 @@ var init_obsidian_fs = __esm({ const abs = this.absPath(rel); if (CONTRACT_PATH_RE.test(rel)) { const body = await fs4.readFile(abs, "utf-8"); - const stat = await fs4.stat(abs); + const stat2 = await fs4.stat(abs); const hash = computeBodyHash(body); return { id, @@ -6159,7 +6276,7 @@ var init_obsidian_fs = __esm({ blocks: [{ kind: "paragraph", text: body }], properties: {}, links: [], - mtime: Math.floor(stat.mtimeMs), + mtime: Math.floor(stat2.mtimeMs), hash, display_url: this.formatDisplayUrl(id) }; @@ -6891,6 +7008,7 @@ async function indexVault(vault, options) { vault.db.transaction(() => { const allNotes = vault.db.notes.listAll(); for (const n of allNotes) { + vault.db.sections.deleteByNote(n.id); vault.db.chunks.deleteByNote(n.id); vault.db.wikilinks.deleteByNote(n.id); vault.db.edges.deleteByNote(n.id); @@ -7497,12 +7615,15 @@ async function catchupVault(options) { } } } - if (isContextFit2 && (reindexed > 0 || removed > 0)) { - const { indexVaultWithContextFit: indexVaultWithContextFit2 } = await Promise.resolve().then(() => (init_contextfit(), contextfit_exports)); - const r = await indexVaultWithContextFit2(vault.config, { onProgress: log }); - log( - r.status === "completed" ? `catch-up: ContextFit KB rebuilt (${r.durationMs}ms)` : `catch-up: ContextFit KB rebuild failed: ${r.error}` - ); + if (isContextFit2) { + const cf = await Promise.resolve().then(() => (init_contextfit(), contextfit_exports)); + const dirty = await (await Promise.resolve().then(() => (init_ingest_lock(), ingest_lock_exports))).isIngestDirty(vault.config.name); + if (reindexed > 0 || removed > 0 || dirty) { + const r = await cf.indexVaultWithContextFit(vault.config, { onProgress: log }); + log( + r.status === "completed" ? `catch-up: ContextFit KB rebuilt (${r.durationMs}ms)` : r.status === "skipped" ? `catch-up: ContextFit KB re-ingest already in progress; skipping` : `catch-up: ContextFit KB rebuild failed: ${r.error}` + ); + } } return { scanned: files.length, @@ -7944,7 +8065,7 @@ async function writeNote(input) { if (written === null) { throw new Error(`Internal error: file disappeared after write: ${relativePath}`); } - const stat = await fs6.stat(absPath); + const stat2 = await fs6.stat(absPath); const previousNote = vault.db.notes.getByPath(relativePath); const previousHash = previousNote?.hash ?? null; const title = extractTitle2(written.content, relativePath); @@ -7958,7 +8079,7 @@ async function writeNote(input) { title, hash: written.hash, bodyHash: computeBodyHash(written.content), - mtime: Math.floor(stat.mtimeMs), + mtime: Math.floor(stat2.mtimeMs), wordCount: countWords3(written.content) }); vault.db.aliases.setForNote(up.id, extractAliases(written.frontmatter)); @@ -8330,7 +8451,7 @@ var init_path = __esm({ }); // src/adapters/delivery/obsidian-fs/contract-yaml-read.ts -import { readFile as readFile4 } from "fs/promises"; +import { readFile as readFile5 } from "fs/promises"; var init_contract_yaml_read = __esm({ "src/adapters/delivery/obsidian-fs/contract-yaml-read.ts"() { "use strict"; @@ -10350,17 +10471,17 @@ var init_get = __esm({ }); // src/brief/lock.ts -import { open, readFile as readFile5, unlink, mkdir as mkdir2 } from "fs/promises"; -import { homedir as homedir5 } from "os"; -import { join as join7 } from "path"; -function lockDir(rootOverride) { - if (rootOverride !== void 0) return join7(rootOverride, "locks"); - return join7(homedir5(), ".vault-memory", "locks"); +import { open as open2, readFile as readFile6, unlink as unlink2, mkdir as mkdir3 } from "fs/promises"; +import { homedir as homedir6 } from "os"; +import { join as join8 } from "path"; +function lockDir2(rootOverride) { + if (rootOverride !== void 0) return join8(rootOverride, "locks"); + return join8(homedir6(), ".vault-memory", "locks"); } -function lockPath(vaultName, rootOverride) { - return join7(lockDir(rootOverride), `${vaultName}.lock`); +function lockPath2(vaultName, rootOverride) { + return join8(lockDir2(rootOverride), `${vaultName}.lock`); } -function isProcessAlive(pid) { +function isProcessAlive2(pid) { try { process.kill(pid, 0); return true; @@ -10369,9 +10490,9 @@ function isProcessAlive(pid) { return true; } } -async function readOwnerPid(path7) { +async function readOwnerPid2(path7) { try { - const buf = await readFile5(path7, "utf8"); + const buf = await readFile6(path7, "utf8"); const pid = parseInt(buf.trim(), 10); return Number.isFinite(pid) && pid > 0 ? pid : null; } catch { @@ -10379,16 +10500,16 @@ async function readOwnerPid(path7) { } } async function tryAcquireLock(vaultName, options = {}) { - const dir = lockDir(options.rootOverride); - await mkdir2(dir, { recursive: true }); - const path7 = lockPath(vaultName, options.rootOverride); + const dir = lockDir2(options.rootOverride); + await mkdir3(dir, { recursive: true }); + const path7 = lockPath2(vaultName, options.rootOverride); const MAX_ATTEMPTS = 3; const attempt = async (n, stolenFromPid) => { if (n > MAX_ATTEMPTS) { return { acquired: false, ownerPid: stolenFromPid ?? -1, path: path7 }; } try { - const handle = await open(path7, "wx"); + const handle = await open2(path7, "wx"); try { await handle.writeFile(`${process.pid} `); @@ -10400,9 +10521,9 @@ async function tryAcquireLock(vaultName, options = {}) { return result; } catch (err) { if (err.code !== "EEXIST") throw err; - const ownerPid = await readOwnerPid(path7); - if (ownerPid === null || !isProcessAlive(ownerPid)) { - await unlink(path7).catch(() => void 0); + const ownerPid = await readOwnerPid2(path7); + if (ownerPid === null || !isProcessAlive2(ownerPid)) { + await unlink2(path7).catch(() => void 0); return attempt(n + 1, ownerPid ?? -1); } return { acquired: false, ownerPid, path: path7 }; @@ -10411,7 +10532,7 @@ async function tryAcquireLock(vaultName, options = {}) { return attempt(1); } async function releaseLock(vaultName, options = {}) { - await unlink(lockPath(vaultName, options.rootOverride)).catch(() => void 0); + await unlink2(lockPath2(vaultName, options.rootOverride)).catch(() => void 0); } var init_lock = __esm({ "src/brief/lock.ts"() { @@ -11279,6 +11400,8 @@ var init_watcher = __esm({ const r = await indexVaultWithContextFit2(this.opts.vault.config, {}); if (r.status === "completed") { this.opts.log(`ContextFit KB refreshed (${r.durationMs}ms)`); + } else if (r.status === "skipped") { + this.opts.log(`ContextFit KB refresh skipped (another ingest in progress; flagged)`); } else { this.opts.log(`ContextFit KB refresh failed: ${r.error}`); } @@ -16328,7 +16451,7 @@ __export(server_exports, { }); import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { homedir as homedir6 } from "os"; +import { homedir as homedir7 } from "os"; import { join as joinPath } from "path"; async function discoverMemorySinks(configured, vaults) { if (configured.length > 0) { @@ -16395,7 +16518,7 @@ async function serve(options = {}) { const activeVault = process.env.VAULT_MEMORY_ACTIVE_VAULT?.trim() || void 0; const rerankerBackend = config.server.reranker_backend ?? (config.server.reranker_model ? "onnx" : void 0); const reranker = config.server.reranker_model ? rerankerBackend === "ollama" ? new OllamaReranker({ ollama, model: config.server.reranker_model }) : new OnnxReranker({ - modelDir: config.server.reranker_model_dir ?? joinPath(homedir6(), ".vault-memory", "models", "bge-reranker-v2-m3") + modelDir: config.server.reranker_model_dir ?? joinPath(homedir7(), ".vault-memory", "models", "bge-reranker-v2-m3") }) : void 0; const watchers = /* @__PURE__ */ new Map(); const briefDaemons = /* @__PURE__ */ new Map(); @@ -17515,6 +17638,10 @@ async function runIndex(rest) { console.error( `\u2713 ${vault.config.name}: ${sqlite.notesIndexed} notes (SQLite) + ContextFit KB \xB7 ${sqlite.durationMs + cfResult.durationMs}ms` ); + } else if (cfResult.status === "skipped") { + console.error( + `\u21B7 ${vault.config.name}: ${sqlite.notesIndexed} notes (SQLite); ContextFit KB re-ingest already in progress in another process \u2014 flagged for retry, skipping` + ); } else { console.error(`\u2717 ${vault.config.name}: ContextFit KB failed \u2014 ${cfResult.error}`); process.exitCode = 1; diff --git a/dist/cli.js.map b/dist/cli.js.map index afe04af..e90cf6b 100644 --- a/dist/cli.js.map +++ b/dist/cli.js.map @@ -1 +1 @@ -{"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../src/config/loader.ts","../src/config/add-vault.ts","../src/config/index.ts","../src/plugin-tools/runtime-config.ts","../src/plugin-tools/set-runtime-config.ts","../src/plugin-tools/resolve-secret.ts","../src/plugin-tools/set-mcp-client.ts","../src/plugin-tools/get-runtime-stats.ts","../src/plugin-tools/trigger-reindex.ts","../src/plugin-tools/suppress-contract-write.ts","../src/plugin-tools/source-tools.ts","../src/errors/format.ts","../src/plugin-tools/index.ts","../src/chunker/headings.ts","../src/sections/anchor.ts","../src/sections/extract.ts","../src/sections/backfill.ts","../src/chunker/chunk-id.ts","../src/db/schema.ts","../src/db/queries/notes.ts","../src/db/queries/chunks.ts","../src/db/queries/embeddings.ts","../src/db/queries/wikilinks.ts","../src/db/queries/edges.ts","../src/db/queries/audit.ts","../src/db/queries/models.ts","../src/db/queries/fts.ts","../src/db/queries/aliases.ts","../src/db/queries/sections.ts","../src/db/queries/brief_sources.ts","../src/db/queries/daemon_state.ts","../src/db/queries/contract-audit.ts","../src/db/database.ts","../src/db/index.ts","../src/vault/manager.ts","../src/vault/index.ts","../src/ollama/retry.ts","../src/ollama/client.ts","../src/ollama/index.ts","../src/adapters/registry.ts","../src/graph/graph.ts","../src/memory/citation-packet.ts","../src/graph/expand.ts","../src/graph/cluster.ts","../src/graph/index.ts","../src/search/hybrid.ts","../src/adapters/retrieval/contextfit/cli.ts","../src/adapters/retrieval/contextfit/index.ts","../src/search/dispatch.ts","../src/search/glob.ts","../src/search/index.ts","../src/rerank/reranker.ts","../src/rerank/onnx-reranker.ts","../src/rerank/index.ts","../src/server/responses.ts","../src/server/utils.ts","../src/frontmatter/query.ts","../src/adapters/source/obsidian-fs/scanner.ts","../src/adapters/source/obsidian-fs/wikilinks.ts","../src/reader/datacore.ts","../src/adapters/source/obsidian-fs/hash.ts","../src/adapters/source/obsidian-fs/parser.ts","../src/adapters/source/obsidian-fs/index.ts","../src/chunker/tokens.ts","../src/chunker/chunker.ts","../src/chunker/index.ts","../src/indexer/resolver.ts","../src/indexer/extract-edges.ts","../src/sections/index.ts","../src/indexer/indexer.ts","../src/indexer/single.ts","../src/indexer/catchup.ts","../src/indexer/shadow.ts","../src/indexer/vacuum.ts","../src/indexer/index.ts","../src/adapters/delivery/obsidian-fs/fs.ts","../src/adapters/delivery/obsidian-fs/write.ts","../src/memory/validator.ts","../src/memory/contract/default-v1.ts","../src/memory/contract/default-brief-v1.ts","../src/adapters/delivery/obsidian-fs/path.ts","../src/adapters/delivery/obsidian-fs/contract-yaml-read.ts","../src/memory/contract/schema.ts","../src/memory/contract/loader.ts","../src/memory/contract/index.ts","../src/memory/sink.ts","../src/adapters/delivery/obsidian-fs/sentinel.ts","../src/adapters/delivery/obsidian-fs/index.ts","../src/frontmatter/update.ts","../src/frontmatter/index.ts","../src/memory/registry.ts","../src/memory/resources/list-sinks.ts","../src/memory/resources/memory-stats.ts","../src/memory/resources/index.ts","../src/memory/index.ts","../src/resource-registry.ts","../src/memory/tools/record-observation.ts","../src/memory/tools/supersede.ts","../src/memory/tools/recall.ts","../src/memory/tools/index.ts","../src/brief/chunk-id.ts","../src/brief/source-hashes.ts","../src/brief/llm-ladder.ts","../src/brief/body-validator.ts","../src/brief/compile.ts","../src/brief/get.ts","../src/brief/lock.ts","../src/brief/daemon.ts","../src/brief/resources.ts","../src/brief/index.ts","../src/assembly/search-sections.ts","../src/assembly/outline.ts","../src/adapters/change-feed/obsidian-fs/queue.ts","../src/adapters/change-feed/obsidian-fs/chokidar-config.ts","../src/adapters/change-feed/obsidian-fs/watcher.ts","../src/adapters/change-feed/obsidian-fs/suppression.ts","../src/adapters/change-feed/obsidian-fs/change-feed.ts","../src/adapters/change-feed/obsidian-fs/index.ts","../src/tool-registry.ts","../src/contracts/types.ts","../src/contracts/types-catalog.ts","../src/contracts/json-schema-ref.ts","../src/contracts/input-schema.ts","../src/contracts/registry.ts","../src/contracts/slug.ts","../src/contracts/audit.ts","../src/contracts/schema.ts","../src/contracts/loader.ts","../src/contracts/auto-register.ts","../src/contracts/templates.ts","../src/contracts/mcp-clients.ts","../src/contracts/verbs/mcp-extension.ts","../src/contracts/verbs/index.ts","../src/contracts/instantiate.ts","../src/contracts/describe.ts","../src/contracts/resources.ts","../src/contracts/sources-resources.ts","../src/contracts/index.ts","../src/audit/audit.ts","../src/audit/index.ts","../src/server/handlers/vault.ts","../src/schema/folder-conventions.ts","../src/schema/neighbor-inference.ts","../src/schema/content-heuristics.ts","../src/schema/combiner.ts","../src/schema/index.ts","../src/server/handlers/notes.ts","../src/server/handlers/search.ts","../src/server/handlers/graph.ts","../src/server/handlers/memory.ts","../src/server/handlers/brief.ts","../src/assembly/dossier.ts","../src/assembly/bundle.ts","../src/assembly/index.ts","../src/server/handlers/assembly.ts","../src/server/handlers/contracts.ts","../package.json","../src/version.ts","../src/server.ts","../src/cli.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","/**\n * Configuration loader.\n *\n * Reads `~/.vault-memory/config.toml`. Returns sensible defaults when the\n * file does not exist (empty vault list, default Ollama endpoint). Validates\n * shape with Zod.\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { parse as parseToml } from \"smol-toml\";\nimport { z } from \"zod\";\nimport type { AppConfig } from \"../types.js\";\n\nconst ServerConfigSchema = z.object({\n log_level: z.enum([\"debug\", \"info\", \"warn\", \"error\"]).optional(),\n ollama_endpoint: z.string().url().optional(),\n default_embedding_model: z.string().optional(),\n reranker_model: z.string().optional(),\n reranker_backend: z.enum([\"onnx\", \"ollama\"]).optional(),\n reranker_model_dir: z.string().optional(),\n});\n\n/**\n * ADR-008: per-vault ContextFit settings. Only consulted when\n * `backend = \"contextfit\"`. All optional — a bare `backend = \"contextfit\"`\n * uses ContextFit on PATH with the cl100k_base tokenizer + hybrid method.\n */\nconst ContextFitConfigSchema = z.object({\n command: z.string().min(1).optional(),\n tokenizer: z.string().min(1).optional(),\n method: z.enum([\"exact\", \"bm25\", \"sid\", \"graph\", \"hierarchy\", \"hybrid\"]).optional(),\n});\n\nconst VaultConfigSchema = z.object({\n name: z.string().min(1),\n path: z.string().min(1),\n // ADR-008: retrieval engine. Omitted ⇒ \"ollama\" (back-compat default).\n backend: z.enum([\"ollama\", \"contextfit\"]).optional(),\n contextfit: ContextFitConfigSchema.optional(),\n embedding_model: z.string().optional(),\n secondary_embedding_model: z.string().optional(),\n write_enabled: z.boolean().optional(),\n exclude_globs: z.array(z.string()).optional(),\n});\n\n/**\n * Phase 5 / D-10 ladder tier 2: per-vault Ollama brief-compile config.\n *\n * `[brief.ollama] model = \"...\"` opts the vault into Tier 2 of the\n * capability-first LLM ladder. The MCP Sampling tier (Tier 1) is\n * checked first per-call; this block is only consulted when Sampling\n * is not available. Strictly localhost (existing OllamaClient binds\n * to `http://localhost:11434`).\n *\n * Schema is OPTIONAL: backwards-compatible. Existing v1.x configs\n * without `[brief]` still parse identically; the ladder simply\n * skips Tier 2 and tries Tier 3 (`prepared_text`) → Tier 4\n * (structured error). See ADR-005 §\"Capability-first LLM ladder\".\n */\nconst BriefOllamaConfigSchema = z.object({\n model: z.string().min(1),\n});\nconst BriefConfigSchema = z.object({\n ollama: BriefOllamaConfigSchema.optional(),\n});\n\n/**\n * Phase 6 / ADR-006 §Decision 1: `[contracts]` block (per-vault gate).\n *\n * Backwards-compatible: a config.toml with no `[contracts]` block parses\n * to the documented defaults via `.optional().default(...)` at the\n * AppConfigSchema attach site.\n *\n * Trust scope (T-06-01-04 disposition: accept): `mcp_clients..command`\n * is the same trust level as the rest of `~/.vault-memory/config.toml`\n * (user-owned). Plan 06-03 uses `child_process.spawn(command, args)` with\n * NO shell — args pass verbatim. Documented in ADR-006 §Threat Model.\n */\nconst ContractsMcpClientConfigSchema = z.object({\n command: z.string().min(1).describe(\"Peer MCP server executable path\"),\n args: z.array(z.string()).optional(),\n env: z.record(z.string(), z.string()).optional(),\n});\n\nconst ContractsConfigSchema = z.object({\n auto_register_tools: z\n .boolean()\n .default(false)\n .describe(\"D-A1b — per-vault gate for auto-registering contracts as MCP Tools\"),\n tool_prefix: z\n .string()\n .min(1)\n .regex(/^[a-z_][a-z0-9_]*$/)\n .default(\"vm_\")\n .describe(\"D-A1c — slug prefix for auto-registered tool names; A7 enforces non-empty\"),\n step_timeout_seconds: z\n .number()\n .int()\n .positive()\n .default(30)\n .describe(\n \"Q-TIMEOUT — applied only to peer-MCP verbs (baseline verbs use their own discipline)\",\n ),\n defaults: z\n .record(z.string(), z.string())\n .default({})\n .describe(\"D-A4b — default chain step 2: handle → URI fallback\"),\n mcp_clients: z\n .record(z.string(), ContractsMcpClientConfigSchema)\n .default({})\n .describe(\"D-A2a — peer MCP clients vault-memory connects to as an MCP client\"),\n});\n\nconst DEFAULT_CONTRACTS_CONFIG = {\n auto_register_tools: false,\n tool_prefix: \"vm_\",\n step_timeout_seconds: 30,\n defaults: {},\n mcp_clients: {},\n} as const;\n\n/**\n * Phase 7 / Plan 07-04 / D-MCP-SURFACE: `[plugin]` block.\n *\n * Single field for v2.0.0: `enabled` — gates the five plugin-control MCP tools\n * (`set_runtime_config`, `resolve_secret`, `set_mcp_client`, `get_runtime_stats`,\n * `trigger_reindex`). Default OFF preserves v1 tools-list snapshot stability\n * (REL-08 ≤32-tool budget for non-plugin deployments).\n *\n * Backwards-compatible: configs without `[plugin]` resolve to\n * `DEFAULT_PLUGIN_CONFIG` via `.optional().default(...)` at the AppConfigSchema\n * attach site.\n */\nconst PluginConfigSchema = z.object({\n enabled: z\n .boolean()\n .default(false)\n .describe(\n \"D-MCP-SURFACE — gates the 5 plugin-control MCP tools (set_runtime_config, resolve_secret, set_mcp_client, get_runtime_stats, trigger_reindex). Default OFF preserves v1 tools-list snapshot stability per REL-08.\",\n ),\n});\n\nconst DEFAULT_PLUGIN_CONFIG = { enabled: false } as const;\n\n// Phase 2: optional [memory] and [[memory_sinks]] blocks.\n//\n// The handle string is intentionally NOT validated against\n// MEMORY_SINK_HANDLE_PATTERN here — the brand-cast (and resulting\n// throw on malformed input) happens in `MemorySinkRegistry`. Keeping\n// the config loader free of `src/memory/*` imports preserves the\n// ADR-002 layering (config is infrastructure; memory is a domain\n// module that depends on config, not the other way around).\nconst MemorySinkConfigSchema = z.object({\n name: z.string().min(1),\n handle: z.string().min(1),\n contract: z.string().min(1).default(\"default-memory-v1\"),\n});\n\nconst MemoryConfigSchema = z.object({\n default_sink: z.string().min(1).optional(),\n});\n\nconst AppConfigSchema = z.object({\n server: ServerConfigSchema.optional().default({}),\n vaults: z.array(VaultConfigSchema).optional().default([]),\n memory: MemoryConfigSchema.optional(),\n memory_sinks: z.array(MemorySinkConfigSchema).optional().default([]),\n // Phase 5 / D-10 tier 2 (ADR-005). Backwards-compatible: existing\n // configs without `[brief]` parse identically.\n brief: BriefConfigSchema.optional(),\n // Phase 6 / ADR-006 §Decision 1. Backwards-compatible: configs without\n // `[contracts]` resolve to DEFAULT_CONTRACTS_CONFIG.\n contracts: ContractsConfigSchema.optional().default(DEFAULT_CONTRACTS_CONFIG),\n // Phase 7 / Plan 07-04 / D-MCP-SURFACE. Backwards-compatible: configs\n // without `[plugin]` resolve to DEFAULT_PLUGIN_CONFIG (enabled: false).\n plugin: PluginConfigSchema.optional().default(DEFAULT_PLUGIN_CONFIG),\n});\n\nconst DEFAULT_CONFIG: AppConfig = {\n server: {\n log_level: \"info\",\n ollama_endpoint: \"http://localhost:11434\",\n default_embedding_model: \"qwen3-embedding\",\n },\n vaults: [],\n memory_sinks: [],\n contracts: { ...DEFAULT_CONTRACTS_CONFIG },\n plugin: { ...DEFAULT_PLUGIN_CONFIG },\n};\n\nexport function configPath(): string {\n return join(homedir(), \".vault-memory\", \"config.toml\");\n}\n\nexport async function loadConfig(path: string = configPath()): Promise {\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n return DEFAULT_CONFIG;\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = parseToml(raw);\n } catch (err) {\n throw new Error(`Failed to parse TOML at ${path}: ${(err as Error).message}`);\n }\n\n const validated = AppConfigSchema.parse(parsed);\n\n return {\n server: {\n ...DEFAULT_CONFIG.server,\n ...validated.server,\n },\n vaults: validated.vaults,\n memory: validated.memory,\n // Phase 5 / ADR-005 §\"Sub-folder MemorySink ordering\": sort the\n // memory_sinks array by path-specificity (longest resource first)\n // so `MemorySinkRegistry.findSinkContaining` (startsWith over\n // insertion order, src/memory/registry.ts:190-202) resolves\n // sub-folder sinks BEFORE their parents. Concretely:\n // `_memory/_briefs/` MUST be registered before `_memory/` so a\n // brief write routes into the brief-specific sink (bound to\n // `default-brief-v1`, accepts `status: \"stale\"`) instead of the\n // parent (bound to `default-memory-v1`, rejects `\"stale\"`).\n memory_sinks: sortSinksByPathSpecificity(validated.memory_sinks),\n brief: validated.brief,\n contracts: validated.contracts,\n plugin: validated.plugin,\n };\n}\n\n/**\n * Phase 5: sort `[[memory_sinks]]` so more-specific paths come first.\n *\n * The `handle` shape is `:///` (ADR-001\n * URI form). Path-specificity is measured by the length of the\n * `` portion — longer resources are more specific and MUST\n * register first. Comparator is stable (Array.prototype.sort is\n * stable in V8 ≥ Node 12); equal-length resources preserve their\n * declaration order.\n *\n * Pitfall 1 mitigation (ADR-005): without this normalization, a TOML\n * that declares `_memory/` before `_memory/_briefs/` would route\n * brief writes through the parent sink's `default-memory-v1`\n * contract, which rejects `status: \"stale\"`.\n */\nfunction sortSinksByPathSpecificity(sinks: T[]): T[] {\n // Compute the resource length once per sink — avoids re-parsing\n // inside the comparator (n*log(n) calls).\n type Tagged = { sink: T; resourceLength: number; order: number };\n const tagged: Tagged[] = sinks.map((s, i) => ({\n sink: s,\n resourceLength: extractResourceLength(s.handle),\n order: i,\n }));\n tagged.sort((a, b) => {\n // Primary: longer resource (more specific) first.\n if (a.resourceLength !== b.resourceLength) {\n return b.resourceLength - a.resourceLength;\n }\n // Secondary: preserve declaration order on ties (defensive — V8\n // sort is already stable but the explicit tie-breaker documents\n // the intent).\n return a.order - b.order;\n });\n return tagged.map((t) => t.sink);\n}\n\n/**\n * Extract the `` portion length from a `:///`\n * handle. Returns 0 for malformed handles — they fall to the bottom\n * of the sorted list, which is harmless because malformed handles\n * are caught downstream by `parseMemorySinkHandle` in\n * `src/memory/sink.ts`.\n */\nfunction extractResourceLength(handle: string): number {\n const schemeEnd = handle.indexOf(\"://\");\n if (schemeEnd === -1) return 0;\n const afterScheme = handle.slice(schemeEnd + 3);\n const firstSlash = afterScheme.indexOf(\"/\");\n if (firstSlash === -1) return 0;\n return afterScheme.length - (firstSlash + 1);\n}\n","/**\n * Atomically add a new vault to vault-memory:\n * 1. Validate the path is a directory and not already registered.\n * 2. Append a [[vaults]] block to ~/.vault-memory/config.toml.\n * 3. Write/merge .mcp.json in the vault root so an MCP-aware client\n * (e.g. ChatGPT Custom Connectors, Claude Desktop, or any other // vault-memory:claude-ok\n * stdio MCP host) can spawn the MCP server when the user opens\n * the vault.\n *\n * This is the source of truth for \"onboard a new vault\" — invoked by both\n * the CLI `add-vault` subcommand and the `/add-vault` skill bundled in\n * `skills/`.\n *\n * Idempotent: re-running with the same path is a no-op for config.toml\n * and a merge for .mcp.json (vault-memory entry under mcpServers gets\n * its env updated if the active-vault flag changed, other servers stay\n * untouched).\n */\n\nimport { promises as fs } from \"node:fs\";\nimport { join, basename, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { loadConfig, configPath } from \"./loader.js\";\n\nexport interface AddVaultOptions {\n /** Absolute path to the Obsidian vault root. */\n path: string;\n /** Optional explicit name. Defaults to slugified basename(path). */\n name?: string;\n /** Whether the MCP server may write to this vault. Default false (safer). */\n writeEnabled?: boolean;\n /** ADR-008: retrieval engine. \"contextfit\" = CPU-only token-native engine\n * (no Ollama/GPU). Omitted/\"ollama\" = the embeddings+sqlite-vec default. */\n backend?: \"ollama\" | \"contextfit\";\n /** Custom exclude_globs. Default = sensible Obsidian-system folders. */\n excludeGlobs?: string[];\n /** Custom config.toml path (testing). */\n configFile?: string;\n /** Custom binary command for .mcp.json (default \"vault-memory\"). */\n binary?: string;\n}\n\nexport type AddVaultStep =\n | { kind: \"config-added\"; name: string; path: string }\n | { kind: \"config-already-registered\"; name: string; existingPath: string }\n | { kind: \"mcp-json-created\"; mcpPath: string }\n | { kind: \"mcp-json-merged\"; mcpPath: string }\n | { kind: \"mcp-json-unchanged\"; mcpPath: string };\n\nexport interface AddVaultResult {\n /** Resolved vault name as it appears in config.toml. */\n name: string;\n /** Absolute, normalised vault path. */\n resolvedPath: string;\n /** Where in config.toml the vault is registered. */\n configFile: string;\n /** Where the .mcp.json was written. */\n mcpJsonPath: string;\n /** Per-step transcript so callers can render a status report. */\n steps: AddVaultStep[];\n}\n\nconst DEFAULT_EXCLUDE_GLOBS = [\n \".obsidian/**\",\n \".trash/**\",\n \"Trash/**\",\n \".claude/**\", // vault-memory:claude-ok — `.claude/` is the literal Obsidian-side directory name for any MCP host integration; not a Claude-only path.\n \".smart-connections/**\",\n \".smart-env/**\",\n \".systemsculpt/**\",\n \".makemd/**\",\n];\n\n/**\n * Slugify a vault basename for use as a vault `name`:\n * - lowercase\n * - non-alnum (except dash) → dash\n * - collapse repeats, trim leading/trailing dashes\n *\n * Names must satisfy: ^[a-z0-9][a-z0-9-]*$ (becomes the SQLite DB filename).\n */\nexport function slugifyVaultName(input: string): string {\n const cleaned = input\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n if (cleaned.length === 0) return \"vault\";\n if (/^[0-9]/.test(cleaned)) return `v-${cleaned}`;\n return cleaned;\n}\n\nexport async function addVault(opts: AddVaultOptions): Promise {\n const resolvedPath = resolve(opts.path);\n const cfgFile = opts.configFile ?? configPath();\n const binary = opts.binary ?? \"vault-memory\";\n const steps: AddVaultStep[] = [];\n\n // 1. Validate the vault path exists and is a directory.\n const stat = await fs.stat(resolvedPath).catch((err) => {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new Error(`Vault path does not exist: ${resolvedPath}`);\n }\n throw err;\n });\n if (!stat.isDirectory()) {\n throw new Error(`Vault path is not a directory: ${resolvedPath}`);\n }\n\n // 2. Determine the canonical name.\n const proposedName = opts.name ?? slugifyVaultName(basename(resolvedPath));\n if (!/^[a-z0-9][a-z0-9-]*$/.test(proposedName)) {\n throw new Error(\n `Vault name \"${proposedName}\" must match /^[a-z0-9][a-z0-9-]*$/ ` +\n `(lowercase alphanumeric + dashes, starting with a letter or digit).`,\n );\n }\n\n // 3. Read existing config to check for duplicates.\n const existing = await loadConfig(cfgFile);\n const sameName = existing.vaults.find((v) => v.name === proposedName);\n const samePath = existing.vaults.find((v) => resolve(v.path) === resolvedPath);\n\n if (samePath) {\n steps.push({\n kind: \"config-already-registered\",\n name: samePath.name,\n existingPath: samePath.path,\n });\n } else if (sameName) {\n throw new Error(\n `A different vault is already registered under name \"${proposedName}\" ` +\n `(path: ${sameName.path}). Pass --name to choose a different one.`,\n );\n } else {\n // Append a new [[vaults]] block. We do not re-stringify the whole\n // config — that would discard user comments. Append-only is safer.\n const block = renderVaultBlock({\n name: proposedName,\n path: resolvedPath,\n writeEnabled: opts.writeEnabled ?? false,\n excludeGlobs: opts.excludeGlobs ?? DEFAULT_EXCLUDE_GLOBS,\n ...(opts.backend ? { backend: opts.backend } : {}),\n });\n await ensureFileExists(cfgFile);\n await appendToFile(cfgFile, block);\n steps.push({ kind: \"config-added\", name: proposedName, path: resolvedPath });\n }\n\n const finalName = samePath?.name ?? proposedName;\n\n // 4. Write/merge .mcp.json in the vault.\n const mcpPath = join(resolvedPath, \".mcp.json\");\n const step = await writeOrMergeMcpJson(mcpPath, finalName, binary);\n steps.push(step);\n\n return {\n name: finalName,\n resolvedPath,\n configFile: cfgFile,\n mcpJsonPath: mcpPath,\n steps,\n };\n}\n\ninterface VaultBlockInput {\n name: string;\n path: string;\n writeEnabled: boolean;\n excludeGlobs: string[];\n /** ADR-008: retrieval engine. Only emitted when \"contextfit\" (ollama is the\n * implicit default and is left out for back-compat clean configs). */\n backend?: \"ollama\" | \"contextfit\";\n}\n\nfunction renderVaultBlock(input: VaultBlockInput): string {\n // Hand-rolled TOML so we control formatting + comments.\n const lines: string[] = [\n \"\",\n `# Added by vault-memory add-vault on ${new Date().toISOString()}`,\n \"[[vaults]]\",\n `name = ${JSON.stringify(input.name)}`,\n `path = ${JSON.stringify(input.path)}`,\n ];\n if (input.backend === \"contextfit\") {\n lines.push(\n `# ADR-008: CPU-only, token-native engine (no Ollama/embeddings/GPU).`,\n `backend = \"contextfit\"`,\n );\n }\n lines.push(\n `write_enabled = ${input.writeEnabled}`,\n `exclude_globs = [`,\n ...input.excludeGlobs.map((g) => ` ${JSON.stringify(g)},`),\n `]`,\n \"\",\n );\n return lines.join(\"\\n\");\n}\n\nasync function ensureFileExists(path: string): Promise {\n try {\n await fs.access(path);\n } catch {\n await fs.mkdir(join(homedir(), \".vault-memory\"), { recursive: true });\n await fs.writeFile(path, \"# vault-memory configuration\\n\", \"utf-8\");\n }\n}\n\nasync function appendToFile(path: string, content: string): Promise {\n await fs.appendFile(path, content, \"utf-8\");\n}\n\ninterface McpServerEntry {\n type?: string;\n command?: string;\n args?: string[];\n env?: Record;\n}\ninterface McpJsonShape {\n mcpServers?: Record;\n}\n\nasync function writeOrMergeMcpJson(\n mcpPath: string,\n vaultName: string,\n binary: string,\n): Promise {\n const desiredEntry: McpServerEntry = {\n type: \"stdio\",\n command: binary,\n args: [\"serve\"],\n env: { VAULT_MEMORY_ACTIVE_VAULT: vaultName },\n };\n\n let existing: McpJsonShape | null = null;\n try {\n const raw = await fs.readFile(mcpPath, \"utf-8\");\n existing = JSON.parse(raw) as McpJsonShape;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n throw new Error(`Failed to read existing .mcp.json at ${mcpPath}: ${(err as Error).message}`);\n }\n }\n\n if (existing === null) {\n const fresh: McpJsonShape = { mcpServers: { \"vault-memory\": desiredEntry } };\n await fs.writeFile(mcpPath, JSON.stringify(fresh, null, 2) + \"\\n\", \"utf-8\");\n return { kind: \"mcp-json-created\", mcpPath };\n }\n\n // Merge: keep other servers untouched, replace/insert vault-memory.\n const before = existing.mcpServers?.[\"vault-memory\"];\n const beforeJson = before ? JSON.stringify(before) : null;\n const merged: McpJsonShape = {\n ...existing,\n mcpServers: {\n ...(existing.mcpServers ?? {}),\n \"vault-memory\": desiredEntry,\n },\n };\n const afterJson = JSON.stringify(merged.mcpServers?.[\"vault-memory\"]);\n if (beforeJson === afterJson) {\n return { kind: \"mcp-json-unchanged\", mcpPath };\n }\n await fs.writeFile(mcpPath, JSON.stringify(merged, null, 2) + \"\\n\", \"utf-8\");\n return { kind: \"mcp-json-merged\", mcpPath };\n}\n","export { loadConfig, configPath } from \"./loader.js\";\nexport { addVault, slugifyVaultName } from \"./add-vault.js\";\nexport type { AddVaultOptions, AddVaultResult, AddVaultStep } from \"./add-vault.js\";\n","/**\n * RuntimeConfigStore — Phase 7 / Plan 07-04 / PLG-01, ADR-007 §D-CHROME-SETTINGS.\n *\n * In-memory mirror of selected `AppConfig` knobs that can be hot-swapped at\n * runtime without restarting the server. The CONFIG FILE\n * (`~/.vault-memory/config.toml`) remains the authoritative source of record\n * across restarts — this store is intentionally NOT persisted. Restarting the\n * server reverts every hot-swap to the on-disk value.\n *\n * Closed enum of hot-swappable keys (RESEARCH Open Q #1, RESOLVED):\n * - reranker_enabled (boolean) — toggles `vault.config` rerank gate in-memory\n * - default_vault (string) — overrides `VAULT_MEMORY_ACTIVE_VAULT`\n * - indexer_batch_size (number) — informational; consulted by next indexVault call\n *\n * Restart-required keys are surfaced via `RESTART_REQUIRED_KEYS` and produce\n * a structured `{ok: false, reason: \"restart_required\", key}` response in\n * the `set_runtime_config` tool — no mutation occurs.\n *\n * # Adapter-seam discipline\n *\n * Pure in-memory key-value store. Zero `fs` / `path` / `yaml` / `chokidar`\n * imports. Zod schemas live in the consuming tool file; this module is just\n * the store.\n */\n\nexport const HOT_SWAPPABLE_KEYS = [\n \"reranker_enabled\",\n \"default_vault\",\n \"indexer_batch_size\",\n] as const;\n\nexport type HotSwappableKey = (typeof HOT_SWAPPABLE_KEYS)[number];\n\nexport const RESTART_REQUIRED_KEYS = [\"ollama_url\", \"embedding_model\", \"fts_tokenizer\"] as const;\n\nexport type RestartRequiredKey = (typeof RESTART_REQUIRED_KEYS)[number];\n\nexport type RuntimeConfigValue = boolean | string | number;\n\nexport interface RuntimeConfigSnapshot {\n reranker_enabled?: boolean;\n default_vault?: string;\n indexer_batch_size?: number;\n}\n\n/**\n * In-memory store. The owning module (typically `src/server.ts` bootstrap)\n * constructs ONE instance, seeds it with the initial on-disk values, and\n * threads it into each tool handler's dependency bag.\n */\nexport class RuntimeConfigStore {\n private values: RuntimeConfigSnapshot;\n\n constructor(initial?: RuntimeConfigSnapshot) {\n this.values = { ...(initial ?? {}) };\n }\n\n /** Read a single hot-swappable value, or `undefined` if never set. */\n get(key: K): RuntimeConfigSnapshot[K] {\n return this.values[key];\n }\n\n /** Read the full snapshot (immutable copy). */\n snapshot(): RuntimeConfigSnapshot {\n return { ...this.values };\n }\n\n /** Write a hot-swappable value. Caller is responsible for type validation. */\n set(key: K, value: RuntimeConfigSnapshot[K]): void {\n this.values[key] = value;\n }\n}\n\n/** True iff `key` is in the closed hot-swappable enum. */\nexport function isHotSwappableKey(key: string): key is HotSwappableKey {\n return (HOT_SWAPPABLE_KEYS as readonly string[]).includes(key);\n}\n\n/** True iff `key` is in the closed restart-required enum. */\nexport function isRestartRequiredKey(key: string): key is RestartRequiredKey {\n return (RESTART_REQUIRED_KEYS as readonly string[]).includes(key);\n}\n","/**\n * set_runtime_config — Phase 7 / Plan 07-04 / PLG-01, ADR-007 §D-CHROME-SETTINGS.\n *\n * Per-key runtime settings tool. Applies hot-swappable settings to the\n * in-memory `RuntimeConfigStore` ONLY — the on-disk `~/.vault-memory/config.toml`\n * is authoritative across restarts and is never mutated by this tool. Server\n * restart reverts hot-swaps to the file values (this is intentional; see PLG-01\n * §\"Hot-swap semantics\").\n *\n * Closed enum of allowed keys (RESEARCH Open Q #1, RESOLVED):\n * - reranker_enabled (boolean)\n * - default_vault (string)\n * - indexer_batch_size (number, positive integer)\n *\n * Restart-required keys (`ollama_url`, `embedding_model`, `fts_tokenizer`)\n * return `{ok: false, reason: \"restart_required\", key}` without mutating.\n * Unknown keys return `{ok: false, reason: \"unknown_key\", key}`.\n *\n * # Adapter-seam discipline\n *\n * Imports only `zod` + sibling `runtime-config.js` / `errors.js`. Zero `fs`,\n * `path`, `yaml`, `chokidar`, MCP SDK. The MCP SDK wiring happens in\n * `src/plugin-tools/index.ts`.\n */\n\nimport { z } from \"zod\";\nimport {\n RuntimeConfigStore,\n HOT_SWAPPABLE_KEYS,\n isHotSwappableKey,\n isRestartRequiredKey,\n} from \"./runtime-config.js\";\n\nconst SetRuntimeConfigArgs = z.object({\n key: z\n .string()\n .min(1)\n .describe(\n \"Closed enum of hot-swappable keys: \" +\n `${HOT_SWAPPABLE_KEYS.join(\", \")}. Restart-required keys ` +\n \"(ollama_url, embedding_model, fts_tokenizer) return reason='restart_required'.\",\n ),\n value: z\n .union([z.boolean(), z.string(), z.number()])\n .describe(\n \"New value. Type must match the key: reranker_enabled = boolean, \" +\n \"default_vault = string, indexer_batch_size = positive integer.\",\n ),\n});\n\nexport type SetRuntimeConfigInput = z.infer;\n\nexport interface SetRuntimeConfigDeps {\n store: RuntimeConfigStore;\n}\n\nexport type SetRuntimeConfigResult =\n | { ok: true; key: string; value: boolean | string | number }\n | { ok: false; reason: \"unknown_key\"; key: string }\n | { ok: false; reason: \"restart_required\"; key: string }\n | { ok: false; reason: \"type_mismatch\"; key: string; expected: string };\n\nasync function handler(\n args: SetRuntimeConfigInput,\n deps: SetRuntimeConfigDeps,\n): Promise {\n const { key, value } = args;\n\n if (isRestartRequiredKey(key)) {\n return { ok: false, reason: \"restart_required\", key };\n }\n if (!isHotSwappableKey(key)) {\n return { ok: false, reason: \"unknown_key\", key };\n }\n\n // Per-key type-narrow validation. Zod already constrained `value` to\n // boolean | string | number; this layer enforces the per-key expected\n // type (e.g. reranker_enabled must be boolean, not \"true\" string).\n switch (key) {\n case \"reranker_enabled\": {\n if (typeof value !== \"boolean\") {\n return { ok: false, reason: \"type_mismatch\", key, expected: \"boolean\" };\n }\n deps.store.set(\"reranker_enabled\", value);\n return { ok: true, key, value };\n }\n case \"default_vault\": {\n if (typeof value !== \"string\") {\n return { ok: false, reason: \"type_mismatch\", key, expected: \"string\" };\n }\n deps.store.set(\"default_vault\", value);\n return { ok: true, key, value };\n }\n case \"indexer_batch_size\": {\n if (typeof value !== \"number\" || !Number.isInteger(value) || value <= 0) {\n return {\n ok: false,\n reason: \"type_mismatch\",\n key,\n expected: \"positive integer\",\n };\n }\n deps.store.set(\"indexer_batch_size\", value);\n return { ok: true, key, value };\n }\n }\n}\n\nexport const setRuntimeConfigTool = {\n name: \"set_runtime_config\" as const,\n description:\n \"Apply a hot-swappable runtime config key (in-memory only — config.toml \" +\n \"remains authoritative across restarts). Closed enum of keys: \" +\n `${HOT_SWAPPABLE_KEYS.join(\", \")}. ADR-007 §D-CHROME-SETTINGS.`,\n inputSchema: SetRuntimeConfigArgs,\n handler,\n};\n","/**\n * resolve_secret — Phase 7 / Plan 07-04 / PLG-02, ADR-007 §D-CHROME-SECRETS.\n *\n * Receives plaintext from the plugin (which decrypted it via Electron\n * `safeStorage.decryptString(...)` inside the Obsidian renderer process) and\n * makes it available to the server-side `${secret:name}` substitution layer.\n *\n * Architectural rationale (RESEARCH §\"Architectural Responsibility Map\"):\n * `safeStorage` is an Electron-renderer API only reachable inside the\n * Obsidian process. The plugin owns ciphertext storage in `data.json` (per-\n * device ciphertext is the correct security posture per CONTEXT\n * D-CHROME-SECRETS); the server tool merely consumes the plaintext for\n * substitution and never logs it.\n *\n * Input shape:\n * {name: string, ciphertext: string} — plugin succeeded; field\n * carries plaintext-of-this-call\n * {name: string, error: \"safe_storage_unavailable\" | \"decrypt_failed\"}\n * — plugin reports decryption failure\n *\n * Output:\n * {ok: true, plaintext: string} — success\n * {ok: false, reason: \"safe_storage_unavailable\", name} — OS keyring missing\n * {ok: false, reason: \"decrypt_failed\", name} — other failure\n *\n * SECURITY: response payload contains plaintext only — handler MUST NOT\n * include `name` in any log line at level >= info; debug-level logging must\n * redact the plaintext. Source-file scan in `resolve-secret.test.ts` enforces\n * that no logging statement references the secret value.\n *\n * # Adapter-seam discipline\n *\n * Imports only `zod` + sibling `errors.js`. Zero `fs`, `path`, `yaml`,\n * `chokidar`, MCP SDK, Electron.\n */\n\nimport { z } from \"zod\";\n\n/**\n * Raw object shape (no `.refine`). Exposed separately so the MCP SDK\n * `registerTool(..., {inputSchema: ResolveSecretShape})` accepts a\n * ZodRawShapeCompat instead of a `ZodEffects` (which the SDK rejects).\n * The refined schema (`ResolveSecretArgs`) layers a cross-field check on\n * top and is used inside the handler for runtime validation.\n */\nexport const ResolveSecretShape = {\n name: z\n .string()\n .min(1)\n .describe(\"Secret identifier referenced as `${secret:name}` in a contract.\"),\n ciphertext: z\n .string()\n .optional()\n .describe(\n \"Plaintext-of-this-call (the plugin has already decrypted ciphertext \" +\n \"in-process via safeStorage). Field name preserved for provenance.\",\n ),\n error: z\n .enum([\"safe_storage_unavailable\", \"decrypt_failed\"])\n .optional()\n .describe(\n \"Plugin-side failure indicator. `safe_storage_unavailable` means \" +\n \"the OS keyring backend was missing; `decrypt_failed` covers any \" +\n \"other plugin-side decryption failure.\",\n ),\n} as const;\n\nconst ResolveSecretArgs = z\n .object(ResolveSecretShape)\n .refine((v) => v.ciphertext !== undefined || v.error !== undefined, {\n message: \"must provide either `ciphertext` or `error`\",\n });\n\nexport type ResolveSecretInput = z.infer;\n\nexport type ResolveSecretResult =\n | { ok: true; plaintext: string }\n | { ok: false; reason: \"safe_storage_unavailable\"; name: string }\n | { ok: false; reason: \"decrypt_failed\"; name: string };\n\nasync function handler(args: ResolveSecretInput): Promise {\n if (args.error !== undefined) {\n return { ok: false, reason: args.error, name: args.name };\n }\n if (args.ciphertext === undefined) {\n // Defensive: Zod refine should have caught this; preserve a typed\n // fall-through so the discriminated-union remains exhaustive.\n return { ok: false, reason: \"decrypt_failed\", name: args.name };\n }\n // SECURITY: do not log or stringify `args.ciphertext` here.\n return { ok: true, plaintext: args.ciphertext };\n}\n\nexport const resolveSecretTool = {\n name: \"resolve_secret\" as const,\n description:\n \"Resolve a secret to plaintext for ${secret:name} substitution. The plugin \" +\n \"decrypts ciphertext in-process via Electron safeStorage; this tool consumes \" +\n \"the plaintext and never logs it. ADR-007 §D-CHROME-SECRETS.\",\n inputSchema: ResolveSecretArgs,\n handler,\n};\n","/**\n * set_mcp_client — Phase 7 / Plan 07-04 / PLG-05, ADR-007 §D-CHROME-CONNECTORS.\n *\n * CRUD for `[contracts.mcp_clients.]` blocks in\n * `~/.vault-memory/config.toml`. Discriminated-union input:\n *\n * Variant A (add/update): {name, command, args?, env_secrets?}\n * - mutates [contracts.mcp_clients.]; idempotent\n * - returns {ok: true, name, action: \"added\" | \"updated\"}\n *\n * Variant B (remove): {name, remove: true}\n * - deletes the entry; idempotent (no-op if absent)\n * - returns {ok: true, name, action: \"removed\"}\n *\n * Variant C (list): {list: true}\n * - reads inventory; returns key-list of env_secrets (no values)\n * - returns {ok: true, clients: Array<{name, command, args, env_secrets, status?}>}\n *\n * In the list response, `env_secrets` is a key-list ONLY (no values, no\n * ciphertext) — values stay in plugin storage; the server only knows the key\n * names that will be substituted at connect time.\n *\n * # Adapter-seam discipline\n *\n * Imports `zod`, `smol-toml`, and node:fs/promises. The config-file mutator\n * is the ONLY plugin-tool that writes to `~/.vault-memory/config.toml`;\n * justified by D-CHROME-CONNECTORS (the connector list is the user-visible\n * source of truth, hot-swap would orphan running peer-MCP clients).\n */\n\nimport { z } from \"zod\";\nimport { parse as parseToml, stringify as stringifyToml } from \"smol-toml\";\nimport { readFile, writeFile } from \"node:fs/promises\";\n\n/**\n * Raw object shape exposed to the MCP SDK (`registerTool({inputSchema})`).\n * The SDK 1.29 input-schema slot accepts a `ZodRawShapeCompat` — a plain\n * object whose properties are Zod schemas. We can't directly hand it a\n * `z.union(...)` because the discriminator decision is per-call, so the\n * shape is union-relaxed: every field is optional at the schema level and\n * the cross-field invariant is enforced by the refined union below\n * (`SetMcpClientArgs`) which the handler re-parses with.\n */\nexport const SetMcpClientShape = {\n name: z.string().min(1).optional().describe(\"Client name (required for Variants A and B).\"),\n command: z\n .string()\n .min(1)\n .optional()\n .describe(\"Executable path. Required for Variant A (add/update).\"),\n args: z.array(z.string()).optional().describe(\"Argv tail for child_process.spawn (Variant A).\"),\n env_secrets: z\n .record(z.string(), z.string())\n .optional()\n .describe(\n \"Map of ENV_NAME → secret-key-name (Variant A). Values resolved via \" +\n \"resolve_secret at connect time; this map carries key names only.\",\n ),\n remove: z\n .literal(true)\n .optional()\n .describe(\"Variant B trigger — set true together with `name` to delete.\"),\n list: z\n .literal(true)\n .optional()\n .describe(\"Variant C trigger — set true to read [contracts.mcp_clients] inventory.\"),\n} as const;\n\nconst SetMcpClientArgs = z.union([\n // Variant A — add/update\n z.object({\n name: z.string().min(1).describe(\"Peer-MCP client name (used as TOML table key).\"),\n command: z\n .string()\n .min(1)\n .describe(\"Executable path. Same trust scope as ~/.vault-memory/config.toml.\"),\n args: z.array(z.string()).optional().describe(\"Argv tail for child_process.spawn.\"),\n env_secrets: z\n .record(z.string(), z.string())\n .optional()\n .describe(\n \"Map of ENV_NAME → secret-key-name. Values are looked up via \" +\n \"resolve_secret at connect time; this map carries key names only.\",\n ),\n }),\n // Variant B — remove\n z.object({\n name: z.string().min(1).describe(\"Client name to remove.\"),\n remove: z.literal(true).describe(\"Set to true to delete the entry.\"),\n }),\n // Variant C — list (inventory)\n z.object({\n list: z.literal(true).describe(\"Set to true to read [contracts.mcp_clients] inventory.\"),\n }),\n]);\n\nexport type SetMcpClientInput = z.infer;\n\nexport interface SetMcpClientDeps {\n /** Path to config.toml. Defaults to `~/.vault-memory/config.toml`. */\n configPath: string;\n}\n\nexport interface McpClientInventoryEntry {\n name: string;\n command: string;\n args: string[];\n env_secrets: string[];\n status?: \"connected\" | \"disconnected\" | \"untested\";\n}\n\nexport type SetMcpClientResult =\n | { ok: true; name: string; action: \"added\" | \"updated\" | \"removed\" }\n | { ok: true; clients: McpClientInventoryEntry[] };\n\ntype TomlRoot = Record & {\n contracts?: { mcp_clients?: Record } & Record;\n};\n\ninterface McpClientTomlEntry {\n command?: string;\n args?: string[];\n env?: Record;\n env_secrets?: Record;\n}\n\nasync function readConfig(configPath: string): Promise {\n try {\n const raw = await readFile(configPath, \"utf-8\");\n return parseToml(raw) as TomlRoot;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") return {};\n throw err;\n }\n}\n\nasync function writeConfig(configPath: string, root: TomlRoot): Promise {\n // smol-toml stringify is total over JSON-serializable values; the round-trip\n // here is parse → mutate → stringify, which preserves field types (TOML\n // strings stay strings, booleans stay booleans, integers stay integers).\n // Comments and blank lines are NOT preserved — this is documented in the\n // ADR-007 threat model under \"TOML round-trip side effects\".\n await writeFile(configPath, stringifyToml(root), \"utf-8\");\n}\n\nasync function handler(\n args: SetMcpClientInput,\n deps: SetMcpClientDeps,\n): Promise {\n // Variant C — list\n if (\"list\" in args) {\n const root = await readConfig(deps.configPath);\n const map = root.contracts?.mcp_clients ?? {};\n const clients: McpClientInventoryEntry[] = Object.entries(map).map(([name, entry]) => ({\n name,\n command: entry.command ?? \"\",\n args: entry.args ?? [],\n // SECURITY: emit key-list only — values stay in plugin storage.\n env_secrets: Object.keys(entry.env_secrets ?? {}),\n }));\n return { ok: true, clients };\n }\n\n const root = await readConfig(deps.configPath);\n if (root.contracts === undefined) root.contracts = {};\n // We control the shape; cast to a mutable record for the local mutation.\n const contracts = root.contracts as { mcp_clients?: Record };\n if (contracts.mcp_clients === undefined) contracts.mcp_clients = {};\n const clients = contracts.mcp_clients;\n\n // Variant B — remove\n if (\"remove\" in args) {\n if (args.name in clients) {\n delete clients[args.name];\n await writeConfig(deps.configPath, root);\n } else {\n // Idempotent — nothing to write, but still report success.\n }\n return { ok: true, name: args.name, action: \"removed\" };\n }\n\n // Variant A — add/update\n const existing = clients[args.name];\n const entry: McpClientTomlEntry = {\n command: args.command,\n };\n if (args.args !== undefined) entry.args = args.args;\n if (args.env_secrets !== undefined) entry.env_secrets = args.env_secrets;\n clients[args.name] = entry;\n await writeConfig(deps.configPath, root);\n return {\n ok: true,\n name: args.name,\n action: existing === undefined ? \"added\" : \"updated\",\n };\n}\n\nexport const setMcpClientTool = {\n name: \"set_mcp_client\" as const,\n description:\n \"Manage [contracts.mcp_clients] in ~/.vault-memory/config.toml. \" +\n \"Variant A: add/update (name + command [+ args, env_secrets]). \" +\n \"Variant B: remove (name + remove:true). \" +\n \"Variant C: list (list:true — inventory, env_secrets is key-list only). \" +\n \"ADR-007 §D-CHROME-CONNECTORS.\",\n inputSchema: SetMcpClientArgs,\n handler,\n};\n","/**\n * get_runtime_stats — Phase 7 / Plan 07-04 / PLG-04, ADR-007 §D-CHROME-STATS.\n *\n * Read-only per-vault stats aggregation for the chrome stats panel.\n *\n * Input: {vault?: string}\n * Output: {\n * vault, notes, chunks, last_index_at, embedding_model, embedding_dim,\n * audit_log_by_kind: Record,\n * peer_mcp_status: Array<{name, available}>,\n * contract_count\n * }\n *\n * `vault` defaults to the single registered vault when only one exists, or\n * is required when multiple are configured (callers receive\n * {ok: false, reason: \"unknown_vault\", vault}). Reads via existing query\n * layers — no new DB statements.\n *\n * # Adapter-seam discipline\n *\n * Imports `zod` only. Deps are threaded via dependency injection so the\n * tool is unit-testable without booting a real VaultManager.\n */\n\nimport { z } from \"zod\";\n\nconst GetRuntimeStatsArgs = z.object({\n vault: z\n .string()\n .min(1)\n .optional()\n .describe(\"Vault name. Defaults to the only registered vault when N=1.\"),\n});\n\nexport type GetRuntimeStatsInput = z.infer;\n\n/**\n * Minimal vault facade used by the tool. Real callers pass the live\n * `Vault` struct from `src/vault/manager.ts`; tests pass a fake conforming\n * to this shape.\n */\nexport interface StatsVault {\n config: { name: string; embedding_model?: string };\n db: {\n notes: { countAll: () => number };\n audit: {\n listRuns: (limit: number) => Array<{\n run_id: string;\n started_at: number;\n finished_at: number | null;\n }>;\n listWrites: (filter: { limit?: number }) => Array<{ op: string }>;\n };\n models: { getActive: () => { name: string; dim: number } | null };\n handle: {\n prepare: (sql: string) => { get: (...args: unknown[]) => T };\n };\n };\n}\n\nexport interface GetRuntimeStatsDeps {\n listVaults: () => StatsVault[];\n peerMcpStatus: () => Array<{ name: string; available: boolean }>;\n contractCountFor: (vault: string) => number;\n}\n\nexport type GetRuntimeStatsResult =\n | {\n vault: string;\n notes: number;\n chunks: number;\n last_index_at: number | null;\n embedding_model: string;\n embedding_dim: number;\n audit_log_by_kind: Record;\n peer_mcp_status: Array<{ name: string; available: boolean }>;\n contract_count: number;\n }\n | { ok: false; reason: \"unknown_vault\"; vault: string }\n | { ok: false; reason: \"ambiguous_vault\"; available_vaults: string[] };\n\nfunction resolveVault(\n arg: string | undefined,\n vaults: StatsVault[],\n):\n | StatsVault\n | { reason: \"unknown_vault\" | \"ambiguous_vault\"; vault?: string; available_vaults?: string[] } {\n if (arg !== undefined) {\n const v = vaults.find((vt) => vt.config.name === arg);\n if (v === undefined) return { reason: \"unknown_vault\", vault: arg };\n return v;\n }\n if (vaults.length === 0) return { reason: \"unknown_vault\", vault: \"(none)\" };\n if (vaults.length > 1) {\n return {\n reason: \"ambiguous_vault\",\n available_vaults: vaults.map((v) => v.config.name),\n };\n }\n return vaults[0]!;\n}\n\nasync function handler(\n args: GetRuntimeStatsInput,\n deps: GetRuntimeStatsDeps,\n): Promise {\n const vaults = deps.listVaults();\n const resolved = resolveVault(args.vault, vaults);\n if (\"reason\" in resolved) {\n if (resolved.reason === \"unknown_vault\") {\n return { ok: false, reason: \"unknown_vault\", vault: resolved.vault ?? args.vault ?? \"\" };\n }\n return {\n ok: false,\n reason: \"ambiguous_vault\",\n available_vaults: resolved.available_vaults ?? [],\n };\n }\n\n const vault = resolved;\n const notes = vault.db.notes.countAll();\n // No `countAll` on ChunksQueries — execute a raw COUNT via the SQLite handle.\n const chunksRow = vault.db.handle\n .prepare<{ c: number }>(\"SELECT COUNT(*) AS c FROM chunks\")\n .get();\n const chunks = chunksRow?.c ?? 0;\n\n const runs = vault.db.audit.listRuns(1);\n const lastRun = runs[0];\n const last_index_at = lastRun?.finished_at ?? null;\n\n const activeModel = vault.db.models.getActive();\n const embedding_model = activeModel?.name ?? vault.config.embedding_model ?? \"\";\n const embedding_dim = activeModel?.dim ?? 0;\n\n // Aggregate the most recent write-audit rows by op. The 1000 cap mirrors\n // the audit_log MCP tool's default — bounded to keep this read cheap.\n const writes = vault.db.audit.listWrites({ limit: 1000 });\n const audit_log_by_kind: Record = {};\n for (const w of writes) {\n audit_log_by_kind[w.op] = (audit_log_by_kind[w.op] ?? 0) + 1;\n }\n\n return {\n vault: vault.config.name,\n notes,\n chunks,\n last_index_at,\n embedding_model,\n embedding_dim,\n audit_log_by_kind,\n peer_mcp_status: deps.peerMcpStatus(),\n contract_count: deps.contractCountFor(vault.config.name),\n };\n}\n\nexport const getRuntimeStatsTool = {\n name: \"get_runtime_stats\" as const,\n description:\n \"Per-vault stats for the chrome stats panel: notes, chunks, last_index_at, \" +\n \"embedding model+dim, audit_log_by_kind, peer_mcp_status, contract_count. \" +\n \"Read-only. ADR-007 §D-CHROME-STATS.\",\n inputSchema: GetRuntimeStatsArgs,\n handler,\n};\n","/**\n * trigger_reindex — Phase 7 / Plan 07-04 / PLG-03, ADR-007 §D-CHROME-REINDEX.\n *\n * Triggers a full or per-vault reindex via the injected `reindexVault`\n * callback (which wraps the existing `indexVault` entry point). When the\n * caller supplies a `progressToken`, the handler emits\n * `notifications/progress` updates via the injected `notifier` so the plugin\n * UI can render progress.\n *\n * Input: {scope: \"this\" | \"all\", vault?: string, progressToken?: string}\n * Output: {ok: true, vaults: string[]} after all triggered vaults finish.\n *\n * # Adapter-seam discipline\n *\n * Imports `zod` only. The `indexVault` call is threaded via dependency\n * injection so this tool is unit-testable without booting a real Ollama\n * client or VaultManager.\n */\n\nimport { z } from \"zod\";\n\nconst TriggerReindexArgs = z.object({\n scope: z\n .enum([\"this\", \"all\"])\n .describe(\"'this' reindexes the named vault; 'all' reindexes every registered vault.\"),\n vault: z\n .string()\n .min(1)\n .optional()\n .describe(\n \"Required when scope='this' AND more than one vault is registered; \" +\n \"defaults to the single registered vault otherwise.\",\n ),\n progressToken: z\n .string()\n .min(1)\n .optional()\n .describe(\"MCP SDK 1.29 progressToken — when set, emits notifications/progress.\"),\n});\n\nexport type TriggerReindexInput = z.infer;\n\nexport interface ReindexVault {\n config: { name: string };\n}\n\nexport interface TriggerReindexProgress {\n progress: number;\n total?: number;\n}\n\nexport interface TriggerReindexDeps {\n listVaults: () => ReindexVault[];\n /**\n * Reindex one vault. The `onProgress` callback receives raw counts; the\n * tool layer translates those into MCP notifications/progress when a\n * progressToken is set.\n */\n reindexVault: (\n vaultName: string,\n onProgress?: (p: TriggerReindexProgress) => void,\n ) => Promise;\n /**\n * MCP SDK notification injector. Real callers pass\n * `server.server.notification.bind(server.server)`; tests pass a vi.fn().\n */\n notifier: (notification: {\n method: \"notifications/progress\";\n params: { progressToken: string; progress: number; total?: number };\n }) => void;\n}\n\nexport type TriggerReindexResult =\n | { ok: true; vaults: string[] }\n | { ok: false; reason: \"unknown_vault\"; vault: string }\n | { ok: false; reason: \"ambiguous_vault\"; available_vaults: string[] };\n\nasync function handler(\n args: TriggerReindexInput,\n deps: TriggerReindexDeps,\n): Promise {\n const allVaults = deps.listVaults().map((v) => v.config.name);\n\n // Resolve target vaults\n let targets: string[];\n if (args.scope === \"all\") {\n targets = allVaults;\n } else {\n // scope === \"this\"\n if (args.vault !== undefined) {\n if (!allVaults.includes(args.vault)) {\n return { ok: false, reason: \"unknown_vault\", vault: args.vault };\n }\n targets = [args.vault];\n } else if (allVaults.length === 1) {\n targets = [allVaults[0]!];\n } else if (allVaults.length === 0) {\n return { ok: false, reason: \"unknown_vault\", vault: \"(none)\" };\n } else {\n return { ok: false, reason: \"ambiguous_vault\", available_vaults: allVaults };\n }\n }\n\n // Run reindex per-target. Progress notifications are emitted only when a\n // progressToken was supplied; otherwise onProgress is undefined and the\n // indexer runs silently (matching the existing CLI behavior).\n const token = args.progressToken;\n for (const vname of targets) {\n const onProgress =\n token !== undefined\n ? (p: TriggerReindexProgress) => {\n deps.notifier({\n method: \"notifications/progress\",\n params:\n token !== undefined && p.total !== undefined\n ? { progressToken: token, progress: p.progress, total: p.total }\n : { progressToken: token!, progress: p.progress },\n });\n }\n : undefined;\n await deps.reindexVault(vname, onProgress);\n }\n\n return { ok: true, vaults: targets };\n}\n\nexport const triggerReindexTool = {\n name: \"trigger_reindex\" as const,\n description:\n \"Trigger a full vault reindex with optional progress notifications. \" +\n \"scope='this' reindexes one vault; scope='all' reindexes every registered vault. \" +\n \"Supply a progressToken to receive notifications/progress updates. \" +\n \"ADR-007 §D-CHROME-REINDEX.\",\n inputSchema: TriggerReindexArgs,\n handler,\n};\n","/**\n * suppress_contract_write — Phase 7 / Plan 07-07 / CAN-08, ADR-007 §D-WATCH-PLUGIN-OUT.\n *\n * Plugin-control MCP tool. Called by the contract editor's\n * `emitYamlCompanion` BEFORE every `.yaml` companion write so the\n * Phase 6 ContractRegistry ChangeFeed handler can recognize the\n * resulting filesystem event as \"our own echo\" and drop it silently.\n *\n * Workflow:\n * 1. Plugin computes `yamlBody = emitYaml(file)` via the 07-02 codec.\n * 2. Plugin computes `hash = sha256(yamlBody)` (SubtleCrypto in the\n * renderer process).\n * 3. Plugin calls THIS tool with {path, hash}.\n * 4. Plugin writes the YAML via `app.vault.adapter.write(...)`.\n * 5. ChangeFeed observes the write → loader.ts hashes the on-disk\n * body → `SuppressionSet.consume(path, hash)` returns true (match)\n * → reload is skipped.\n *\n * # Input validation (THREAT-T-07-07-01 mitigation)\n *\n * - `path`: must match `^_contracts/[^/]+\\.yaml$` (non-recursive,\n * Pitfall F3-aligned with the loader's `CONTRACT_PATH_REGEX`).\n * - `hash`: 64-char lowercase hex (SHA-256 digest format).\n * - `ttl_ms`: bounded 200..30_000 (defends THREAT-T-07-07-02 — a\n * too-long TTL could swallow a legitimate later edit).\n *\n * Invalid paths return a structured `{ok: false, reason: \"invalid_path\"}`\n * result without registering a suppression entry. Zod schema failures\n * surface as exceptions caught by `syncPluginTools`'s wrapper and\n * returned as `isError: true` MCP responses.\n *\n * # Plugin-gating\n *\n * Like the other 5 plugin-control tools, this one only registers when\n * `[plugin] enabled = true` (D-MCP-SURFACE). The v1-baseline tools-list\n * snapshot stays byte-identical under the default-OFF gate.\n *\n * # Adapter-seam discipline\n *\n * Imports only `zod` + the sibling `SuppressionSet` type. Zero `fs`,\n * `path`, `yaml`, `chokidar`, MCP SDK.\n */\n\nimport { z } from \"zod\";\nimport type { SuppressionSet } from \"../adapters/change-feed/obsidian-fs/suppression.js\";\n\n/**\n * `^_contracts/.yaml$` — non-recursive, matches the loader's\n * `CONTRACT_PATH_REGEX` (Pitfall F3). Tools written for a contract\n * outside this shape are rejected with `invalid_path` rather than\n * silently registering a useless suppression entry.\n */\nconst CONTRACT_PATH_REGEX = /^_contracts\\/[^/]+\\.yaml$/;\n\nconst SuppressContractWriteArgs = z.object({\n path: z\n .string()\n .min(1)\n .describe(\n \"Vault-relative path of the YAML companion (e.g. `_contracts/foo.yaml`). \" +\n \"Non-recursive — `_contracts/sub/foo.yaml` is rejected with invalid_path.\",\n ),\n hash: z\n .string()\n .regex(/^[0-9a-f]{64}$/, \"must be 64-char lowercase hex (SHA-256)\")\n .describe(\n \"SHA-256 of the YAML body the plugin is about to write. Used by the \" +\n \"ChangeFeed handler to distinguish echo events from real external edits.\",\n ),\n ttl_ms: z\n .number()\n .int()\n .min(200)\n .max(30_000)\n .optional()\n .describe(\n \"Suppression entry TTL in ms (default 2000). Bounded 200..30000 to \" +\n \"defend against an over-long entry swallowing a legitimate later edit.\",\n ),\n});\n\nexport type SuppressContractWriteInput = z.infer;\n\nexport interface SuppressContractWriteDeps {\n suppression: SuppressionSet;\n}\n\nexport type SuppressContractWriteResult =\n | { ok: true }\n | { ok: false; reason: \"invalid_path\"; path: string };\n\nasync function handler(\n args: SuppressContractWriteInput,\n deps: SuppressContractWriteDeps,\n): Promise {\n const { path, hash, ttl_ms } = args;\n\n if (!CONTRACT_PATH_REGEX.test(path)) {\n return { ok: false, reason: \"invalid_path\", path };\n }\n\n deps.suppression.add(path, { hash, ttlMs: ttl_ms ?? 2000 });\n return { ok: true };\n}\n\nexport const suppressContractWriteTool = {\n name: \"suppress_contract_write\" as const,\n description:\n \"Register a hash-keyed suppression entry for an upcoming `.yaml` \" +\n \"companion write. The Phase 6 ContractRegistry ChangeFeed handler \" +\n \"uses this to distinguish plugin-driven echoes from external edits \" +\n \"(CAN-08 D-WATCH-PLUGIN-OUT). Plugin must call BEFORE writing.\",\n inputSchema: SuppressContractWriteArgs,\n handler,\n};\n","/**\n * unset_mcp_client + refresh_source — SOURCES-REGISTRY.md §6 (Stage 2).\n *\n * Two plugin-gated tools that operate on the LIVE PeerMcpRegistry (not\n * config.toml). They complement `set_mcp_client`, which mutates the\n * persisted config:\n *\n * - refresh_source({name}) — re-issue tools/list against the live peer\n * and refresh the cache. Returns the updated status + tool_count.\n *\n * - unset_mcp_client({name}) — dispose the live client and drop it from\n * the registry. Idempotent. NOTE: this affects the running process\n * only; to also remove the persisted entry, the caller pairs this\n * with `set_mcp_client({name, remove:true})`.\n *\n * # Adapter-seam discipline\n *\n * Imports `zod` only. The registry is threaded via a minimal facade so\n * the tools are unit-testable without spawning peers.\n */\n\nimport { z } from \"zod\";\nimport type { PeerMcpStatus } from \"../contracts/mcp-clients.js\";\n\n/**\n * Minimal live-registry facade the tools depend on. The real caller\n * passes the singleton `PeerMcpRegistry`; tests pass a fake.\n */\nexport interface SourceRegistryFacade {\n refresh(\n name: string,\n ): Promise<{ status: PeerMcpStatus; tools: readonly unknown[]; error?: string } | undefined>;\n remove(name: string): boolean;\n}\n\n// ─── refresh_source ─────────────────────────────────────────────────────\n\nconst RefreshSourceArgs = z.object({\n name: z.string().min(1).describe(\"Peer-MCP source name to refresh (re-poll tools/list).\"),\n});\n\nexport type RefreshSourceInput = z.infer;\n\nexport type RefreshSourceResult =\n | { ok: true; name: string; status: PeerMcpStatus; tool_count: number; error?: string }\n | { ok: false; name: string; error: string };\n\nasync function refreshHandler(\n args: RefreshSourceInput,\n deps: SourceRegistryFacade,\n): Promise {\n const info = await deps.refresh(args.name);\n if (info === undefined) {\n return { ok: false, name: args.name, error: `unknown source: ${args.name}` };\n }\n const result: RefreshSourceResult = {\n ok: true,\n name: args.name,\n status: info.status,\n tool_count: info.tools.length,\n };\n if (info.error !== undefined) result.error = info.error;\n return result;\n}\n\nexport const refreshSourceTool = {\n name: \"refresh_source\" as const,\n description:\n \"Re-poll tools/list against a live peer-MCP source and refresh its cached \" +\n \"tool list. Returns the updated status (connected/unavailable/unreachable) \" +\n \"and tool_count. SOURCES-REGISTRY §6.3.\",\n inputSchema: RefreshSourceArgs,\n handler: refreshHandler,\n};\n\n// ─── unset_mcp_client ─────────────────────────────────────────────────────\n\nconst UnsetMcpClientArgs = z.object({\n name: z.string().min(1).describe(\"Peer-MCP source name to disconnect + drop from the registry.\"),\n});\n\nexport type UnsetMcpClientInput = z.infer;\n\nexport type UnsetMcpClientResult = {\n ok: true;\n name: string;\n /** True when a live client was disposed; false when the name was unknown. */\n removed: boolean;\n};\n\nasync function unsetHandler(\n args: UnsetMcpClientInput,\n deps: SourceRegistryFacade,\n): Promise {\n const removed = deps.remove(args.name);\n return { ok: true, name: args.name, removed };\n}\n\nexport const unsetMcpClientTool = {\n name: \"unset_mcp_client\" as const,\n description:\n \"Disconnect a live peer-MCP source and drop it from the running registry. \" +\n \"Idempotent (removed:false when the name is unknown). Affects the running \" +\n \"process only — pair with set_mcp_client({name, remove:true}) to also \" +\n \"delete the persisted config entry. SOURCES-REGISTRY §6.2.\",\n inputSchema: UnsetMcpClientArgs,\n handler: unsetHandler,\n};\n","/**\n * Error formatting helper.\n *\n * Collapses the recurring \"instanceof Error ? .message : String()\"\n * boilerplate into a single, testable function.\n *\n * # Adapter-seam discipline\n *\n * Pure helper. Zero runtime imports.\n */\n\n/**\n * Render an unknown thrown value as a human-readable string.\n *\n * Byte-identical to the inline ternary it replaces (an Error's `.message`,\n * otherwise `String(value)`).\n */\nexport function errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","/**\n * syncPluginTools — Phase 7 / Plan 07-04 / D-MCP-SURFACE, ADR-007.\n *\n * Diff-based dynamic MCP Tool registration for the five plugin-control tools.\n * Mirrors `syncAutoRegistered` from Phase 6 (`src/contracts/auto-register.ts`)\n * line-for-line:\n * 1. computes the desired set from `PLUGIN_TOOL_NAMES` based on `opts.enabled`;\n * 2. removes tools no longer desired via `RegisteredTool.remove()`;\n * 3. adds new tools via `server.registerTool(name, config, callback)`;\n * 4. calls `server.sendToolListChanged()` exactly ONCE per mutation cycle.\n *\n * No-op (after removing any prior registrations) when `opts.enabled === false`.\n * Default-OFF gate is the structural mechanism that keeps the v1-baseline\n * tools-list snapshot byte-stable for non-plugin deployments (Phase 8 REL-08\n * ≤32-tool budget).\n *\n * # Adapter-seam discipline\n *\n * Imports only `@modelcontextprotocol/sdk` types + sibling tool modules.\n * Zero `fs` / `path` / `yaml` / `chokidar`.\n */\n\nimport type { McpServer, RegisteredTool } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport { setRuntimeConfigTool } from \"./set-runtime-config.js\";\nimport type { SetRuntimeConfigInput } from \"./set-runtime-config.js\";\nimport { resolveSecretTool, ResolveSecretShape } from \"./resolve-secret.js\";\nimport type { ResolveSecretInput } from \"./resolve-secret.js\";\nimport { setMcpClientTool, SetMcpClientShape } from \"./set-mcp-client.js\";\nimport type { SetMcpClientInput } from \"./set-mcp-client.js\";\nimport { getRuntimeStatsTool } from \"./get-runtime-stats.js\";\nimport type { GetRuntimeStatsInput, StatsVault } from \"./get-runtime-stats.js\";\nimport { triggerReindexTool } from \"./trigger-reindex.js\";\nimport type {\n ReindexVault,\n TriggerReindexInput,\n TriggerReindexProgress,\n} from \"./trigger-reindex.js\";\nimport { suppressContractWriteTool } from \"./suppress-contract-write.js\";\nimport type { SuppressContractWriteInput } from \"./suppress-contract-write.js\";\nimport {\n refreshSourceTool,\n unsetMcpClientTool,\n type RefreshSourceInput,\n type UnsetMcpClientInput,\n type SourceRegistryFacade,\n} from \"./source-tools.js\";\nimport type { SuppressionSet } from \"../adapters/change-feed/obsidian-fs/suppression.js\";\nimport type { RuntimeConfigStore } from \"./runtime-config.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\n// Re-exports — consumed by server.ts wiring + tests.\nexport { setRuntimeConfigTool } from \"./set-runtime-config.js\";\nexport { resolveSecretTool } from \"./resolve-secret.js\";\nexport { setMcpClientTool } from \"./set-mcp-client.js\";\nexport { getRuntimeStatsTool } from \"./get-runtime-stats.js\";\nexport { triggerReindexTool } from \"./trigger-reindex.js\";\nexport { suppressContractWriteTool } from \"./suppress-contract-write.js\";\nexport { refreshSourceTool, unsetMcpClientTool } from \"./source-tools.js\";\nexport type { SourceRegistryFacade } from \"./source-tools.js\";\nexport { RuntimeConfigStore } from \"./runtime-config.js\";\n\n/**\n * Canonical list of plugin-control tool names. ORDER is significant only for\n * stable `tools/list` output — pinned here so the gating test can match\n * deterministically.\n *\n * Plan 07-07 added `suppress_contract_write` (CAN-08). The v1-baseline\n * tools-list snapshot stays byte-identical because the gate is default-OFF\n * — these names only land on the wire when `[plugin] enabled = true`.\n */\nexport const PLUGIN_TOOL_NAMES = [\n \"set_runtime_config\",\n \"resolve_secret\",\n \"set_mcp_client\",\n \"get_runtime_stats\",\n \"trigger_reindex\",\n \"suppress_contract_write\",\n // SOURCES-REGISTRY.md §6 (Stage 2) — live-registry source management.\n \"refresh_source\",\n \"unset_mcp_client\",\n] as const;\n\nexport type PluginToolName = (typeof PLUGIN_TOOL_NAMES)[number];\n\nexport interface SyncPluginToolsOpts {\n /** D-MCP-SURFACE — default-OFF gate. No-op when false. */\n enabled: boolean;\n /** Runtime-config store consumed by set_runtime_config (PLG-01). */\n runtimeConfig: RuntimeConfigStore;\n /** Path to config.toml consumed by set_mcp_client (PLG-05). */\n configPath: string;\n /** Vault list provider consumed by get_runtime_stats + trigger_reindex. */\n listVaults: () => StatsVault[] & ReindexVault[];\n /** Peer-MCP status snapshot consumed by get_runtime_stats. */\n peerMcpStatus: () => Array<{ name: string; available: boolean }>;\n /** Contract count provider consumed by get_runtime_stats. */\n contractCountFor: (vault: string) => number;\n /** Reindex callback consumed by trigger_reindex (wraps indexVault). */\n reindexVault: (\n vaultName: string,\n onProgress?: (p: TriggerReindexProgress) => void,\n ) => Promise;\n /** MCP SDK notifier consumed by trigger_reindex (for progressToken). */\n notifier: (notification: {\n method: \"notifications/progress\";\n params: { progressToken: string; progress: number; total?: number };\n }) => void;\n /**\n * Phase 7 / Plan 07-07 / CAN-08. Shared SuppressionSet consumed by\n * `suppress_contract_write`. Required when `enabled === true` — the\n * server bootstrap owns the singleton instance and threads it both\n * here and into `startContractRegistry` so a single set sees both\n * pathways.\n */\n suppression: SuppressionSet;\n /**\n * SOURCES-REGISTRY.md §6 (Stage 2). Live peer-MCP registry facade\n * consumed by `refresh_source` + `unset_mcp_client`. Required when\n * `enabled === true` — the server bootstrap owns the singleton\n * `PeerMcpRegistry` and threads it here.\n */\n sourceRegistry: SourceRegistryFacade;\n}\n\n/**\n * Wrap a handler result as an MCP `content[]` response. Mirrors `ok()` in\n * `src/server.ts`. We inline it rather than importing from server.ts to\n * preserve the adapter-seam discipline (no upward imports).\n */\nfunction ok(data: unknown): { content: Array<{ type: \"text\"; text: string }> } {\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n}\n\nfunction errorResponse(message: string): {\n isError: true;\n content: Array<{ type: \"text\"; text: string }>;\n} {\n return { isError: true, content: [{ type: \"text\", text: message }] };\n}\n\n/**\n * Sync the plugin-control MCP tools against the McpServer. Idempotent: a\n * second call with the same `enabled` state is a no-op (no register/remove\n * happens, `sendToolListChanged` does not fire).\n */\nexport function syncPluginTools(\n server: McpServer,\n registered: Map,\n opts: SyncPluginToolsOpts,\n): void {\n const desired = new Set(opts.enabled ? PLUGIN_TOOL_NAMES : []);\n\n let mutated = false;\n\n // Remove tools no longer desired.\n for (const [toolName, regd] of Array.from(registered)) {\n if (!desired.has(toolName)) {\n regd.remove();\n registered.delete(toolName);\n mutated = true;\n }\n }\n\n if (!opts.enabled) {\n if (mutated) server.sendToolListChanged();\n return;\n }\n\n // Add missing tools. Each tool's Zod input schema is its raw object shape\n // (SDK 1.29 accepts a Zod schema OR a raw shape). We pass the schema's\n // `.shape` to satisfy the SDK type expectations (Pitfall F1 of Phase 6).\n const adds: Array<{ name: PluginToolName; reg: () => RegisteredTool }> = [\n {\n name: \"set_runtime_config\",\n reg: () =>\n server.registerTool(\n setRuntimeConfigTool.name,\n {\n description: setRuntimeConfigTool.description,\n inputSchema: setRuntimeConfigTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = setRuntimeConfigTool.inputSchema.parse(\n args,\n ) as SetRuntimeConfigInput;\n const result = await setRuntimeConfigTool.handler(validated, {\n store: opts.runtimeConfig,\n });\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"resolve_secret\",\n reg: () =>\n server.registerTool(\n resolveSecretTool.name,\n {\n description: resolveSecretTool.description,\n // The exported raw shape (no .refine) is what SDK 1.29 accepts.\n // The handler re-validates with the refined schema for the\n // cross-field invariant (ciphertext OR error).\n inputSchema: ResolveSecretShape,\n },\n async (args: unknown) => {\n try {\n const validated = resolveSecretTool.inputSchema.parse(args) as ResolveSecretInput;\n const result = await resolveSecretTool.handler(validated);\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"set_mcp_client\",\n reg: () =>\n server.registerTool(\n setMcpClientTool.name,\n {\n description: setMcpClientTool.description,\n // SDK 1.29 wants a ZodRawShapeCompat — the discriminator is\n // re-validated inside the handler via the refined union schema.\n inputSchema: SetMcpClientShape,\n },\n async (args: unknown) => {\n try {\n const validated = setMcpClientTool.inputSchema.parse(args) as SetMcpClientInput;\n const result = await setMcpClientTool.handler(validated, {\n configPath: opts.configPath,\n });\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"get_runtime_stats\",\n reg: () =>\n server.registerTool(\n getRuntimeStatsTool.name,\n {\n description: getRuntimeStatsTool.description,\n inputSchema: getRuntimeStatsTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = getRuntimeStatsTool.inputSchema.parse(args) as GetRuntimeStatsInput;\n const result = await getRuntimeStatsTool.handler(validated, {\n listVaults: opts.listVaults,\n peerMcpStatus: opts.peerMcpStatus,\n contractCountFor: opts.contractCountFor,\n });\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"trigger_reindex\",\n reg: () =>\n server.registerTool(\n triggerReindexTool.name,\n {\n description: triggerReindexTool.description,\n inputSchema: triggerReindexTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = triggerReindexTool.inputSchema.parse(args) as TriggerReindexInput;\n const result = await triggerReindexTool.handler(validated, {\n listVaults: opts.listVaults,\n reindexVault: opts.reindexVault,\n notifier: opts.notifier,\n });\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"suppress_contract_write\",\n reg: () =>\n server.registerTool(\n suppressContractWriteTool.name,\n {\n description: suppressContractWriteTool.description,\n inputSchema: suppressContractWriteTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = suppressContractWriteTool.inputSchema.parse(\n args,\n ) as SuppressContractWriteInput;\n const result = await suppressContractWriteTool.handler(validated, {\n suppression: opts.suppression,\n });\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"refresh_source\",\n reg: () =>\n server.registerTool(\n refreshSourceTool.name,\n {\n description: refreshSourceTool.description,\n inputSchema: refreshSourceTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = refreshSourceTool.inputSchema.parse(args) as RefreshSourceInput;\n const result = await refreshSourceTool.handler(validated, opts.sourceRegistry);\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"unset_mcp_client\",\n reg: () =>\n server.registerTool(\n unsetMcpClientTool.name,\n {\n description: unsetMcpClientTool.description,\n inputSchema: unsetMcpClientTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = unsetMcpClientTool.inputSchema.parse(args) as UnsetMcpClientInput;\n const result = await unsetMcpClientTool.handler(validated, opts.sourceRegistry);\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n ];\n\n for (const { name, reg } of adds) {\n if (registered.has(name)) continue;\n registered.set(name, reg());\n mutated = true;\n }\n\n if (mutated) server.sendToolListChanged();\n}\n","/**\n * Heading extraction for Markdown content.\n *\n * Recognizes ATX-style headings (`#`..`######`) outside fenced code blocks.\n * Setext-style headings (underlined with `===` / `---`) are not supported —\n * they are extremely rare in Obsidian vaults and skipping them keeps the\n * parser simple and predictable.\n */\n\nexport interface HeadingRef {\n /** Heading level, 1–6. */\n level: number;\n /** Heading text, without leading `#` markers or trimming whitespace. */\n text: string;\n /** 1-based line number in source content. */\n line: number;\n /** Character offset where the heading line starts in source content. */\n startOffset: number;\n}\n\nconst ATX_HEADING_RE = /^(#{1,6})\\s+(.+?)\\s*#*\\s*$/;\nconst FENCE_RE = /^(\\s*)(`{3,}|~{3,})/;\n\n/**\n * Extract all ATX headings from the content, ignoring anything inside fenced\n * code blocks. Returns headings in document order.\n */\nexport function extractHeadings(content: string): HeadingRef[] {\n const headings: HeadingRef[] = [];\n if (content.length === 0) return headings;\n\n const lines = content.split(\"\\n\");\n let offset = 0;\n let inFence = false;\n let fenceMarker: string | null = null;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n const fenceMatch = FENCE_RE.exec(line);\n if (fenceMatch) {\n const marker = fenceMatch[2] ?? \"\";\n if (!inFence) {\n inFence = true;\n fenceMarker = marker[0] ?? null; // remember whether it was ` or ~\n } else if (fenceMarker && marker.startsWith(fenceMarker)) {\n inFence = false;\n fenceMarker = null;\n }\n } else if (!inFence) {\n const m = ATX_HEADING_RE.exec(line);\n if (m) {\n const hashes = m[1] ?? \"\";\n const text = m[2] ?? \"\";\n headings.push({\n level: hashes.length,\n text: text.trim(),\n line: i + 1,\n startOffset: offset,\n });\n }\n }\n // +1 for the newline character (the last line may have no trailing newline,\n // but we never read past the end of the lines array).\n offset += line.length + 1;\n }\n\n return headings;\n}\n\n/**\n * Return the nearest preceding heading as a short path string,\n * e.g. `\"## 5. Empfehlung\"`. Returns `null` if no heading precedes the offset.\n *\n * This is an MVP-style path: only the immediate predecessor, not a full\n * `H1 > H2 > H3` breadcrumb.\n */\nexport function headingPathAtOffset(headings: HeadingRef[], offset: number): string | null {\n let last: HeadingRef | null = null;\n for (const h of headings) {\n if (h.startOffset <= offset) {\n last = h;\n } else {\n break;\n }\n }\n if (!last) return null;\n return `${\"#\".repeat(last.level)} ${last.text}`;\n}\n","/**\n * Phase 3 — section anchor computation.\n *\n * Per ADR-003 H-7:\n * anchor = sha256_hex(NFC(heading_text) || \"\\n\" || NFC(plain_text_body))\n *\n * The `plain_text_body` is produced by `blockToPlainText` (defined below)\n * walking the section's `BlockNode[]`. The renderer is intentionally\n * minimal — it is NOT a markdown round-trip; its only contract is that\n * identical-content sections produce identical hashes.\n *\n * Pure function. No fs / gray-matter / chokidar / path imports. The\n * adapter-seam linter (`scripts/lint-adapters.sh`) enforces this.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { BlockNode } from \"../types.js\";\n\n/**\n * Compute the canonical content-hash anchor for a section.\n *\n * Algorithm:\n * plainBody = blocks.map(blockToPlainText).join(\"\\n\")\n * canonical = headingText.normalize(\"NFC\") + \"\\n\" + plainBody.normalize(\"NFC\")\n * anchor = sha256_hex(canonical)\n *\n * NFC normalization is required so that the same logical string\n * encoded differently (precomposed vs decomposed Unicode) produces\n * identical anchors. LF (0x0A) is the only separator.\n *\n * The trailing newline separator (between heading and body) is emitted\n * UNCONDITIONALLY — even when the body is empty or the heading is the\n * synthetic preamble \"\" — so that a section with `heading_text = \"\"`\n * and `blocks = []` produces a deterministic, well-defined hash\n * (not the sha256 of the empty string).\n */\nexport function computeAnchor(headingText: string, blocks: readonly BlockNode[]): string {\n const plainBody = blocks.map(blockToPlainText).join(\"\\n\");\n const canonical = headingText.normalize(\"NFC\") + \"\\n\" + plainBody.normalize(\"NFC\");\n return createHash(\"sha256\").update(canonical, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Deterministic plain-text rendering of a single block. Identical\n * content produces identical output; this is the only requirement.\n *\n * Discriminated-union exhaustiveness is enforced via the `never`\n * fallthrough — a future block variant added without updating this\n * function fails type-check.\n */\nexport function blockToPlainText(block: BlockNode): string {\n switch (block.kind) {\n case \"paragraph\":\n return block.text;\n case \"heading\":\n return \"#\".repeat(block.level) + \" \" + block.text;\n case \"code\":\n return \"```\" + (block.lang ?? \"\") + \"\\n\" + block.text + \"\\n```\";\n case \"list\": {\n const marker = block.ordered ? \"1.\" : \"-\";\n return block.items.map((item) => marker + \" \" + item).join(\"\\n\");\n }\n case \"section\":\n // Recursive case — sections nesting sections is permitted by the\n // type union (the canonical Phase 3 `BlockNode` tree). The plain\n // text of a section block is its own heading line + its blocks'\n // plain text, joined consistently with the top-level anchor\n // algorithm above.\n return (\n \"#\".repeat(Math.max(1, block.level)) +\n \" \" +\n // For the synthetic preamble (level 0, empty heading_text) the\n // hash collapses to \"# \" + \"\" which is fine — sections-of-sections\n // is an unusual shape and only appears in tree-builder outputs.\n (block.heading_path[block.heading_path.length - 1] ?? \"\") +\n \"\\n\" +\n block.blocks.map(blockToPlainText).join(\"\\n\")\n );\n default: {\n const _exhaustive: never = block;\n return _exhaustive;\n }\n }\n}\n","/**\n * Phase 3 — section extraction.\n *\n * Walks `BlockNode[]` left-to-right and produces a flat array of\n * `SectionInfo` per ADR-003 H-7. Each section aggregates a heading\n * and all `BlockNode` descendants up to (but not including) the\n * next equal-or-shallower heading. Top-of-document content with no\n * preceding heading becomes a synthetic preamble section\n * (`level: 0, heading_path: [], heading_text: \"\"`).\n *\n * Also exports `markdownToSectionBlocks(content)` — a minimal markdown\n * → `BlockNode[]` lifter used by the indexer and the migration-time\n * backfill (since v1 storage only carries the raw markdown content,\n * not a parsed `BlockNode[]`). The lifter emits `heading` and\n * `paragraph` variants only, which is sufficient for section identity\n * (anchor + heading_path). Fenced code blocks are kept as paragraphs\n * so their body bytes participate in the anchor exactly as written.\n *\n * Pure module — no fs / gray-matter / chokidar / path imports.\n * Enforced by `scripts/lint-adapters.sh`.\n */\n\nimport type { BlockNode, SectionInfo } from \"../types.js\";\nimport { extractHeadings } from \"../chunker/headings.js\";\nimport { computeAnchor } from \"./anchor.js\";\n\n/**\n * Walk `blocks` left-to-right and return the section list. The list\n * order is document order (preamble first if present, then sections\n * in source order). `parent_index` points into this array; `ord` is\n * the sibling index under the same parent (assigned in a second pass).\n *\n * Algorithm (per plan):\n * - Maintain a stack of open sections, each at some level.\n * - On each heading:\n * pop while top.level >= heading.level\n * new section parent_index = top-of-stack (or null)\n * push it\n * - On each non-heading block:\n * if stack is empty, lazily open a synthetic preamble (level 0).\n * append to the current top-of-stack section's plain-text body.\n *\n * `plain_text_body` is built by joining each contained block's plain\n * text with `\"\\n\"`, identical to how `computeAnchor` consumes blocks.\n * This keeps the body bytes deterministic and lets the anchor be\n * computed directly from `(heading_text, blocks_in_section)` without\n * a second walk.\n */\nexport function extractSections(blocks: readonly BlockNode[]): SectionInfo[] {\n // Working representation: each section owns the BlockNode[] it\n // accumulates, plus its level + heading_text + heading_path +\n // parent_index. Anchors are computed at the end.\n interface Working {\n level: 0 | 1 | 2 | 3 | 4 | 5 | 6;\n heading_text: string;\n heading_path: string[];\n parent_index: number | null;\n blocks: BlockNode[];\n }\n\n const out: Working[] = [];\n // Stack tracks indices into `out` (so we can update parent_index\n // and append blocks). Each entry is an index whose section is\n // currently \"open\".\n const stack: number[] = [];\n\n const stackTop = (): number | null =>\n stack.length === 0 ? null : (stack[stack.length - 1] ?? null);\n\n const ensurePreamble = (): number => {\n // The preamble exists iff there's a level-0 section at index 0.\n if (out.length > 0 && out[0]!.level === 0) return 0;\n // No preamble yet — open one. It must be the FIRST entry in `out`.\n if (out.length > 0) {\n // Defensive: if non-heading content appears after some headings\n // have been opened, this branch is unreachable (the heading\n // would be on the stack already). The check is here only to\n // guarantee preamble-at-index-0 if anyone calls ensurePreamble\n // mid-walk.\n throw new Error(\n \"Internal invariant: ensurePreamble called after sections exist; section walker is buggy.\",\n );\n }\n out.push({\n level: 0,\n heading_text: \"\",\n heading_path: [],\n parent_index: null,\n blocks: [],\n });\n stack.push(0);\n return 0;\n };\n\n for (const block of blocks) {\n if (block.kind === \"heading\") {\n // Pop open sections whose level >= this heading's level.\n // The synthetic preamble (level 0) is also popped on the first\n // heading we encounter — preambles live at the document root\n // alongside top-level headings, NOT as their parent. (Without\n // this special case, a `0 >= 1` check would be false and the\n // first H1 would be threaded under the preamble.)\n while (stack.length > 0) {\n const topIdx = stack[stack.length - 1]!;\n const top = out[topIdx]!;\n if (top.level >= block.level || top.level === 0) {\n stack.pop();\n } else {\n break;\n }\n }\n const parentIdx = stackTop();\n const parentPath = parentIdx === null ? [] : out[parentIdx]!.heading_path;\n const headingText = block.text;\n out.push({\n level: block.level,\n heading_text: headingText,\n heading_path: [...parentPath, headingText],\n parent_index: parentIdx,\n blocks: [],\n });\n stack.push(out.length - 1);\n continue;\n }\n // Non-heading block (paragraph / code / list / section / etc).\n // If nothing is open yet, lazily open the synthetic preamble.\n if (stack.length === 0) {\n ensurePreamble();\n }\n const topIdx = stackTop()!;\n out[topIdx]!.blocks.push(block);\n }\n\n // Second pass: assign `ord` per (parent_index) sibling group.\n // `ord` is the index in document order among sections sharing the\n // same `parent_index`.\n const ords: number[] = new Array(out.length).fill(0);\n const seenPerParent = new Map();\n for (let i = 0; i < out.length; i++) {\n const parent = out[i]!.parent_index;\n const next = seenPerParent.get(parent) ?? 0;\n ords[i] = next;\n seenPerParent.set(parent, next + 1);\n }\n\n // Materialize SectionInfo[] with anchors + ord + plain_text_body.\n return out.map((w, i) => {\n const plainBody = w.blocks.map(blockToPlainTextLocal).join(\"\\n\");\n const anchor = computeAnchor(w.heading_text, w.blocks);\n return {\n anchor,\n heading_path: w.heading_path,\n heading_text: w.heading_text,\n level: w.level,\n parent_index: w.parent_index,\n ord: ords[i]!,\n plain_text_body: plainBody,\n };\n });\n}\n\n/**\n * Local plain-text helper for body byte reconstruction inside\n * `extractSections`. Mirrors `blockToPlainText` from `./anchor.ts` but\n * keeps the function inline to avoid a circular-import path. The two\n * helpers MUST emit byte-identical output for the same `BlockNode` —\n * the `markdownToSectionBlocks` round-trip test in `extract.test.ts`\n * verifies this indirectly (anchor equivalence).\n *\n * `section` variant deliberately not handled here — the section walker\n * never emits a nested `section` block into `Working.blocks`; that\n * variant exists only as the canonical OUTPUT shape returned from\n * `get_outline` (Phase 3 slice 03-02), not as input to extraction.\n */\nfunction blockToPlainTextLocal(block: BlockNode): string {\n switch (block.kind) {\n case \"paragraph\":\n return block.text;\n case \"heading\":\n return \"#\".repeat(block.level) + \" \" + block.text;\n case \"code\":\n return \"```\" + (block.lang ?? \"\") + \"\\n\" + block.text + \"\\n```\";\n case \"list\": {\n const marker = block.ordered ? \"1.\" : \"-\";\n return block.items.map((item) => marker + \" \" + item).join(\"\\n\");\n }\n case \"section\":\n // See JSDoc — should not appear as input, but render defensively.\n return (\n \"#\".repeat(Math.max(1, block.level)) +\n \" \" +\n (block.heading_path[block.heading_path.length - 1] ?? \"\") +\n \"\\n\" +\n block.blocks.map(blockToPlainTextLocal).join(\"\\n\")\n );\n default: {\n const _exhaustive: never = block;\n return _exhaustive;\n }\n }\n}\n\n/**\n * Lift raw markdown into a minimal `BlockNode[]` of `heading` +\n * `paragraph` variants — enough for section identity. Used by the\n * indexer and the migration-time backfill.\n *\n * Why this lifter exists: v1 storage holds `notes.content` (raw\n * markdown) but no parsed `BlockNode[]`. Phase 3 needs sections\n * extracted from that markdown. A full markdown→BlockNode parser is\n * out of scope for this slice (and would duplicate Phase 1 adapter\n * work). This minimal lifter is sufficient because anchors only\n * depend on heading_text + plain_text_body, and the body bytes are\n * preserved verbatim regardless of how they're labeled.\n *\n * Algorithm:\n * 1. Run `extractHeadings(content)` to get every ATX heading's\n * level + text + startOffset (already fenced-code-aware).\n * 2. Slice the content between heading start offsets:\n * - The slice from 0 to the first heading's start is the preamble\n * body (emitted as a single `paragraph` block IF non-empty).\n * - Each heading + the slice between its line and the next\n * heading's line becomes a `heading` block followed by a\n * `paragraph` block carrying the body bytes (verbatim,\n * with the heading line itself stripped).\n *\n * Body slices are kept verbatim (including blank lines and code\n * fences). The indexer's anchor calculation depends on byte\n * stability — we do NOT trim trailing whitespace, normalize\n * line endings, or collapse blanks. NFC normalization happens\n * inside `computeAnchor`.\n *\n * Pure — no fs / gray-matter / chokidar imports.\n */\nexport function markdownToSectionBlocks(content: string): BlockNode[] {\n if (content.length === 0) return [];\n const headings = extractHeadings(content);\n\n const out: BlockNode[] = [];\n\n // Preamble: bytes from 0 to first heading's startOffset (or end of\n // content if no headings).\n const firstHeadingStart = headings.length === 0 ? content.length : headings[0]!.startOffset;\n if (firstHeadingStart > 0) {\n const preamble = content.slice(0, firstHeadingStart);\n if (preamble.length > 0) {\n // Strip a single trailing newline so the paragraph block doesn't\n // carry the separator into its body bytes. (Preserves stability\n // when the body is \"intro text\\n\" before \"# H1\" — the heading's\n // own line begins exactly at firstHeadingStart, so the slice\n // includes the newline between intro and #.)\n out.push({ kind: \"paragraph\", text: stripTrailingNewline(preamble) });\n }\n }\n\n for (let i = 0; i < headings.length; i++) {\n const h = headings[i]!;\n const next = headings[i + 1];\n const headingLineEnd = nextLineEnd(content, h.startOffset);\n const headingBodyStart = headingLineEnd;\n const headingBodyEnd = next ? next.startOffset : content.length;\n // Cast to the strict heading-level type — extractHeadings only\n // emits 1..6 per the ATX regex, so the runtime guarantee holds.\n const level = h.level as 1 | 2 | 3 | 4 | 5 | 6;\n out.push({ kind: \"heading\", level, text: h.text });\n if (headingBodyEnd > headingBodyStart) {\n const body = content.slice(headingBodyStart, headingBodyEnd);\n const trimmed = stripTrailingNewline(body);\n // Skip an empty body to keep the BlockNode list tight — sections\n // with no body still get a valid anchor (sha256 of \"\\n\").\n if (trimmed.length > 0) {\n out.push({ kind: \"paragraph\", text: trimmed });\n }\n }\n }\n\n return out;\n}\n\nfunction nextLineEnd(content: string, start: number): number {\n // Find the first 0x0A at or after `start`. Returns the index AFTER\n // the newline (so the next line begins there), or content.length if\n // no newline is found.\n const idx = content.indexOf(\"\\n\", start);\n if (idx === -1) return content.length;\n return idx + 1;\n}\n\nfunction stripTrailingNewline(s: string): string {\n if (s.endsWith(\"\\r\\n\")) return s.slice(0, -2);\n if (s.endsWith(\"\\n\")) return s.slice(0, -1);\n return s;\n}\n","/**\n * Phase 3 — one-time section backfill (M2 fix in plan 03-01).\n *\n * Wired into migration 010 (`src/db/schema.ts:runMigration010`) so an\n * existing v1 user vault gets `sections` rows populated immediately on\n * upgrade — WITHOUT requiring a content edit / catchup pass.\n *\n * Approach (per 03-01-DEVIATIONS.md §D1): re-derive sections from each\n * note's `content` column via `markdownToSectionBlocks` →\n * `extractSections`, NOT from `chunks.heading_path` (which only carries\n * the immediate-predecessor heading as a markdown string, insufficient\n * to reconstruct a full section tree). This keeps the\n * anchor-equivalence guarantee trivially: backfill and a fresh re-index\n * run the SAME pipeline against the SAME `notes.content` bytes.\n *\n * Pure of fs / gray-matter / chokidar / path imports. Reads + writes\n * only via the supplied `BetterSqlite3.Database` handle.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\nimport type { BlockNode, ChunkRow, InsertSectionRow, SectionInfo } from \"../types.js\";\nimport { extractSections, markdownToSectionBlocks } from \"./extract.js\";\n\n/**\n * Walk every note in the DB and ensure it has a corresponding set of\n * `sections` rows. Idempotent: if a note already has sections, skip\n * it. Returns the number of notes for which sections were newly\n * populated (for migration log / test assertions).\n *\n * Called from migration 010 inside the migration transaction; safe to\n * call again from tests against an in-memory DB.\n */\nexport function backfillSectionsFromChunks(db: BetterSqlite3.Database): number {\n // Inline these queries (rather than going through SectionsQueries /\n // NotesQueries / ChunksQueries) so the migration runner doesn't\n // depend on the high-level query namespaces. The schema is\n // guaranteed to exist at this point (migration 010 step A ran first).\n const notesRows = db\n .prepare<[], { id: number; content: string }>(\"SELECT id, content FROM notes\")\n .all();\n\n const existingCount = db.prepare<[number], { c: number }>(\n \"SELECT COUNT(*) AS c FROM sections WHERE note_id = ?\",\n );\n const getChunks = db.prepare<[number], ChunkRow>(\n \"SELECT * FROM chunks WHERE note_id = ? ORDER BY id ASC\",\n );\n // INSERT OR IGNORE: section identity is (note_id, heading_path, anchor) per\n // ADR-032 (revised). A collision needs same anchor AND same heading_path —\n // i.e. byte-identical content in the same context. A plain INSERT on a true\n // collision would roll back the whole migration transaction and crash every\n // CLI command (see ISSUE-migration-010-duplicate-anchor.md). `OR IGNORE`\n // makes the first sibling win; later same-identity siblings collapse into it\n // for parent-linkage via lookupExistingSection. Differently-placed\n // byte-identical sections (different heading_path) persist as distinct rows.\n const insertSection = db.prepare(`\n INSERT OR IGNORE INTO sections\n (note_id, anchor, heading_path, heading_text, level,\n parent_id, ord, chunk_id_first, chunk_id_last, created_at)\n VALUES\n (@note_id, @anchor, @heading_path, @heading_text, @level,\n @parent_id, @ord, @chunk_id_first, @chunk_id_last, @created_at)\n `);\n\n const lookupExistingSection = db.prepare<[number, string, string], { id: number }>(\n \"SELECT id FROM sections WHERE note_id = ? AND heading_path = ? AND anchor = ?\",\n );\n\n let backfilled = 0;\n const now = Date.now();\n\n for (const note of notesRows) {\n // Skip notes that already have sections (idempotency / safety).\n const existing = existingCount.get(note.id);\n if (existing && existing.c > 0) continue;\n\n if (!note.content || note.content.length === 0) {\n // Empty notes get no sections — keep storage tight.\n continue;\n }\n\n const blocks: BlockNode[] = markdownToSectionBlocks(note.content);\n const sectionInfos: SectionInfo[] = extractSections(blocks);\n if (sectionInfos.length === 0) continue;\n\n // Walk this note's chunks once and bin them into the section list\n // by `start_offset`. Sections own a [chunk_id_first, chunk_id_last]\n // range; we compute it by mapping each chunk's start offset to its\n // owning heading region.\n const chunks = getChunks.all(note.id);\n const chunkRanges = computeChunkRangesForSections(note.content, sectionInfos, chunks);\n\n // Insert in two passes so parent_id can reference the newly-minted\n // section IDs. Per-index → ID map populated as we go. Slots for\n // duplicate-anchor siblings reuse the surviving row's id so any\n // subsequent child still resolves its parent_id correctly.\n const insertedIds: Array = [];\n for (let i = 0; i < sectionInfos.length; i++) {\n const s = sectionInfos[i]!;\n const parentId = s.parent_index === null ? null : (insertedIds[s.parent_index] ?? null);\n const range = chunkRanges[i] ?? { first: null, last: null };\n const row: InsertSectionRow & { created_at: number } = {\n note_id: note.id,\n anchor: s.anchor,\n heading_path: JSON.stringify(s.heading_path),\n heading_text: s.heading_text,\n level: s.level,\n parent_id: parentId,\n ord: s.ord,\n chunk_id_first: range.first,\n chunk_id_last: range.last,\n created_at: now,\n };\n const info = insertSection.run(row);\n if (info.changes > 0) {\n // Row inserted normally.\n insertedIds.push(Number(info.lastInsertRowid));\n } else {\n // Collision on UNIQUE(note_id, heading_path, anchor): a byte-identical\n // sibling in the SAME context already won the slot. Look up by the full\n // identity (heading_path stored JSON-stringified, matching the row) so\n // any later child still has a parent_id to resolve against. Acceptable\n // for a one-time migration backfill; the next full re-index rebuilds.\n const existing = lookupExistingSection.get(\n note.id,\n JSON.stringify(s.heading_path),\n s.anchor,\n );\n insertedIds.push(existing ? Number(existing.id) : null);\n }\n }\n backfilled++;\n }\n\n return backfilled;\n}\n\n/**\n * Map each section to its [chunk_id_first, chunk_id_last] range.\n *\n * Algorithm: re-derive each section's character offset window in the\n * source `content` by re-running `extractHeadingsLite` on the same\n * bytes, then place each chunk into the section whose offset window\n * contains the chunk's `start_offset`.\n *\n * Sections with no chunks (e.g. a heading followed by another heading\n * with no body) get `{ first: null, last: null }`. The chunker drops\n * heading-only spans, so this is the common case for documents with\n * empty subsections.\n */\nfunction computeChunkRangesForSections(\n content: string,\n sections: SectionInfo[],\n chunks: ChunkRow[],\n): Array<{ first: number | null; last: number | null }> {\n // We need each section's character range in the source bytes. The\n // simplest correct construction: re-walk the same heading list the\n // lifter uses, and produce a (sectionIndex → [start, end]) map.\n //\n // We import the SAME heading extractor used by markdownToSectionBlocks\n // to guarantee identical offset semantics. (No fs/gray-matter — pure.)\n // To avoid a circular import we lazy-require here via the named\n // export.\n const ranges = computeSectionOffsetRanges(content, sections);\n const out: Array<{ first: number | null; last: number | null }> = sections.map(() => ({\n first: null,\n last: null,\n }));\n\n for (const chunk of chunks) {\n const offset = chunk.start_offset;\n // Find the section whose [start, end) range contains this offset.\n // Walk in reverse so the innermost (latest, deepest) section wins.\n let chosenIdx: number | null = null;\n for (let i = ranges.length - 1; i >= 0; i--) {\n const r = ranges[i];\n if (!r) continue;\n if (offset >= r.start && offset < r.end) {\n chosenIdx = i;\n break;\n }\n }\n if (chosenIdx === null) continue;\n const slot = out[chosenIdx]!;\n if (slot.first === null || chunk.id < slot.first) slot.first = chunk.id;\n if (slot.last === null || chunk.id > slot.last) slot.last = chunk.id;\n }\n\n return out;\n}\n\n/**\n * Compute the [start, end) byte range for each section in `content`,\n * matching the slicing semantics of `markdownToSectionBlocks`.\n *\n * - Preamble (level 0) range is [0, firstHeading.startOffset).\n * - Each heading section range is [heading.startOffset, nextSibling.startOffset)\n * where nextSibling is the next heading at an equal-or-shallower level\n * (or content.length if none).\n *\n * `sections` is provided so the function can assign ranges in a way that\n * matches the section walker's output order (preamble first if present,\n * then headings in source order).\n *\n * The implementation re-extracts headings from `content` directly so it\n * doesn't depend on the section walker's internal state. This means\n * `extractHeadings` from src/chunker/headings.ts is the canonical\n * heading source — both for the lifter AND for this offset map.\n */\nfunction computeSectionOffsetRanges(\n content: string,\n sections: SectionInfo[],\n): Array<{ start: number; end: number }> {\n // Local import — avoids a circular path through schema.ts.\n // (sections/backfill.ts → chunker/headings.ts is a clean dependency.)\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n // Inline import to keep this helper self-contained; not type-imported\n // because we need the runtime call. ESM `import` at module scope is\n // the correct form below.\n const headings = headingExtractor(content);\n\n const ranges: Array<{ start: number; end: number }> = [];\n let cursor = 0; // walks the sections array\n\n // Preamble (if present) is sections[0] with level 0.\n const hasPreamble =\n sections.length > 0 && sections[0]!.level === 0 && sections[0]!.heading_text === \"\";\n const firstHeadingOffset = headings.length === 0 ? content.length : headings[0]!.startOffset;\n if (hasPreamble) {\n ranges.push({ start: 0, end: firstHeadingOffset });\n cursor = 1;\n }\n\n // For every heading section, find the next equal-or-shallower\n // heading in the source — that's the section's end offset.\n for (let h = 0; h < headings.length; h++) {\n const h0 = headings[h]!;\n let endOffset = content.length;\n for (let j = h + 1; j < headings.length; j++) {\n if (headings[j]!.level <= h0.level) {\n endOffset = headings[j]!.startOffset;\n break;\n }\n }\n ranges.push({ start: h0.startOffset, end: endOffset });\n cursor++;\n }\n\n // Defensive: if there's a length mismatch (shouldn't happen for valid\n // input), fall back to whole-document ranges for any tail entries.\n while (ranges.length < sections.length) {\n ranges.push({ start: 0, end: content.length });\n }\n return ranges;\n}\n\n// Lazy heading-extractor binding to keep `backfill.ts` free of static\n// type-side imports of the chunker module beyond what's needed for the\n// section walker. Kept as a function-import indirection so the type is\n// inferred from the call site and module-scope import-cycles stay\n// simple.\nimport { extractHeadings as headingExtractor } from \"../chunker/headings.js\";\n","/**\n * Phase 5 — chunk-fragment computation.\n *\n * Per ADR-005 §\"Decision: Chunk-level source_hashes (ChunkId)\" and\n * ADR-003 H-3 (NFC) + H-4 (LF) + Pitfall 8 (trim trailing whitespace):\n *\n * canonical = text.replace(/\\r\\n/g, \"\\n\").trimEnd().normalize(\"NFC\")\n * hash = \"sha256:\" + sha256_hex(canonical)\n * fragment = hash.slice(\"sha256:\".length, \"sha256:\".length + 7)\n *\n * `computeChunkHash` is the **single source of truth** for both\n * `chunks.chunk_id_fragment` (D-04) AND the brief\n * `source_hashes.recorded_hash` value. Scattered `createHash` calls\n * across call sites are an anti-pattern (RESEARCH §Pitfall 14).\n *\n * Pure function. No fs / gray-matter / chokidar / path imports. The\n * adapter-seam linter (`scripts/lint-adapters.sh`) enforces this.\n */\n\nimport { createHash } from \"node:crypto\";\n\n/**\n * Canonical chunk-hash. Drives BOTH `chunks.chunk_id_fragment` (D-04)\n * AND the brief `source_hashes.recorded_hash` value.\n *\n * Algorithm (ADR-003 H-3/H-4 + ADR-005 Pitfall 8):\n * 1. Normalize CRLF → LF (`\\r\\n` → `\\n`).\n * 2. Trim trailing whitespace (`trimEnd()`).\n * 3. Unicode NFC normalize.\n * 4. sha256_hex over the canonical UTF-8 bytes.\n *\n * Output format: `\"sha256:\"`. The `sha256:` prefix is part of the\n * versioned-API hash inclusion (ADR-003 H-6) — a future v3 hash flavour\n * switch (blake3 / xxhash) replaces the prefix in a single migration.\n */\nexport function computeChunkHash(text: string): string {\n const canonical = text.replace(/\\r\\n/g, \"\\n\").trimEnd().normalize(\"NFC\");\n return \"sha256:\" + createHash(\"sha256\").update(canonical, \"utf8\").digest(\"hex\");\n}\n\n/**\n * First 7 hex chars of `computeChunkHash(text)`. Public ChunkId\n * fragment per D-04.\n *\n * Collision risk at 7 hex chars (~268M combos) is acceptable at\n * document scope: worst-case thousands of chunks per doc; document\n * boundary is the disambiguator in the public ChunkId\n * (`#chunk-`).\n */\nexport function computeChunkIdFragment(text: string): string {\n return computeChunkHash(text).slice(\"sha256:\".length, \"sha256:\".length + 7);\n}\n","/**\n * SQL DDL strings and migrations.\n *\n * Migrations are inlined as TS constants — no external .sql files. This is\n * intentional: it keeps the build trivial (tsup doesn't need to copy assets)\n * and makes the migration list a single source of truth.\n *\n * To add a migration: append to `MIGRATIONS` with a monotonically increasing\n * `version`. The runner applies all migrations whose version > user_version\n * in order, then sets PRAGMA user_version to the highest version applied.\n */\n\nimport { backfillSectionsFromChunks } from \"../sections/backfill.js\";\nimport { computeChunkIdFragment } from \"../chunker/chunk-id.js\";\n\n/**\n * Context passed to every function-style migration. New optional fields can be\n * added here without rewriting existing migrations — they accept the whole\n * context as a single arg and ignore the bits they don't need.\n *\n * `vaultName` is plumbed in from the Database constructor (see database.ts).\n * Migration 008 (doc_uri backfill) requires it; earlier function-style\n * migrations (005) accept it and ignore it.\n */\nexport interface MigrationContext {\n readonly vaultName: string | undefined;\n}\n\n/**\n * A migration either ships static SQL or a function that runs imperative\n * steps against the DB. Function-style migrations are used when the steps\n * depend on the current schema state (e.g. discover all `embeddings_`\n * tables and rebuild each).\n */\nexport type Migration =\n | {\n version: number;\n description: string;\n sql: string;\n }\n | {\n version: number;\n description: string;\n run: (db: BetterSqlite3Database, ctx: MigrationContext) => void;\n };\n\n/** Section 3 of the spec — full initial schema. */\nexport const INITIAL_SCHEMA: string = `\n-- ── 3.1 Raw Layer ────────────────────────────────────────────────────────\n\n-- Migration 006 adds body_hash to this table (kept out of v1 schema so\n-- the migration chain has historical accuracy and frequent DB-rebuild\n-- tests do not trip over duplicate-column errors).\nCREATE TABLE IF NOT EXISTS notes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n path TEXT NOT NULL UNIQUE,\n content TEXT NOT NULL,\n frontmatter TEXT,\n title TEXT,\n hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n word_count INTEGER,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_notes_hash ON notes(hash);\nCREATE INDEX IF NOT EXISTS idx_notes_mtime ON notes(mtime);\n\nCREATE TABLE IF NOT EXISTS chunks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n idx INTEGER NOT NULL,\n text TEXT NOT NULL,\n heading_path TEXT,\n start_offset INTEGER NOT NULL,\n end_offset INTEGER NOT NULL,\n token_count INTEGER NOT NULL,\n UNIQUE (note_id, idx)\n);\nCREATE INDEX IF NOT EXISTS idx_chunks_note ON chunks(note_id);\n\n-- ── 3.2 Derived Layer ────────────────────────────────────────────────────\n\n-- Dimension 1024 matches qwen3-embedding (our default per Memory System spec).\n-- For future multi-model support with different dims, see roadmap Phase 7.\nCREATE VIRTUAL TABLE IF NOT EXISTS embeddings USING vec0(\n chunk_id INTEGER PRIMARY KEY,\n model_id INTEGER NOT NULL,\n vector FLOAT[1024]\n);\n\nCREATE TABLE IF NOT EXISTS models (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL UNIQUE,\n provider TEXT NOT NULL,\n dim INTEGER NOT NULL,\n created_at INTEGER NOT NULL,\n active INTEGER NOT NULL DEFAULT 1\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(\n text,\n content='chunks',\n content_rowid='id'\n);\n\n-- Triggers to keep chunks_fts in sync with chunks\nCREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN\n INSERT INTO chunks_fts(rowid, text) VALUES (new.id, new.text);\nEND;\nCREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN\n INSERT INTO chunks_fts(chunks_fts, rowid, text) VALUES('delete', old.id, old.text);\nEND;\nCREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN\n INSERT INTO chunks_fts(chunks_fts, rowid, text) VALUES('delete', old.id, old.text);\n INSERT INTO chunks_fts(rowid, text) VALUES (new.id, new.text);\nEND;\n\nCREATE TABLE IF NOT EXISTS wikilinks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_note INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n target_path TEXT NOT NULL,\n target_note INTEGER REFERENCES notes(id) ON DELETE SET NULL,\n link_text TEXT,\n anchor TEXT,\n line_number INTEGER,\n UNIQUE (source_note, target_path, anchor)\n);\nCREATE INDEX IF NOT EXISTS idx_wikilinks_source ON wikilinks(source_note);\nCREATE INDEX IF NOT EXISTS idx_wikilinks_target ON wikilinks(target_note);\n\n-- ── 3.3 Audit Layer ──────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS index_runs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL UNIQUE,\n vault_name TEXT NOT NULL,\n model_id INTEGER REFERENCES models(id),\n started_at INTEGER NOT NULL,\n finished_at INTEGER,\n trigger TEXT NOT NULL,\n notes_indexed INTEGER NOT NULL DEFAULT 0,\n chunks_created INTEGER NOT NULL DEFAULT 0,\n notes_updated INTEGER NOT NULL DEFAULT 0,\n notes_deleted INTEGER NOT NULL DEFAULT 0,\n error TEXT\n);\n\nCREATE TABLE IF NOT EXISTS write_audit (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n note_id INTEGER REFERENCES notes(id) ON DELETE SET NULL,\n op TEXT NOT NULL,\n previous_hash TEXT,\n new_hash TEXT,\n expected_hash TEXT,\n client_id TEXT,\n diff_summary TEXT,\n at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_write_audit_note ON write_audit(note_id);\n`;\n\n/**\n * Migration 002 — note_aliases table.\n *\n * Obsidian notes can declare `aliases: [\"short\", \"another\"]` in frontmatter.\n * A wikilink `[[short]]` should resolve to that note. We index aliases\n * separately so the wikilink resolver can do a fast lookup without\n * re-parsing every note's frontmatter.\n */\nconst MIGRATION_002_ALIASES = `\nCREATE TABLE IF NOT EXISTS note_aliases (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n alias TEXT NOT NULL,\n /* Aliases are case-insensitive matched in practice; we store original\n case for display but enforce a normalized key as UNIQUE per note. */\n alias_norm TEXT NOT NULL,\n UNIQUE (note_id, alias_norm)\n);\nCREATE INDEX IF NOT EXISTS idx_note_aliases_norm ON note_aliases(alias_norm);\n`;\n\n/**\n * Migration 003 — fix delete-cascade gaps in the wikilink + audit FKs.\n *\n * Original schema (v1) declared:\n * wikilinks.target_note REFERENCES notes(id) -- no action\n * write_audit.note_id REFERENCES notes(id) -- no action\n *\n * Both meant a `DELETE FROM notes` would FAIL whenever any other note still\n * linked to the deleted one, or when audit rows referenced it. That made\n * external/watcher/catchup deletes throw, and forced `delete_note` to\n * disable FKs entirely (leaving dangling `target_note` refs).\n *\n * The fix: rebuild both FKs.\n * - wikilinks.target_note → ON DELETE SET NULL (the link becomes broken,\n * correctly surfaced by find_broken_links)\n * - write_audit.note_id → ON DELETE SET NULL (audit history survives\n * the deletion, which is the whole point of audit)\n *\n * SQLite cannot ALTER a column's foreign-key action, so we rebuild each\n * table the standard way (create *_new, copy rows, drop, rename).\n */\nconst MIGRATION_003_FIX_DELETE_FKS = `\n-- 1) wikilinks: rebuild with ON DELETE SET NULL on target_note\nCREATE TABLE wikilinks_new (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_note INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n target_path TEXT NOT NULL,\n target_note INTEGER REFERENCES notes(id) ON DELETE SET NULL,\n link_text TEXT,\n anchor TEXT,\n line_number INTEGER,\n UNIQUE (source_note, target_path, anchor)\n);\nINSERT INTO wikilinks_new SELECT * FROM wikilinks;\nDROP TABLE wikilinks;\nALTER TABLE wikilinks_new RENAME TO wikilinks;\nCREATE INDEX IF NOT EXISTS idx_wikilinks_source ON wikilinks(source_note);\nCREATE INDEX IF NOT EXISTS idx_wikilinks_target ON wikilinks(target_note);\n\n-- 2) write_audit: rebuild with ON DELETE SET NULL on note_id\n-- note_id must allow NULL for this to work; the column was NOT NULL in v1.\n-- Existing audit rows that already reference vanished notes (residue from\n-- the pre-migration FK-OFF delete workaround) have their note_id healed\n-- to NULL during the copy — preserving audit history without re-introducing\n-- dangling refs.\nCREATE TABLE write_audit_new (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n note_id INTEGER REFERENCES notes(id) ON DELETE SET NULL,\n op TEXT NOT NULL,\n previous_hash TEXT,\n new_hash TEXT,\n expected_hash TEXT,\n client_id TEXT,\n diff_summary TEXT,\n at INTEGER NOT NULL\n);\nINSERT INTO write_audit_new (id, note_id, op, previous_hash, new_hash, expected_hash, client_id, diff_summary, at)\nSELECT\n wa.id,\n CASE WHEN n.id IS NULL THEN NULL ELSE wa.note_id END,\n wa.op, wa.previous_hash, wa.new_hash, wa.expected_hash, wa.client_id, wa.diff_summary, wa.at\nFROM write_audit wa\nLEFT JOIN notes n ON n.id = wa.note_id;\nDROP TABLE write_audit;\nALTER TABLE write_audit_new RENAME TO write_audit;\nCREATE INDEX IF NOT EXISTS idx_write_audit_note ON write_audit(note_id);\n`;\n\n/**\n * Migration 004 — variable embedding dimensions (Phase 7b).\n *\n * Original schema declared a single virtual table:\n * embeddings USING vec0(chunk_id, model_id, vector FLOAT[1024])\n * with the dim hard-wired to 1024 (qwen3-embedding default).\n *\n * Phase 7b lets multiple models with different output dimensions coexist\n * in the same vault DB (e.g. qwen3 @ 1024 + embeddinggemma @ 768). Because\n * sqlite-vec's vec0 requires a compile-time-fixed dimension per column,\n * we use one virtual table per dim: `embeddings_`.\n *\n * This migration:\n * 1) Creates `embeddings_1024` and `embeddings_768` up-front (the two\n * dims we know about today). Additional dims are materialized\n * on-demand by Database.ensureEmbeddingsTable(dim).\n * 2) Copies all rows from the legacy `embeddings` table into\n * `embeddings_1024` (since the legacy schema was 1024-only).\n * 3) Drops the legacy `embeddings` table.\n *\n * vec0 virtual tables do not support INSERT ... SELECT directly across\n * vec0 instances reliably across older sqlite-vec builds — we copy row\n * by row via a SELECT loop, materialised as a CTE-driven INSERT here.\n * For empty tables this is a no-op.\n */\nconst MIGRATION_004_VARIABLE_DIMS = `\nCREATE VIRTUAL TABLE IF NOT EXISTS embeddings_1024 USING vec0(\n chunk_id INTEGER PRIMARY KEY,\n model_id INTEGER NOT NULL,\n vector FLOAT[1024]\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS embeddings_768 USING vec0(\n chunk_id INTEGER PRIMARY KEY,\n model_id INTEGER NOT NULL,\n vector FLOAT[768]\n);\n\nINSERT INTO embeddings_1024 (chunk_id, model_id, vector)\n SELECT chunk_id, model_id, vector FROM embeddings;\n\nDROP TABLE embeddings;\n`;\n\n/**\n * Migration 005 — add `partition key` on `model_id` so two embedding models\n * with the same dim (e.g. qwen3 @ 1024 + bge-m3 @ 1024) can coexist for the\n * same chunks. Discovered as a bug during the Phase 7e eval run.\n *\n * sqlite-vec vec0 tables do not support ALTER COLUMN, so the only path is\n * rebuild-and-copy:\n * 1) For every existing `embeddings_` table:\n * a) Rename to `embeddings___old`.\n * b) Create new `embeddings_` with `model_id partition key`.\n * c) Copy all rows back. The partition column accepts ordinary inserts.\n * d) Drop the `__old` table.\n *\n * We can't write this as a single static SQL string because the set of\n * dim-tables in any given DB is data-dependent (768 only exists if someone\n * registered a 768-dim model). The runner therefore calls a function-style\n * migration: see `Migration.run()` below.\n */\nfunction runMigration005(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n // Phase 7e bugfix: split per-dim tables into per-model tables so two models\n // with the same dim (e.g. qwen3 + bge-m3, both 1024) can coexist for the\n // same chunk_ids. New naming: `embeddings_m_d`.\n //\n // The earlier partition-key approach was a dead end — sqlite-vec's\n // `partition key` is an internal index hint, NOT a composite PK; chunk_id\n // remains globally unique inside a vec0 table.\n //\n // Migration steps per legacy `embeddings_` table:\n // 1) Read all rows (grouped by model_id).\n // 2) DROP the legacy table.\n // 3) For each model_id with rows, CREATE `embeddings_m_d`\n // and copy that model's rows back.\n const rows = db\n .prepare<\n [],\n { name: string }\n >(\"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'embeddings\\\\_%' ESCAPE '\\\\'\")\n .all();\n const legacyTables: { name: string; dim: number }[] = [];\n for (const r of rows) {\n // Match only the OLD per-dim shape (`embeddings_`), not anything\n // already in the new shape.\n const m = /^embeddings_(\\d+)$/.exec(r.name);\n if (m && m[1]) legacyTables.push({ name: r.name, dim: Number(m[1]) });\n }\n\n for (const { name, dim } of legacyTables) {\n const rows = db\n .prepare<\n [],\n { chunk_id: number; model_id: number; vector: Buffer }\n >(`SELECT chunk_id, model_id, vector FROM ${name}`)\n .all();\n\n db.exec(`DROP TABLE ${name}`);\n\n // Group rows by model_id so we materialise one new table per model.\n const byModel = new Map();\n for (const row of rows) {\n let bucket = byModel.get(row.model_id);\n if (!bucket) {\n bucket = [];\n byModel.set(row.model_id, bucket);\n }\n bucket.push(row);\n }\n\n for (const [modelId, bucket] of byModel) {\n const newName = `embeddings_m${modelId}_d${dim}`;\n db.exec(\n `CREATE VIRTUAL TABLE ${newName} USING vec0(\n chunk_id INTEGER PRIMARY KEY,\n vector FLOAT[${dim}]\n )`,\n );\n const insert = db.prepare(`INSERT INTO ${newName} (chunk_id, vector) VALUES (?, ?)`);\n for (const row of bucket) {\n insert.run(BigInt(row.chunk_id), row.vector);\n }\n }\n }\n}\n\ntype BetterSqlite3Database = import(\"better-sqlite3\").Database;\n\n/**\n * Migration 006 — add `body_hash` to notes.\n *\n * Why: the existing `hash` column mixes content + frontmatter. Any\n * frontmatter-only change (e.g. `update_frontmatter` adding a tag) flips\n * the hash and forces the indexer to re-chunk + re-embed the entire\n * note. The body is unchanged — embeddings should stay untouched.\n *\n * `body_hash` = sha256(content) only — independent of frontmatter.\n * The indexer compares body_hash before deciding whether to re-embed:\n * - body_hash unchanged AND hash changed → frontmatter-only diff →\n * update note row + aliases, keep chunks/embeddings\n * - body_hash changed → full re-chunk + re-embed\n *\n * Existing rows have body_hash=NULL after this migration. The indexer\n * treats NULL as \"unknown — must recompute on next touch\" and fills it\n * in lazily during the next upsert. No backfill needed.\n */\nconst MIGRATION_006_BODY_HASH = `\nALTER TABLE notes ADD COLUMN body_hash TEXT;\nCREATE INDEX IF NOT EXISTS idx_notes_body_hash ON notes(body_hash);\n`;\n\n/**\n * Migration 007: doc_uri Strategy A — additive nullable column.\n *\n * Adds the v2 canonical identifier column to `notes`. Stored UN-ENCODED:\n * a raw forward-slash path with spaces / Unicode passed through (matches\n * the existing `path` column shape). Percent-encoding happens only at\n * formatDisplayUrl time per RESEARCH Pitfall 5.\n *\n * Indexer behavior: new writes populate doc_uri alongside path (plan 01-02\n * Task 04 wires this into NotesQueries.upsertByPath). Backfill of existing\n * rows is migration 008. Reads continue to use path as PK until phase 3 or\n * later flips read preference.\n *\n * Strategy A staging (RESEARCH §doc_uri Dual-Column Migration):\n * v7 = this migration (ADD COLUMN; nullable)\n * v8 = backfill (function-style; idempotent)\n * v9 = NOT NULL assertion + drop path PK — DEFERRED to phase 3+\n */\nconst MIGRATION_007_DOC_URI_ADD = `\nALTER TABLE notes ADD COLUMN doc_uri TEXT;\nCREATE INDEX IF NOT EXISTS idx_notes_doc_uri ON notes(doc_uri);\n`;\n\n/**\n * Migration 008: doc_uri Strategy A — backfill existing rows.\n *\n * For every notes row, derives:\n * doc_uri = 'obsidian-fs://' + ctx.vaultName + '/' + path\n *\n * Path is stored un-encoded (matches the existing `path` column shape).\n * Percent-encoding is a presentation concern handled by formatDisplayUrl\n * (per RESEARCH Pitfall 5).\n *\n * IDEMPOTENT: rows where doc_uri IS already NOT NULL are skipped. Re-running\n * the migration on a fully backfilled DB is a no-op. The runner is wrapped\n * in the existing SQLite transaction (database.ts:99) so failure rolls back.\n *\n * Requires `ctx.vaultName` (plumbed from VaultManager via Database constructor).\n * Throws clearly if vaultName is undefined — see RESEARCH §Pitfall 5 / A8.\n */\nfunction runMigration008(db: BetterSqlite3Database, ctx: MigrationContext): void {\n // Short-circuit: zero notes to backfill means we don't need vaultName at\n // all. This lets `:memory:` fresh DBs migrate cleanly without forcing\n // every test fixture to specify a vault name.\n const pending = db\n .prepare<[], { c: number }>(\"SELECT COUNT(*) AS c FROM notes WHERE doc_uri IS NULL\")\n .get();\n if (!pending || pending.c === 0) return;\n\n if (!ctx.vaultName) {\n throw new Error(\n \"runMigration008 requires vaultName context to backfill doc_uri on existing notes (Database constructor must be called with the vault name; check src/vault/manager.ts).\",\n );\n }\n const prefix = `obsidian-fs://${ctx.vaultName}/`;\n const update = db.prepare(`\n UPDATE notes\n SET doc_uri = @prefix || path\n WHERE doc_uri IS NULL\n `);\n update.run({ prefix });\n}\n\n/**\n * Migration 009 — audit discriminator for memory-sink writes (MEM-08, Plan 02-06).\n *\n * Adds an `is_memory_sink_write` column to `write_audit` so the audit log\n * can distinguish writes routed under a MemorySink (agent observations,\n * supersede records) from regular user writes. Existing v1.x rows migrate\n * with the default value 0 — they pre-date the memory namespace.\n *\n * A partial index on `(is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1`\n * keeps the common \"show me only memory writes\" filter fast without\n * widening the index footprint for user writes. Per RESEARCH §Q8: partial\n * indexes are the standard SQLite idiom for boolean discriminators where\n * one branch dominates volume.\n *\n * Function-style (not pure SQL) so the column-add is IDEMPOTENT: a test\n * fixture that rewinds `user_version` to replay earlier migrations against\n * a DB whose write_audit already carries the v9 column (because the\n * Database constructor migrated it to head on open) must not crash on a\n * duplicate-column error. The behavior of a clean v8→v9 upgrade is\n * identical to the pure-SQL form: ALTER ADD COLUMN with DEFAULT 0 +\n * partial index creation.\n */\nfunction runMigration009(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n const cols = db.prepare(\"PRAGMA table_info(write_audit)\").all() as Array<{\n name: string;\n }>;\n const hasColumn = cols.some((c) => c.name === \"is_memory_sink_write\");\n if (!hasColumn) {\n db.exec(\"ALTER TABLE write_audit ADD COLUMN is_memory_sink_write INTEGER NOT NULL DEFAULT 0\");\n }\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_write_audit_memory\n ON write_audit(is_memory_sink_write, at DESC)\n WHERE is_memory_sink_write = 1\n `);\n}\n\n/**\n * Migration 010 — Phase 3 (slice 03-01) sections infrastructure.\n *\n * Three ordered steps inside ONE transaction (per plan 03-01):\n * A) `sections` table + 3 indexes (DDL).\n * B) Denormalized `notes.status` column + UPDATE backfill from\n * `json_extract(frontmatter, '$.status')` + partial index\n * `notes_status WHERE status IS NOT NULL`.\n * C) Function-style call to `backfillSectionsFromChunks(db)` —\n * one-time backfill of `sections` rows for existing notes\n * (M2 fix from the plan-checker). Re-derives sections from each\n * note's `content` column, NOT from `chunks.heading_path` (see\n * 03-01-DEVIATIONS.md §D1 for why).\n *\n * Function-style so we can interleave SQL + a TS helper call inside the\n * same transaction. The runner is already inside `db.transaction(...)`\n * at `src/db/database.ts:114` — calling `db.exec` from here participates\n * in that outer transaction by default with better-sqlite3.\n *\n * IDEMPOTENCY: the v1 migration runner only runs migrations whose\n * version > `user_version` so this function executes at most once per\n * DB. As a defence-in-depth measure the steps are still individually\n * idempotent (column-add via PRAGMA introspection; `CREATE TABLE IF\n * NOT EXISTS`; backfill helper short-circuits when rows already exist\n * for a note).\n */\nfunction runMigration010(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n // ── Step A: sections table + 3 indexes ────────────────────────────\n // Use `IF NOT EXISTS` so a fixture replay against a DB whose v10\n // schema already exists does not crash. The composite indexes match\n // the plan's read patterns:\n // - sections_note_anchor: O(log) unique lookup by (note_id, anchor)\n // - sections_note_parent_ord: O(log) tree iteration in get_outline\n // - sections_chunk_range: O(log) chunk → section promotion\n db.exec(`\n CREATE TABLE IF NOT EXISTS sections (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n anchor TEXT NOT NULL,\n heading_path TEXT NOT NULL,\n heading_text TEXT NOT NULL,\n level INTEGER NOT NULL,\n parent_id INTEGER REFERENCES sections(id) ON DELETE CASCADE,\n ord INTEGER NOT NULL,\n chunk_id_first INTEGER REFERENCES chunks(id),\n chunk_id_last INTEGER REFERENCES chunks(id),\n created_at INTEGER NOT NULL\n );\n CREATE UNIQUE INDEX IF NOT EXISTS sections_note_anchor\n ON sections(note_id, anchor);\n CREATE INDEX IF NOT EXISTS sections_note_parent_ord\n ON sections(note_id, parent_id, ord);\n CREATE INDEX IF NOT EXISTS sections_chunk_range\n ON sections(note_id, chunk_id_first, chunk_id_last);\n `);\n\n // ── Step B: notes.status denormalized column (M4 fix) ─────────────\n // Idempotent column-add: check PRAGMA table_info first. `notes.status`\n // is read by 03-05's superseded SQL filter and maintained by the\n // indexer via `NotesQueries.setStatus(noteId, parsedProperties.status\n // ?? null)`.\n const cols = db.prepare(\"PRAGMA table_info(notes)\").all() as Array<{ name: string }>;\n const hasStatus = cols.some((c) => c.name === \"status\");\n if (!hasStatus) {\n db.exec(\"ALTER TABLE notes ADD COLUMN status TEXT\");\n }\n // Backfill `status` from existing JSON-stringified `notes.frontmatter`.\n // `notes.frontmatter` is stored as a JSON string (verified at\n // src/indexer/indexer.ts:168 — `JSON.stringify(parsed.frontmatter)`).\n // `json_extract` handles malformed JSON by returning NULL, so notes\n // with corrupt/missing frontmatter end up with `status: NULL` —\n // exactly the correct behavior.\n db.exec(`\n UPDATE notes\n SET status = json_extract(frontmatter, '$.status')\n WHERE frontmatter IS NOT NULL\n AND status IS NULL\n `);\n // Partial index: tiny footprint, only indexes rows with a non-null\n // status. Most notes have no status — the index stays small even on\n // large vaults.\n db.exec(`\n CREATE INDEX IF NOT EXISTS notes_status\n ON notes(status) WHERE status IS NOT NULL\n `);\n\n // ── Step C: section backfill (M2 fix) ─────────────────────────────\n // Re-derive sections from each note's `content` column. The helper\n // is co-located in `src/sections/backfill.ts` so the migration\n // module stays adapter-import-clean.\n backfillSectionsFromChunks(db);\n}\n\n/**\n * Migration 011 — Phase 4 / 04-01 / GRA-04 (D-01): `edges` table substrate.\n *\n * Lands the typed-edge graph storage that every Phase 4 surface\n * (`expand`, `cluster`, `search_hybrid({expand})`, the widened v1 graph\n * tools, bundle/dossier link entries) reads from. Mirrors the\n * established function-style backfill pattern from `runMigration008`\n * (lines 443–464) and the multi-step DDL+helper pattern from\n * `runMigration010` (lines 531–596).\n *\n * Three steps inside ONE transaction (the runner's outer transaction\n * from `database.ts:118`):\n *\n * A) DDL — `edges` table + 3 indexes. Idempotent (`IF NOT EXISTS`).\n * Columns match D-01 and `Edge.type` union from `src/types.ts:470`:\n * `(id, source_doc, target_doc, target_path, type, rel, anchor,\n * line_number)` with `UNIQUE(source_doc, target_doc, type,\n * anchor)` for `INSERT OR IGNORE` idempotency.\n * FKs: `source_doc REFERENCES notes(id) ON DELETE CASCADE`,\n * `target_doc REFERENCES notes(id) ON DELETE SET NULL`.\n * CHECK constraint on `type` mirrors `Edge.type` verbatim.\n *\n * B) Zero-row short-circuit (mirrors `runMigration008` lines 444–448):\n * if `wikilinks` is empty, skip backfill scan entirely. Keeps fresh\n * `:memory:` test fixtures fast and avoids needless work on\n * vaults that have no v1 wikilinks to migrate.\n *\n * C) Chunked backfill — copies every row from `wikilinks` into\n * `edges` with `type='wikilink'`. Chunked at 10,000 rows per\n * batch (per RESEARCH §Pattern 1 / Pitfall 5). better-sqlite3\n * is synchronous, so a multi-second backfill of a 100k+ wikilink\n * vault must not block the event loop in one statement —\n * chunking keeps each statement bounded. Pagination via\n * `wikilinks.id > @after_id` + `LIMIT @chunk` (Pattern 1).\n * `INSERT OR IGNORE` + the UNIQUE constraint make the backfill\n * idempotent across partial-migration replays.\n *\n * Storage cost: ~doubling on the wikilink subset until v3 cleanup\n * drops the `wikilinks` table. Acceptable per D-01.\n *\n * No `fs`, `path.join`, or `gray-matter` imports anywhere in this\n * function (adapter-seam discipline, ADR-002).\n */\nfunction runMigration011(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n // ── Step A: DDL ──────────────────────────────────────────────────────\n //\n // D-01 names UNIQUE(source_doc, target_doc, type, anchor) but `target_doc`\n // and `anchor` are both nullable, and SQLite's standard UNIQUE constraint\n // treats every NULL as distinct (per the SQL spec the codebase already\n // relies on at `notes(path)` etc.). Without further accommodation,\n // INSERT OR IGNORE would fail to dedupe broken edges (target_doc IS NULL,\n // anchor IS NULL — two rows with the same source+type would both insert).\n //\n // The fix: a UNIQUE INDEX with COALESCE on the nullable columns. This is\n // the standard SQLite idiom for \"treat NULL as equal for dedup\" and is\n // semantically identical to D-01's intent.\n //\n // COALESCE(target_doc, -1) — `-1` is safe because `notes.id` is\n // AUTOINCREMENT starting at 1; no real note id can collide.\n // COALESCE(anchor, '') — empty string acts as the \"no anchor\" key;\n // real anchors are non-empty strings (Obsidian wikilink syntax\n // `[[note#section]]` rejects empty `#`).\n //\n // INSERT OR IGNORE consults the unique index for conflict resolution\n // (`ON CONFLICT IGNORE` semantics propagate from any unique constraint\n // or unique index — per SQLite docs §\"INSERT ... OR IGNORE\").\n db.exec(`\n CREATE TABLE IF NOT EXISTS edges (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_doc INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n target_doc INTEGER REFERENCES notes(id) ON DELETE SET NULL,\n target_path TEXT,\n type TEXT NOT NULL CHECK (type IN ('wikilink','mention','frontmatter-ref','hyperlink')),\n rel TEXT,\n anchor TEXT,\n line_number INTEGER,\n link_text TEXT\n );\n CREATE UNIQUE INDEX IF NOT EXISTS idx_edges_unique\n ON edges(source_doc, COALESCE(target_doc, -1), type, COALESCE(anchor, ''));\n CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_doc);\n CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_doc);\n CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);\n `);\n\n // ── Step B: zero-row short-circuit ───────────────────────────────────\n // Mirrors runMigration008 lines 444–448. Fresh DBs have no wikilinks\n // to backfill; skip the scan entirely.\n const pending = db.prepare<[], { c: number }>(\"SELECT COUNT(*) AS c FROM wikilinks\").get();\n if (!pending || pending.c === 0) return;\n\n // ── Step C: chunked backfill from wikilinks → edges ──────────────────\n // Chunked at 10k rows (RESEARCH §Pattern 1). Pagination via\n // `wikilinks.id > @after_id ORDER BY id ASC LIMIT @chunk`. INSERT OR\n // IGNORE + UNIQUE(source_doc, target_doc, type, anchor) is idempotent\n // across replays.\n //\n // NOTE: wikilink rows can have `target_path` IS NOT NULL while\n // `target_note` IS NULL (broken wikilinks). Those land in `edges`\n // with `target_doc IS NULL` and `target_path` preserved — `edges`\n // mirrors the unresolved-target convention from `wikilinks`.\n const CHUNK = 10_000;\n const copy = db.prepare(`\n INSERT OR IGNORE INTO edges\n (source_doc, target_doc, target_path, type, rel, anchor, line_number, link_text)\n SELECT source_note, target_note, target_path, 'wikilink', NULL, anchor, line_number, link_text\n FROM wikilinks\n WHERE id > @after_id\n ORDER BY id ASC\n LIMIT @chunk\n `);\n // `nextLastIdAfter(@after_id, @chunk)` returns the wikilinks.id at\n // position @chunk-th row past @after_id, OR undefined if fewer than\n // @chunk rows remain — which signals the final partial chunk.\n const nextLast = db.prepare<[number, number], { id: number }>(\n \"SELECT id FROM wikilinks WHERE id > ? ORDER BY id ASC LIMIT 1 OFFSET ?\",\n );\n\n let lastId = 0;\n while (true) {\n copy.run({ after_id: lastId, chunk: CHUNK });\n const nxt = nextLast.get(lastId, CHUNK - 1);\n if (!nxt) break;\n lastId = nxt.id;\n }\n}\n\n/**\n * Migration 012 — Phase 4 / CR-01: widen `idx_edges_unique` so that\n * legitimate non-duplicate edges no longer collide on `INSERT OR IGNORE`.\n *\n * The original migration-011 unique index was\n * `(source_doc, COALESCE(target_doc, -1), type, COALESCE(anchor, ''))`\n * which silently dropped four classes of distinct rows:\n *\n * 1. Multiple broken wikilinks from the same source (different\n * `target_path` but both have `target_doc IS NULL` + `anchor IS NULL`).\n * 2. Multiple hyperlinks from the same source (`target_doc IS NULL`,\n * `anchor IS NULL` — only one survives per source note).\n * 3. Multiple `frontmatter-ref` edges from the same source to the same\n * target with different `rel` (e.g. `{owner: [[a]], assignee: [[a]]}`).\n * 4. Multi-line `mention` edges to the same target (line_number was not\n * a disambiguator).\n *\n * The widened key includes `target_path`, `rel`, and `line_number` (with\n * `COALESCE` defaults for nulls so SQLite's \"every NULL is distinct\"\n * default does not re-introduce the dedup-failure on the inverse axis).\n *\n * Three steps inside the runner's outer transaction:\n * A) DROP idx_edges_unique. SQLite cannot alter a unique-index\n * definition in place — drop + recreate is the only path.\n * B) CREATE the widened idx_edges_unique. If a re-run finds the wider\n * index already exists (e.g. partial-replay against a DB that was\n * hand-fixed), `IF NOT EXISTS` keeps the migration idempotent.\n * C) Re-run the wikilink backfill from migration 011 (broken-link rows\n * were lost during the narrow-key window between 011 and 012). The\n * backfill uses `INSERT OR IGNORE` against the now-widened key so\n * the rows that already survived stay untouched and the rows that\n * were silently dropped are re-inserted.\n *\n * Cross-table FKs on `edges` are untouched. CHECK constraint on\n * `edges.type` is untouched. Read paths (`getBacklinks`, `getForwardLinks`,\n * `getAllForNodes`) are untouched — they SELECT, never INSERT.\n *\n * Adapter-seam discipline: no `fs`, `path`, `gray-matter`, or `chokidar`\n * imports anywhere in this function.\n */\nfunction runMigration012(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n // ── Step A: drop the narrow index ─────────────────────────────────────\n db.exec(`DROP INDEX IF EXISTS idx_edges_unique`);\n\n // ── Step B: create the widened index ──────────────────────────────────\n //\n // COALESCE defaults:\n // target_doc → -1 (notes.id is AUTOINCREMENT from 1; -1 cannot\n // collide with a real note id)\n // target_path → '' (real target_path values are non-empty strings)\n // rel → '' (real rel values are non-empty per ADR-003)\n // anchor → '' (Obsidian wikilink `[[note#]]` rejects empty)\n // line_number → -1 (real line numbers are 1-based positive ints)\n db.exec(`\n CREATE UNIQUE INDEX IF NOT EXISTS idx_edges_unique\n ON edges(\n source_doc,\n COALESCE(target_doc, -1),\n COALESCE(target_path, ''),\n type,\n COALESCE(rel, ''),\n COALESCE(anchor, ''),\n COALESCE(line_number, -1)\n );\n `);\n\n // ── Step C: re-run the wikilink → edges backfill ──────────────────────\n //\n // Broken-wikilink rows were lost during the narrow-key window because\n // migration 011 used `INSERT OR IGNORE` against a key that collapsed\n // every `(source_note, target_path=*, anchor=NULL)` row to a single\n // edges row. The widened key now distinguishes broken targets by\n // `target_path`. Re-running the same chunked copy with the same\n // `INSERT OR IGNORE` guard is idempotent on the already-correct rows\n // and refills the gaps.\n //\n // Mirrors runMigration011 Step C verbatim (chunked at 10k rows,\n // pagination via wikilinks.id > @after_id ORDER BY id ASC LIMIT\n // @chunk). The zero-row short-circuit also mirrors 011 — fresh DBs\n // do not need the backfill scan.\n const pending = db.prepare<[], { c: number }>(\"SELECT COUNT(*) AS c FROM wikilinks\").get();\n if (!pending || pending.c === 0) return;\n\n const CHUNK = 10_000;\n const copy = db.prepare(`\n INSERT OR IGNORE INTO edges\n (source_doc, target_doc, target_path, type, rel, anchor, line_number, link_text)\n SELECT source_note, target_note, target_path, 'wikilink', NULL, anchor, line_number, link_text\n FROM wikilinks\n WHERE id > @after_id\n ORDER BY id ASC\n LIMIT @chunk\n `);\n const nextLast = db.prepare<[number, number], { id: number }>(\n \"SELECT id FROM wikilinks WHERE id > ? ORDER BY id ASC LIMIT 1 OFFSET ?\",\n );\n\n let lastId = 0;\n while (true) {\n copy.run({ after_id: lastId, chunk: CHUNK });\n const nxt = nextLast.get(lastId, CHUNK - 1);\n if (!nxt) break;\n lastId = nxt.id;\n }\n}\n\n/**\n * Migration 013 — Phase 5 / BRF-* / D-04..D-06 / D-09.\n *\n * Three additive substrates land at this version:\n *\n * A) `chunks.chunk_id_fragment TEXT NOT NULL DEFAULT ''` column +\n * chunked backfill (10k rows per batch, mirrors `runMigration008`).\n * Per D-04/D-05 the fragment is `sha256(NFC(LF-normalized,\n * trimEnd(text))).slice(0,7)`. The canonical computation lives in\n * `src/chunker/chunk-id.ts` so the migration and the chunker share\n * a single source of truth (anti-pattern: scattered createHash\n * calls — see RESEARCH §Pitfall 14).\n *\n * B) `brief_sources(brief_doc_id, chunk_id_fragment, chunk_doc_id,\n * recorded_hash)` reverse-index table per D-06 with\n * UNIQUE(brief_doc_id, chunk_id_fragment) and indexes on\n * `(chunk_doc_id)` and `(chunk_id_fragment)`. Populated on brief\n * write in slice 2 (Plan 05-02); rows deleted on brief\n * delete/supersede. Staleness check on a ChangeEvent for `doc_id D`\n * becomes O(log N) — `SELECT brief_doc_id FROM brief_sources WHERE\n * chunk_doc_id = D AND recorded_hash != `.\n *\n * C) `daemon_state(vault_name PRIMARY KEY, last_seen_doc_mtime)` per\n * D-09. Used by the staleness daemon (Plan 05-03) for the hybrid\n * replay strategy: startup full scan (correctness floor) + cursor\n * for steady-state diagnostic (\"is my daemon current?\").\n *\n * Step ordering inside the runner's outer transaction:\n * A.1 — DDL idempotency for `chunks.chunk_id_fragment` column-add\n * (PRAGMA introspection per `runMigration009:489-497`).\n * A.2 — Zero-row short-circuit (mirrors `runMigration008:447-450`)\n * on `COUNT(*) WHERE chunk_id_fragment = ''` so fresh DBs and\n * already-backfilled DBs both skip the scan.\n * A.3 — Chunked backfill at CHUNK = 10_000 (matches\n * `runMigration011:701`). Pagination via `id > @after_id ORDER\n * BY id ASC LIMIT 10000`. Each batch wraps a transaction so a\n * multi-second backfill on a 100k+ chunk vault does not freeze\n * the event loop (better-sqlite3 is synchronous).\n * B. — `CREATE TABLE IF NOT EXISTS brief_sources` + indexes.\n * C. — `CREATE TABLE IF NOT EXISTS daemon_state`.\n *\n * Adapter-seam discipline: no `fs`, `path`, `gray-matter`, or\n * `chokidar` imports anywhere in this function. The chunker helper\n * imported here is itself pure (`src/chunker/chunk-id.ts`).\n */\nfunction runMigration013(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n // ── Step A.1: chunks.chunk_id_fragment column-add (idempotent) ─────\n const cols = db.prepare(\"PRAGMA table_info(chunks)\").all() as Array<{\n name: string;\n }>;\n const hasColumn = cols.some((c) => c.name === \"chunk_id_fragment\");\n if (!hasColumn) {\n db.exec(\"ALTER TABLE chunks ADD COLUMN chunk_id_fragment TEXT NOT NULL DEFAULT ''\");\n }\n\n // ── Step A.2: zero-row short-circuit ──────────────────────────────\n // Skip the backfill scan entirely on fresh `:memory:` DBs and on\n // re-runs against an already-backfilled DB. Mirrors\n // runMigration008:447-450.\n const pending = db\n .prepare<[], { c: number }>(\"SELECT COUNT(*) AS c FROM chunks WHERE chunk_id_fragment = ''\")\n .get();\n if (pending && pending.c > 0) {\n // ── Step A.3: chunked backfill at 10k rows/batch ────────────────\n const CHUNK = 10_000;\n const update = db.prepare(\"UPDATE chunks SET chunk_id_fragment = ? WHERE id = ?\");\n const select = db.prepare<[number], { id: number; text: string }>(\n \"SELECT id, text FROM chunks WHERE id > ? AND chunk_id_fragment = '' ORDER BY id ASC LIMIT 10000\",\n );\n let afterId = 0;\n while (true) {\n const rows = select.all(afterId);\n if (rows.length === 0) break;\n const tx = db.transaction((batch: { id: number; text: string }[]) => {\n for (const row of batch) {\n update.run(computeChunkIdFragment(row.text), row.id);\n }\n });\n tx(rows);\n const last = rows[rows.length - 1];\n if (!last) break;\n afterId = last.id;\n if (rows.length < CHUNK) break;\n }\n }\n\n // ── Step B: brief_sources reverse-index table + indexes ───────────\n db.exec(`\n CREATE TABLE IF NOT EXISTS brief_sources (\n brief_doc_id TEXT NOT NULL,\n chunk_id_fragment TEXT NOT NULL,\n chunk_doc_id TEXT NOT NULL,\n recorded_hash TEXT NOT NULL,\n UNIQUE(brief_doc_id, chunk_id_fragment)\n );\n CREATE INDEX IF NOT EXISTS idx_brief_sources_chunk_doc\n ON brief_sources(chunk_doc_id);\n CREATE INDEX IF NOT EXISTS idx_brief_sources_fragment\n ON brief_sources(chunk_id_fragment);\n `);\n\n // ── Step C: daemon_state single-row-per-vault state ───────────────\n db.exec(`\n CREATE TABLE IF NOT EXISTS daemon_state (\n vault_name TEXT PRIMARY KEY,\n last_seen_doc_mtime INTEGER NOT NULL\n );\n `);\n}\n\n/**\n * Migration 014 — Phase 6 / Q-AUD: contract_audit table.\n *\n * DDL-only (no backfill — contract_audit is greenfield). Mirrors the\n * additive substrate pattern from `runMigration013` (Phase 5, brief_sources).\n *\n * Rationale (Q-AUD): orchestration steps cannot live in `write_audit`\n * because `write_audit.note_id INTEGER NOT NULL` foreign-key constraint\n * blocks rows that don't correspond to a vault note (orchestration rows\n * may reference DocIds, peer-MCP outputs, or load errors with no\n * note_id). Same wall Phase 5 daemon hit per RESEARCH §Don't Hand-Roll.\n *\n * Stores only `{kind, contract, verb, step_alias, vault, ts, error_message}`\n * — never step output payloads (Security pattern §I; Invariant C-5 in\n * ADR-006). Peer-MCP outputs may contain sensitive data; we explicitly\n * do not capture them.\n *\n * Adapter-seam discipline: no `fs`, `path`, `gray-matter`, or `chokidar`\n * imports anywhere in this function.\n */\nfunction runMigration014(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS contract_audit (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL,\n contract TEXT,\n verb TEXT,\n step_alias TEXT,\n vault TEXT,\n ts INTEGER NOT NULL,\n error_message TEXT\n );\n CREATE INDEX IF NOT EXISTS idx_contract_audit_kind_ts\n ON contract_audit(kind, ts);\n CREATE INDEX IF NOT EXISTS idx_contract_audit_verb\n ON contract_audit(verb);\n `);\n}\n\n/**\n * Migration 015 — section identity becomes (note_id, heading_path, anchor).\n *\n * Per ADR-032 (revised): a section's identity is its content PLUS its\n * location/context, not content alone. The original UNIQUE(note_id, anchor)\n * collapsed two byte-identical sibling sections into one row even when they\n * sat under different parent headings (e.g. `# Q1 > ## Risks \"TBD\"` and\n * `# Q2 > ## Risks \"TBD\"`) — discarding the context that distinguishes them.\n *\n * `anchor` stays a pure content hash (ADR-003 H-7 unchanged; brief\n * `source_hashes` per D-05 unaffected). We only widen the UNIQUE key to add\n * `heading_path` (the ancestor chain), so differently-placed sections persist\n * as distinct rows. Genuinely-duplicated content in the SAME context\n * (verbatim repeat under the same parent) still collapses — acceptable.\n *\n * Migration is index-only: drop the old unique index, create the new one.\n * Existing DBs may have already-collapsed rows from the old behavior; this\n * migration cannot resurrect siblings dropped before it ran, but the next\n * `index --full` regenerates them correctly. No data is lost or rewritten.\n *\n * Adapter-seam discipline: no fs/path/gray-matter/chokidar imports.\n */\nfunction runMigration015(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n db.exec(\n \"DROP INDEX IF EXISTS sections_note_anchor; \" +\n \"CREATE UNIQUE INDEX IF NOT EXISTS sections_note_headingpath_anchor \" +\n \"ON sections(note_id, heading_path, anchor);\",\n );\n}\n\n/**\n * Migration 016 — notes.rendered_source_hash (ADR-033).\n *\n * Marks a note whose indexed content came from the Obsidian plugin's RENDERED\n * Datacore/Dataview output rather than the raw file. The value is the source\n * file's hash at render time, so the watcher/CLI can detect when a rendered\n * overlay has gone stale (source changed since the render) and fall back to\n * raw re-indexing. NULL (the default for every existing + raw-indexed row)\n * means \"raw-indexed\" — fully backwards-compatible, no row rewrite.\n *\n * Idempotent: PRAGMA-guard the column-add so a replay against a DB that\n * already has the column is a no-op.\n *\n * Adapter-seam discipline: no fs/path/gray-matter/chokidar imports.\n */\nfunction runMigration016(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n const cols = db.prepare(\"PRAGMA table_info(notes)\").all() as Array<{ name: string }>;\n if (!cols.some((c) => c.name === \"rendered_source_hash\")) {\n db.exec(\"ALTER TABLE notes ADD COLUMN rendered_source_hash TEXT\");\n }\n}\n\nexport const MIGRATIONS: readonly Migration[] = [\n {\n version: 1,\n description: \"initial schema\",\n sql: INITIAL_SCHEMA,\n },\n {\n version: 2,\n description: \"note aliases for wikilink resolution\",\n sql: MIGRATION_002_ALIASES,\n },\n {\n version: 3,\n description: \"fix delete-cascade gaps in wikilinks + write_audit FKs\",\n sql: MIGRATION_003_FIX_DELETE_FKS,\n },\n {\n version: 4,\n description: \"variable embedding dimensions (split embeddings table per dim)\",\n sql: MIGRATION_004_VARIABLE_DIMS,\n },\n {\n version: 5,\n description: \"add partition key on model_id (two models per dim can coexist)\",\n run: runMigration005,\n },\n {\n version: 6,\n description: \"add body_hash for frontmatter-only-change short-circuit\",\n sql: MIGRATION_006_BODY_HASH,\n },\n {\n version: 7,\n description: \"add doc_uri column to notes (Strategy A, additive)\",\n sql: MIGRATION_007_DOC_URI_ADD,\n },\n {\n version: 8,\n description: \"backfill doc_uri from /path\",\n run: runMigration008,\n },\n {\n version: 9,\n description:\n \"audit discriminator — is_memory_sink_write column + partial index (MEM-08, Plan 02-06)\",\n run: runMigration009,\n },\n {\n version: 10,\n description:\n \"sections table + notes.status denormalization + one-time section backfill (Phase 3 / 03-01)\",\n run: runMigration010,\n },\n {\n version: 11,\n description: \"edges table + backfill from wikilinks (Phase 4 / 04-01 / GRA-04)\",\n run: runMigration011,\n },\n {\n version: 12,\n description:\n \"widen idx_edges_unique to include target_path/rel/line_number; re-run wikilink backfill (CR-01)\",\n run: runMigration012,\n },\n {\n version: 13,\n description:\n \"chunks.chunk_id_fragment + brief_sources + daemon_state (Phase 5 / BRF-* / D-04..D-06 / D-09)\",\n run: runMigration013,\n },\n {\n version: 14,\n description: \"contract_audit table — Phase 6 / CON-* / Q-AUD\",\n run: runMigration014,\n },\n {\n version: 15,\n description:\n \"section identity = (note_id, heading_path, anchor) — context-aware, no longer collapse byte-identical siblings in different contexts (ADR-032 revised)\",\n run: runMigration015,\n },\n {\n version: 16,\n description:\n \"notes.rendered_source_hash — overlay marker for plugin-rendered Datacore content (ADR-033)\",\n run: runMigration016,\n },\n];\n","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { NoteRow } from \"../../types.js\";\n\n/**\n * Default cap on `listByPathPrefix` row count. Sized to cover any\n * realistic v2.0.0 sink (sinks hold tens of documents at most). The\n * cap is exposed as a constant so consumers (e.g. `memory-stats`\n * Resource at `src/memory/resources/memory-stats.ts`) can detect\n * when the cap was hit and emit `truncated: true` (IN-03 closure).\n */\nexport const LIST_BY_PATH_PREFIX_DEFAULT_LIMIT = 10_000;\n\nexport interface UpsertNoteInput {\n path: string;\n content: string;\n frontmatter: string | null;\n title: string;\n hash: string;\n /** Body-only SHA-256. Used by indexer's frontmatter-only-change\n * short-circuit (migration 006). */\n bodyHash: string;\n mtime: number;\n wordCount: number;\n /**\n * v2 canonical identifier (plan 01-02 Task 04). When provided, written\n * verbatim into the `doc_uri` column. When omitted but `vaultName` IS\n * provided, the writer synthesizes `obsidian-fs:///`\n * un-encoded. When both are omitted, the column is left NULL — the\n * v8 backfill catches it on the next migration replay.\n *\n * UPDATE semantics: an undefined `docUri` on an existing row PRESERVES\n * the existing value via SQL COALESCE — callers can safely omit the\n * field on edit-style upserts without clobbering data.\n */\n docUri?: string;\n /**\n * Vault name used only to synthesize a default `docUri` when the caller\n * hasn't precomputed one. Indexer / write-path callers that already know\n * the vault SHOULD pass this so new rows ship with doc_uri populated.\n */\n vaultName?: string;\n}\n\nexport class NotesQueries {\n private readonly _selectByPath: BetterSqlite3.Statement<[string], NoteRow>;\n private readonly _selectById: BetterSqlite3.Statement<[number], NoteRow>;\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _update: BetterSqlite3.Statement;\n private readonly _delete: BetterSqlite3.Statement<[string]>;\n private readonly _listAll: BetterSqlite3.Statement<[number, number], NoteRow>;\n private readonly _count: BetterSqlite3.Statement<[], { c: number }>;\n /** Phase 3 / 03-01 (M4): denormalized `notes.status` accessors. */\n private readonly _getStatus: BetterSqlite3.Statement<[number], { status: string | null }>;\n private readonly _setStatus: BetterSqlite3.Statement;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._selectByPath = db.prepare<[string], NoteRow>(\"SELECT * FROM notes WHERE path = ?\");\n this._selectById = db.prepare<[number], NoteRow>(\"SELECT * FROM notes WHERE id = ?\");\n this._insert = db.prepare(`\n INSERT INTO notes (path, content, frontmatter, title, hash, body_hash, doc_uri, mtime, word_count, created_at, updated_at)\n VALUES (@path, @content, @frontmatter, @title, @hash, @body_hash, @doc_uri, @mtime, @word_count, @now, @now)\n `);\n // doc_uri uses COALESCE(@doc_uri, doc_uri) so that a caller passing\n // undefined / null PRESERVES the existing value instead of clobbering it.\n // See UpsertNoteInput.docUri TSDoc and plan 01-02 W3 caveat.\n this._update = db.prepare(`\n UPDATE notes\n SET content = @content,\n frontmatter = @frontmatter,\n title = @title,\n hash = @hash,\n body_hash = @body_hash,\n doc_uri = COALESCE(@doc_uri, doc_uri),\n mtime = @mtime,\n word_count = @word_count,\n updated_at = @now\n WHERE id = @id\n `);\n this._delete = db.prepare(\"DELETE FROM notes WHERE path = ?\");\n this._listAll = db.prepare<[number, number], NoteRow>(\n \"SELECT * FROM notes ORDER BY id LIMIT ? OFFSET ?\",\n );\n this._count = db.prepare<[], { c: number }>(\"SELECT COUNT(*) AS c FROM notes\");\n // Phase 3 / 03-01 (M4): denormalized `notes.status` column accessors.\n // Prepared statements MUST be created AFTER migration v10 added the\n // column. The Database constructor runs migrate() before instantiating\n // any query class (`src/db/database.ts:57`), so this ordering holds.\n this._getStatus = db.prepare<[number], { status: string | null }>(\n \"SELECT status FROM notes WHERE id = ?\",\n );\n this._setStatus = db.prepare(\"UPDATE notes SET status = @status WHERE id = @id\");\n }\n\n upsertByPath(input: UpsertNoteInput): { id: number; isNew: boolean } {\n const existing = this._selectByPath.get(input.path);\n const now = Date.now();\n // doc_uri resolution: explicit > synthesized-from-vaultName > NULL.\n // NULL is acceptable during the Phase 1 dual-column window — migration\n // 008 backfills it on the next replay, and Phase 3+ flips reads.\n const docUri: string | null =\n input.docUri ??\n (input.vaultName !== undefined ? `obsidian-fs://${input.vaultName}/${input.path}` : null);\n if (existing) {\n if (existing.hash === input.hash) {\n return { id: existing.id, isNew: false };\n }\n this._update.run({\n id: existing.id,\n content: input.content,\n frontmatter: input.frontmatter,\n title: input.title,\n hash: input.hash,\n body_hash: input.bodyHash,\n // Pass null when the caller didn't compute one — COALESCE in the\n // UPDATE statement keeps the existing doc_uri intact.\n doc_uri: docUri,\n mtime: input.mtime,\n word_count: input.wordCount,\n now,\n });\n return { id: existing.id, isNew: false };\n }\n const info = this._insert.run({\n path: input.path,\n content: input.content,\n frontmatter: input.frontmatter,\n title: input.title,\n hash: input.hash,\n body_hash: input.bodyHash,\n doc_uri: docUri,\n mtime: input.mtime,\n word_count: input.wordCount,\n now,\n });\n return { id: Number(info.lastInsertRowid), isNew: true };\n }\n\n getById(id: number): NoteRow | null {\n return this._selectById.get(id) ?? null;\n }\n\n getByPath(path: string): NoteRow | null {\n return this._selectByPath.get(path) ?? null;\n }\n\n deleteByPath(path: string): boolean {\n const info = this._delete.run(path);\n return info.changes > 0;\n }\n\n listAll(limit = 1000, offset = 0): NoteRow[] {\n return this._listAll.all(limit, offset);\n }\n\n countAll(): number {\n const row = this._count.get();\n return row?.c ?? 0;\n }\n\n /**\n * Plan 02-06 (MEM-09): count rows whose `path` begins with the given\n * prefix. Used by the `memory-stats` MCP Resource to count documents\n * inside a `MemorySink` (the sink's `resolveToRelativePath` is the\n * prefix, with trailing slash). The path is bound as a parameter; the\n * `prefix` value MUST end with `/` to keep the match well-defined.\n */\n countByPathPrefix(prefix: string): number {\n const row = this.db\n .prepare<\n [string],\n { c: number }\n >(\"SELECT COUNT(*) AS c FROM notes WHERE path LIKE ? ESCAPE '\\\\'\")\n .get(escapeLikePrefix(prefix) + \"%\");\n return row?.c ?? 0;\n }\n\n /**\n * Plan 02-06 (MEM-09): list rows whose `path` begins with the given\n * prefix. Used by the `memory-stats` MCP Resource to aggregate\n * `by_type` / `by_status` counts from the stored frontmatter JSON.\n * Default limit is `LIST_BY_PATH_PREFIX_DEFAULT_LIMIT` (10_000) —\n * sinks are user-scoped and typically hold tens of documents in\n * v2.0.0; the cap exists only as a hedge against pathological sinks.\n * Callers that need to detect cap-hit (e.g. memory-stats `truncated`\n * marker, IN-03) compare `rows.length === LIST_BY_PATH_PREFIX_DEFAULT_LIMIT`.\n */\n listByPathPrefix(prefix: string, limit = LIST_BY_PATH_PREFIX_DEFAULT_LIMIT): NoteRow[] {\n return this.db\n .prepare<\n [string, number],\n NoteRow\n >(\"SELECT * FROM notes WHERE path LIKE ? ESCAPE '\\\\' ORDER BY path LIMIT ?\")\n .all(escapeLikePrefix(prefix) + \"%\", limit);\n }\n\n /**\n * Phase 3 / 03-01 (M4): read the denormalized `notes.status` column.\n * Returns `null` for unknown note IDs or notes with no status. Reads\n * the column directly (avoids re-parsing the JSON frontmatter blob).\n *\n * Maintained in sync with `notes.frontmatter` by the indexer — every\n * write that touches `notes.frontmatter` MUST call `setStatus(...)`\n * immediately after so the column doesn't drift.\n */\n getStatus(noteId: number): string | null {\n const row = this._getStatus.get(noteId);\n return row?.status ?? null;\n }\n\n /**\n * Phase 3 / 03-01 (M4): write the denormalized `notes.status` column.\n * `null` clears the column (frontmatter removed the status key).\n * Returns the number of rows affected (0 for unknown note IDs).\n */\n setStatus(noteId: number, status: string | null): number {\n const info = this._setStatus.run({ id: noteId, status });\n return info.changes;\n }\n\n /**\n * Phase 3 / 03-05 (M4): return the subset of `chunkIds` whose owning\n * note has `notes.status = 'superseded'`. Used by `searchOneVault` to\n * filter the vec0 ANN candidate list at the SQL level after the kNN\n * search (vec0 virtual tables do not support inline JOINs the way\n * FTS5 does).\n *\n * Uses the `notes_status` partial index (migration 010) — superseded\n * notes are rare, so the index is tiny and lookups are cheap.\n *\n * The query parameterizes a variable-length IN clause; we generate\n * the placeholders inline rather than re-preparing the statement\n * because the chunk-id list varies per call. better-sqlite3's\n * `pluck()` returns a flat array of scalar column values when the\n * SELECT projects a single column — we lean on that to avoid an\n * extra map step.\n */\n getSupersededChunkIds(chunkIds: readonly number[]): Set {\n if (chunkIds.length === 0) return new Set();\n // Inline placeholders — chunkIds are int primary keys from our own\n // DB, never user input, so injection risk is zero. Cap the list\n // size defensively at 999 (SQLite's default SQLITE_MAX_VARIABLE_NUMBER\n // floor) — callers asking for more should batch.\n const ids = chunkIds.slice(0, 999);\n const placeholders = ids.map(() => \"?\").join(\",\");\n const sql = `SELECT chunks.id AS chunkId\n FROM chunks\n JOIN notes ON notes.id = chunks.note_id\n WHERE chunks.id IN (${placeholders})\n AND notes.status = 'superseded'`;\n const stmt = this.db.prepare(sql);\n // better-sqlite3 spread-args want a tuple type; widen via `as` so the\n // variable-length IN list survives strict-mode argument typing.\n const rows = (stmt.all as (...args: number[]) => { chunkId: number }[])(...ids);\n return new Set(rows.map((r) => r.chunkId));\n }\n}\n\n/**\n * Backslash-escape SQLite LIKE wildcards in a vault-relative path prefix\n * so a sink `resolveToRelativePath` containing `%` / `_` / `\\` matches\n * literally. Sinks normally use plain folder names (\"_memory/\"), but\n * defending against pathological inputs costs nothing.\n */\nfunction escapeLikePrefix(prefix: string): string {\n return prefix.replace(/\\\\/g, \"\\\\\\\\\").replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\");\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { ChunkRow } from \"../../types.js\";\nimport { computeChunkIdFragment } from \"../../chunker/chunk-id.js\";\n\nexport interface ChunkInput {\n idx: number;\n text: string;\n headingPath: string | null;\n startOffset: number;\n endOffset: number;\n tokenCount: number;\n /**\n * Phase 5 / D-04 / D-05: content-stable chunk identity fragment.\n * First 7 hex chars of `sha256(NFC(LF-normalized, trimEnd(text)))`.\n *\n * Optional at the type level so existing test fixtures and lightweight\n * call sites can omit it; when omitted, `insertBatch` computes it via\n * the canonical helper (`src/chunker/chunk-id.ts`). Production call\n * sites (indexer, single-indexer) pass an explicit value, which is the\n * preferred path — keeping the helper as the single source of truth\n * (RESEARCH §Pitfall 14: scattered createHash calls are forbidden).\n */\n chunkIdFragment?: string;\n}\n\nexport class ChunksQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deleteByNote: BetterSqlite3.Statement<[number]>;\n private readonly _getByNote: BetterSqlite3.Statement<[number], ChunkRow>;\n private readonly _getById: BetterSqlite3.Statement<[number], ChunkRow>;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._insert = db.prepare(`\n INSERT INTO chunks (note_id, idx, text, heading_path, start_offset, end_offset, token_count, chunk_id_fragment)\n VALUES (@note_id, @idx, @text, @heading_path, @start_offset, @end_offset, @token_count, @chunk_id_fragment)\n `);\n this._deleteByNote = db.prepare(\"DELETE FROM chunks WHERE note_id = ?\");\n this._getByNote = db.prepare<[number], ChunkRow>(\n \"SELECT * FROM chunks WHERE note_id = ? ORDER BY idx\",\n );\n this._getById = db.prepare<[number], ChunkRow>(\"SELECT * FROM chunks WHERE id = ?\");\n }\n\n insertBatch(noteId: number, chunks: ChunkInput[]): number[] {\n const ids: number[] = [];\n const tx = this.db.transaction((cs: ChunkInput[]) => {\n for (const c of cs) {\n const info = this._insert.run({\n note_id: noteId,\n idx: c.idx,\n text: c.text,\n heading_path: c.headingPath,\n start_offset: c.startOffset,\n end_offset: c.endOffset,\n token_count: c.tokenCount,\n // Phase 5 / D-04 / D-05: prefer the caller-supplied fragment\n // (production path: chunker computed it once). Fall back to\n // the canonical helper for legacy / test-only call sites that\n // pre-date the field. The helper is the single source of\n // truth — there is no other place in the codebase that\n // computes `chunk_id_fragment`.\n chunk_id_fragment: c.chunkIdFragment ?? computeChunkIdFragment(c.text),\n });\n ids.push(Number(info.lastInsertRowid));\n }\n });\n tx(chunks);\n return ids;\n }\n\n deleteByNote(noteId: number): number {\n return this._deleteByNote.run(noteId).changes;\n }\n\n getByNote(noteId: number): ChunkRow[] {\n return this._getByNote.all(noteId);\n }\n\n getById(id: number): ChunkRow | null {\n return this._getById.get(id) ?? null;\n }\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\n\nimport type { ModelsQueries } from \"./models.js\";\n\nexport interface EmbeddingInput {\n chunkId: number;\n modelId: number;\n vector: number[];\n}\n\nexport interface SemanticHit {\n chunkId: number;\n distance: number;\n}\n\ninterface ModelStatements {\n insert: BetterSqlite3.Statement;\n deleteByChunk: BetterSqlite3.Statement<[bigint]>;\n deleteAll: BetterSqlite3.Statement;\n search: BetterSqlite3.Statement<[string, number], { chunk_id: number; distance: number }>;\n}\n\n/**\n * sqlite-vec embedding store with one vec0 table per (modelId, dim).\n *\n * Distance metric: vec0 with `FLOAT[N]` uses L2 (Euclidean) distance by\n * default. For cosine similarity, normalize vectors to unit length before\n * insert and at query time — L2 on unit vectors is monotonically equivalent\n * to cosine distance.\n *\n * Layout history:\n * - v1..v3: one global `embeddings(FLOAT[1024])` table.\n * - v4 (Phase 7b): per-dim tables `embeddings_` so two models with\n * DIFFERENT dims could coexist.\n * - v5 (Phase 7e bugfix): per-MODEL tables `embeddings_m_d`\n * so two models with the SAME dim (e.g. qwen3 + bge-m3, both 1024)\n * can ALSO coexist. The earlier `partition key` attempt turned out\n * not to give us a composite primary key.\n *\n * The caller always passes a `modelId`; the dim is looked up from the\n * `models` table — never inferred from the vector length. Unknown model\n * throws (no silent defaults).\n */\nexport class EmbeddingsQueries {\n private readonly stmtsByModel = new Map();\n\n constructor(\n private readonly db: BetterSqlite3.Database,\n private readonly models: ModelsQueries,\n ) {}\n\n private tableName(modelId: number, dim: number): string {\n return `embeddings_m${modelId}_d${dim}`;\n }\n\n /**\n * Ensure the vec0 table for this model exists. Idempotent. Called lazily\n * on first use of a model. Tables for an existing pre-v5 dataset are\n * materialized by migration 005.\n */\n ensureTableForModel(modelId: number, dim: number): void {\n if (!Number.isInteger(modelId) || modelId <= 0) {\n throw new Error(`Invalid modelId: ${modelId}`);\n }\n if (!Number.isInteger(dim) || dim <= 0) {\n throw new Error(`Invalid embedding dim: ${dim}`);\n }\n const table = this.tableName(modelId, dim);\n this.db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS ${table} USING vec0(\n chunk_id INTEGER PRIMARY KEY,\n vector FLOAT[${dim}]\n )`,\n );\n }\n\n private dimForModel(modelId: number): number {\n const row = this.models.getById(modelId);\n if (!row) {\n throw new Error(`EmbeddingsQueries: model_id ${modelId} not found in models table`);\n }\n return row.dim;\n }\n\n private getStmts(modelId: number): ModelStatements {\n const cached = this.stmtsByModel.get(modelId);\n if (cached) return cached;\n\n const dim = this.dimForModel(modelId);\n this.ensureTableForModel(modelId, dim);\n const table = this.tableName(modelId, dim);\n const stmts: ModelStatements = {\n insert: this.db.prepare(`INSERT INTO ${table} (chunk_id, vector) VALUES (?, ?)`),\n deleteByChunk: this.db.prepare(`DELETE FROM ${table} WHERE chunk_id = ?`),\n deleteAll: this.db.prepare(`DELETE FROM ${table}`),\n search: this.db.prepare<[string, number], { chunk_id: number; distance: number }>(\n `SELECT chunk_id, distance\n FROM ${table}\n WHERE vector MATCH ? AND k = ?\n ORDER BY distance`,\n ),\n };\n this.stmtsByModel.set(modelId, stmts);\n return stmts;\n }\n\n insertBatch(items: EmbeddingInput[]): void {\n if (items.length === 0) return;\n\n // Group by model_id so each batch hits one prepared statement.\n const byModel = new Map();\n for (const x of items) {\n let bucket = byModel.get(x.modelId);\n if (!bucket) {\n bucket = [];\n byModel.set(x.modelId, bucket);\n }\n bucket.push(x);\n }\n\n const tx = this.db.transaction(() => {\n for (const [modelId, xs] of byModel) {\n const stmts = this.getStmts(modelId);\n for (const x of xs) {\n // sqlite-vec vec0 INTEGER PK is strict — BigInt forces SQLite\n // INTEGER instead of REAL.\n stmts.insert.run(BigInt(x.chunkId), serializeVector(x.vector));\n }\n }\n });\n tx();\n }\n\n /**\n * Delete embeddings for a chunk across every registered model — the\n * caller doesn't track which models embedded the chunk.\n */\n deleteByChunk(chunkId: number): void {\n for (const modelId of this.registeredModelIds()) {\n const stmts = this.getStmts(modelId);\n stmts.deleteByChunk.run(BigInt(chunkId));\n }\n }\n\n /**\n * Wipe every embedding row for the given model. Cheap because each\n * model owns its own table — equivalent to `DELETE FROM table`.\n */\n deleteByModel(modelId: number): void {\n const stmts = this.getStmts(modelId);\n stmts.deleteAll.run();\n }\n\n searchSemantic(modelId: number, queryVector: number[], topK: number): SemanticHit[] {\n const dim = this.dimForModel(modelId);\n if (queryVector.length !== dim) {\n throw new Error(\n `searchSemantic: query vector length ${queryVector.length} ` +\n `does not match model ${modelId} dim ${dim}`,\n );\n }\n const stmts = this.getStmts(modelId);\n const rows = stmts.search.all(serializeVector(queryVector), topK);\n return rows.map((r) => ({ chunkId: r.chunk_id, distance: r.distance }));\n }\n\n /**\n * Every model_id with a materialized embeddings table. Read from the\n * model registry — every model that has ever been inserted-into has\n * its table created via `ensureTableForModel`.\n */\n private registeredModelIds(): number[] {\n return this.models.listAll().map((m) => m.id);\n }\n}\n\n/**\n * sqlite-vec accepts vectors as JSON arrays of numbers (text) or as raw\n * little-endian Float32 BLOBs. JSON is simplest and fast enough for our\n * scale; switch to Float32Array.buffer if profiling demands it.\n */\nfunction serializeVector(v: number[]): string {\n return JSON.stringify(v);\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\n\nexport interface WikilinkInput {\n targetPath: string;\n targetNoteId: number | null;\n linkText: string | null;\n anchor: string | null;\n lineNumber: number | null;\n}\n\nexport interface BacklinkRow {\n sourceNoteId: number;\n lineNumber: number | null;\n linkText: string | null;\n}\n\nexport interface ForwardLinkRow {\n targetPath: string;\n targetNoteId: number | null;\n anchor: string | null;\n linkText: string | null;\n}\n\nexport interface BrokenLinkRow {\n sourceNoteId: number;\n targetPath: string;\n}\n\nexport class WikilinksQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deleteByNote: BetterSqlite3.Statement<[number]>;\n private readonly _backlinks: BetterSqlite3.Statement<\n [number],\n { source_note: number; line_number: number | null; link_text: string | null }\n >;\n private readonly _forward: BetterSqlite3.Statement<\n [number],\n {\n target_path: string;\n target_note: number | null;\n anchor: string | null;\n link_text: string | null;\n }\n >;\n private readonly _broken: BetterSqlite3.Statement<\n [],\n { source_note: number; target_path: string }\n >;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._insert = db.prepare(`\n INSERT OR IGNORE INTO wikilinks\n (source_note, target_path, target_note, link_text, anchor, line_number)\n VALUES (@source_note, @target_path, @target_note, @link_text, @anchor, @line_number)\n `);\n this._deleteByNote = db.prepare(\"DELETE FROM wikilinks WHERE source_note = ?\");\n this._backlinks = db.prepare(\n `SELECT source_note, line_number, link_text\n FROM wikilinks\n WHERE target_note = ?`,\n );\n this._forward = db.prepare(\n `SELECT target_path, target_note, anchor, link_text\n FROM wikilinks\n WHERE source_note = ?`,\n );\n this._broken = db.prepare(\n `SELECT source_note, target_path\n FROM wikilinks\n WHERE target_note IS NULL`,\n );\n }\n\n insertBatch(sourceNoteId: number, links: WikilinkInput[]): void {\n const tx = this.db.transaction((xs: WikilinkInput[]) => {\n for (const x of xs) {\n this._insert.run({\n source_note: sourceNoteId,\n target_path: x.targetPath,\n target_note: x.targetNoteId,\n link_text: x.linkText,\n anchor: x.anchor,\n line_number: x.lineNumber,\n });\n }\n });\n tx(links);\n }\n\n deleteByNote(noteId: number): number {\n return this._deleteByNote.run(noteId).changes;\n }\n\n getBacklinks(noteId: number): BacklinkRow[] {\n return this._backlinks.all(noteId).map((r) => ({\n sourceNoteId: r.source_note,\n lineNumber: r.line_number,\n linkText: r.link_text,\n }));\n }\n\n getForwardLinks(noteId: number): ForwardLinkRow[] {\n return this._forward.all(noteId).map((r) => ({\n targetPath: r.target_path,\n targetNoteId: r.target_note,\n anchor: r.anchor,\n linkText: r.link_text,\n }));\n }\n\n resolveBrokenLinks(): BrokenLinkRow[] {\n return this._broken.all().map((r) => ({\n sourceNoteId: r.source_note,\n targetPath: r.target_path,\n }));\n }\n}\n","/**\n * EdgesQueries — Phase 4 / 04-01 / GRA-04 (D-01) typed-edge substrate.\n *\n * Mirrors `src/db/queries/wikilinks.ts` verbatim in structure. Phase 4\n * promotes the v1 wikilink-only graph to a typed-edge graph (the four\n * `Edge.type` literals in `src/types.ts:470`):\n * `wikilink | mention | frontmatter-ref | hyperlink`.\n *\n * v2.0.0 keeps `wikilinks` in place (read-deprecated; Plan 04-02 stops\n * writing to it). All reads from this point forward go through\n * `vault.db.edges.*`; the v1 graph tools (`list_backlinks` /\n * `list_forward_links` / `findBrokenLinks`) are switched in Task 2 of\n * this plan.\n *\n * UPSERT discipline mirrors `wikilinks.ts:52` — `INSERT OR IGNORE`\n * against the widened UNIQUE index (migration 012) over\n * `(source_doc, COALESCE(target_doc, -1), COALESCE(target_path, ''),\n * type, COALESCE(rel, ''), COALESCE(anchor, ''),\n * COALESCE(line_number, -1))` makes re-extraction idempotent.\n *\n * The narrow key originally shipped by migration 011 (just\n * `source_doc, target_doc, type, anchor`) silently dropped legitimate\n * non-duplicate rows: multiple broken wikilinks from the same source,\n * multiple hyperlinks from the same source, multiple `frontmatter-ref`\n * edges with different `rel`, and multi-line mentions all collided.\n * Migration 012 widens the key to include the disambiguators and\n * re-runs the wikilink backfill to recover rows lost during the\n * narrow-key window.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\n\nimport type { Edge } from \"../../types.js\";\n\n/**\n * Edge.type union re-exported as `EdgeType` for ergonomic use at the\n * query namespace + barrel layer. The canonical definition stays in\n * `src/types.ts:470` (ADR-003); this is a strict re-export so any\n * future widening propagates without touching downstream call sites.\n */\nexport type EdgeType = Edge[\"type\"];\n\nexport interface EdgeInput {\n /** Resolved target note id, or `null` for unresolved targets. */\n targetNoteId: number | null;\n /**\n * Raw target string for unresolved edges (dangling wikilinks,\n * hyperlink URLs, frontmatter-ref strings that don't match a known\n * doc). Mirrors `wikilinks.target_path`. May be `null` only when\n * `targetNoteId` is set.\n */\n targetPath: string | null;\n type: EdgeType;\n /** ADR-003 `Edge.rel` — optional adapter-specific sub-classifier. */\n rel: string | null;\n /** Section anchor for wikilinks (`[[target#section]]`). */\n anchor: string | null;\n lineNumber: number | null;\n /**\n * Optional display text from the source (e.g., wikilink alias\n * `[[target|display text]]`). Carried through from the v1\n * `wikilinks.link_text` column so the graph-tool result shape is\n * preserved post-04-01 read switch.\n */\n linkText: string | null;\n}\n\nexport interface EdgeBacklinkRow {\n sourceNoteId: number;\n type: EdgeType;\n anchor: string | null;\n lineNumber: number | null;\n linkText: string | null;\n}\n\nexport interface EdgeForwardLinkRow {\n targetPath: string | null;\n targetNoteId: number | null;\n type: EdgeType;\n anchor: string | null;\n lineNumber: number | null;\n linkText: string | null;\n}\n\nexport interface EdgeBrokenLinkRow {\n sourceNoteId: number;\n targetPath: string | null;\n type: EdgeType;\n lineNumber: number | null;\n}\n\n/**\n * Row shape returned by `getAllForNodes` — Phase 4 / 04-05 / GRA-02.\n *\n * Carries the full edge metadata needed by `cluster()` to build an\n * undirected graphology graph (source + target DocIds), collapse\n * parallel edges by `(min(src,tgt), max(src,tgt))`, skip self-loops,\n * and pass the result into Louvain.\n */\nexport interface EdgeRowFull {\n sourceDoc: number;\n targetDoc: number;\n type: EdgeType;\n anchor: string | null;\n lineNumber: number | null;\n}\n\nexport class EdgesQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deleteByNote: BetterSqlite3.Statement<[number]>;\n private readonly _backlinks: BetterSqlite3.Statement<\n [number],\n {\n source_doc: number;\n type: EdgeType;\n anchor: string | null;\n line_number: number | null;\n link_text: string | null;\n }\n >;\n private readonly _forward: BetterSqlite3.Statement<\n [number],\n {\n target_doc: number | null;\n target_path: string | null;\n type: EdgeType;\n anchor: string | null;\n line_number: number | null;\n link_text: string | null;\n }\n >;\n private readonly _broken: BetterSqlite3.Statement<\n [],\n {\n source_doc: number;\n target_path: string | null;\n type: EdgeType;\n line_number: number | null;\n }\n >;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._insert = db.prepare(`\n INSERT OR IGNORE INTO edges\n (source_doc, target_doc, target_path, type, rel, anchor, line_number, link_text)\n VALUES (@source_doc, @target_doc, @target_path, @type, @rel, @anchor, @line_number, @link_text)\n `);\n this._deleteByNote = db.prepare(\"DELETE FROM edges WHERE source_doc = ?\");\n this._backlinks = db.prepare(\n `SELECT source_doc, type, anchor, line_number, link_text\n FROM edges\n WHERE target_doc = ?`,\n );\n this._forward = db.prepare(\n `SELECT target_doc, target_path, type, anchor, line_number, link_text\n FROM edges\n WHERE source_doc = ?`,\n );\n this._broken = db.prepare(\n `SELECT source_doc, target_path, type, line_number\n FROM edges\n WHERE target_doc IS NULL`,\n );\n }\n\n insertBatch(sourceNoteId: number, edges: EdgeInput[]): void {\n const tx = this.db.transaction((xs: EdgeInput[]) => {\n for (const x of xs) {\n this._insert.run({\n source_doc: sourceNoteId,\n target_doc: x.targetNoteId,\n target_path: x.targetPath,\n type: x.type,\n rel: x.rel,\n anchor: x.anchor,\n line_number: x.lineNumber,\n link_text: x.linkText,\n });\n }\n });\n tx(edges);\n }\n\n deleteByNote(noteId: number): number {\n return this._deleteByNote.run(noteId).changes;\n }\n\n /**\n * Get inbound edges where `target_doc = noteId`.\n *\n * Phase 4 / 04-03 (GRA-01 / D-08): the optional `edgeTypes` filter\n * narrows the result to rows matching one of the listed types. The\n * filter is passed through as parameterized placeholders in an\n * `IN (?, ?, …)` clause; `EdgeType` is a closed Zod-validated union\n * (4 strings), so SQL injection is not a vector. When `edgeTypes` is\n * `undefined` or empty, the unfiltered prepared statement is used (no\n * per-call prepare cost — matches the v1 behavior).\n */\n getBacklinks(noteId: number, edgeTypes?: readonly EdgeType[]): EdgeBacklinkRow[] {\n if (!edgeTypes || edgeTypes.length === 0) {\n return this._backlinks.all(noteId).map((r) => ({\n sourceNoteId: r.source_doc,\n type: r.type,\n anchor: r.anchor,\n lineNumber: r.line_number,\n linkText: r.link_text,\n }));\n }\n // Dynamic IN-clause; EdgeType is a closed union, so the placeholder\n // count is bounded and the parameters are bound — no string concat\n // of user data. T-04-03-04 mitigation.\n const placeholders = edgeTypes.map(() => \"?\").join(\", \");\n const stmt = this.db.prepare<\n [number, ...EdgeType[]],\n {\n source_doc: number;\n type: EdgeType;\n anchor: string | null;\n line_number: number | null;\n link_text: string | null;\n }\n >(\n `SELECT source_doc, type, anchor, line_number, link_text\n FROM edges\n WHERE target_doc = ? AND type IN (${placeholders})`,\n );\n return stmt.all(noteId, ...edgeTypes).map((r) => ({\n sourceNoteId: r.source_doc,\n type: r.type,\n anchor: r.anchor,\n lineNumber: r.line_number,\n linkText: r.link_text,\n }));\n }\n\n /**\n * Get outbound edges where `source_doc = noteId`.\n *\n * Phase 4 / 04-03 (GRA-01 / D-08): optional `edgeTypes` filter — see\n * `getBacklinks` for the SQL injection / closed-union rationale.\n * Hyperlink rows return `target_doc=null` + raw URL in `target_path`;\n * callers iterating for BFS traversal SKIP those (Phase 4 BFS only\n * traverses resolved edges).\n */\n getForwardLinks(noteId: number, edgeTypes?: readonly EdgeType[]): EdgeForwardLinkRow[] {\n if (!edgeTypes || edgeTypes.length === 0) {\n return this._forward.all(noteId).map((r) => ({\n targetPath: r.target_path,\n targetNoteId: r.target_doc,\n type: r.type,\n anchor: r.anchor,\n lineNumber: r.line_number,\n linkText: r.link_text,\n }));\n }\n const placeholders = edgeTypes.map(() => \"?\").join(\", \");\n const stmt = this.db.prepare<\n [number, ...EdgeType[]],\n {\n target_doc: number | null;\n target_path: string | null;\n type: EdgeType;\n anchor: string | null;\n line_number: number | null;\n link_text: string | null;\n }\n >(\n `SELECT target_doc, target_path, type, anchor, line_number, link_text\n FROM edges\n WHERE source_doc = ? AND type IN (${placeholders})`,\n );\n return stmt.all(noteId, ...edgeTypes).map((r) => ({\n targetPath: r.target_path,\n targetNoteId: r.target_doc,\n type: r.type,\n anchor: r.anchor,\n lineNumber: r.line_number,\n linkText: r.link_text,\n }));\n }\n\n resolveBrokenLinks(): EdgeBrokenLinkRow[] {\n return this._broken.all().map((r) => ({\n sourceNoteId: r.source_doc,\n targetPath: r.target_path,\n type: r.type,\n lineNumber: r.line_number,\n }));\n }\n\n /**\n * Phase 4 / 04-05 / GRA-02 — return ALL resolved edges whose BOTH\n * endpoints (`source_doc` AND `target_doc`) lie inside the input\n * `noteIds` set. Unresolved edges (`target_doc IS NULL`) are excluded\n * — `cluster()` operates only on the resolved-DocId graph.\n *\n * The implementation uses a dynamic `IN (?, ?, …)` clause on both the\n * source and target columns; the placeholders are integer noteIds, so\n * there is no SQL-injection vector (the input type is `number[]`, not\n * caller-supplied strings). The statement is NOT cached because the\n * placeholder count varies per call and this method is invoked at most\n * once per `cluster()` call.\n *\n * Self-loops are not filtered here because the `edges` table does not\n * store them (the indexer skips `source === target`); `cluster()`\n * defensively filters at graph-build time anyway (Plan 04-05 task 2).\n *\n * Empty input → empty output (no SQL executed). Single-node input →\n * empty output (no in-set edge possible because target ∉ {noteId}).\n */\n getAllForNodes(noteIds: readonly number[]): EdgeRowFull[] {\n if (noteIds.length === 0) return [];\n const placeholders = noteIds.map(() => \"?\").join(\", \");\n const sql = `\n SELECT source_doc, target_doc, type, anchor, line_number\n FROM edges\n WHERE source_doc IN (${placeholders})\n AND target_doc IN (${placeholders})\n AND target_doc IS NOT NULL\n `;\n const stmt = this.db.prepare<\n number[],\n {\n source_doc: number;\n target_doc: number;\n type: EdgeType;\n anchor: string | null;\n line_number: number | null;\n }\n >(sql);\n return stmt.all(...noteIds, ...noteIds).map((r) => ({\n sourceDoc: r.source_doc,\n targetDoc: r.target_doc,\n type: r.type,\n anchor: r.anchor,\n lineNumber: r.line_number,\n }));\n }\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { IndexRunRow, WriteAuditRow } from \"../types.js\";\n\nexport interface StartRunInput {\n runId: string;\n vaultName: string;\n modelId: number | null;\n trigger: string;\n}\n\nexport interface FinishRunStats {\n notesIndexed: number;\n chunksCreated: number;\n notesUpdated: number;\n notesDeleted: number;\n error?: string;\n}\n\nexport interface RecordWriteInput {\n noteId: number;\n op: \"create\" | \"update\" | \"delete\";\n previousHash: string | null;\n newHash: string | null;\n expectedHash: string | null;\n clientId: string | null;\n diffSummary: string | null;\n /**\n * Plan 02-06 (MEM-08): true iff this write was routed under a MemorySink\n * (agent observation / supersede), false for regular user writes. Stored\n * as INTEGER 1/0 via migration 009's `is_memory_sink_write` column.\n * Defaults to false when omitted — preserves Phase 1 call sites that\n * have not yet been threaded with the sink-derived flag.\n */\n isMemorySinkWrite?: boolean;\n}\n\nexport interface ListWritesFilter {\n noteId?: number;\n op?: string;\n since?: number;\n limit?: number;\n /**\n * Plan 02-06 (MEM-08): filter to memory-sink writes only (`true`) or\n * non-memory writes only (`false`). Omit to include all rows (default,\n * preserves Phase 1 v1 audit_log behavior). Uses the partial index\n * `idx_write_audit_memory` for the `true` branch.\n */\n isMemorySinkWrite?: boolean;\n}\n\nexport class AuditQueries {\n private readonly _startRun: BetterSqlite3.Statement;\n private readonly _finishRun: BetterSqlite3.Statement;\n private readonly _listRuns: BetterSqlite3.Statement<[number], IndexRunRow>;\n private readonly _recordWrite: BetterSqlite3.Statement;\n private readonly _isIndexing: BetterSqlite3.Statement<[], { c: number }>;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._startRun = db.prepare(`\n INSERT INTO index_runs (run_id, vault_name, model_id, started_at, trigger)\n VALUES (@run_id, @vault_name, @model_id, @started_at, @trigger)\n `);\n this._finishRun = db.prepare(`\n UPDATE index_runs\n SET finished_at = @finished_at,\n notes_indexed = @notes_indexed,\n chunks_created = @chunks_created,\n notes_updated = @notes_updated,\n notes_deleted = @notes_deleted,\n error = @error\n WHERE run_id = @run_id\n `);\n this._listRuns = db.prepare<[number], IndexRunRow>(\n \"SELECT * FROM index_runs ORDER BY id DESC LIMIT ?\",\n );\n // True iff there is at least one unfinished run in the audit log.\n // Used by the search layer to avoid surfacing chunks from a vault\n // whose embeddings are mid-flight (see search/scope.ts).\n this._isIndexing = db.prepare<[], { c: number }>(\n \"SELECT COUNT(*) AS c FROM index_runs WHERE finished_at IS NULL\",\n );\n this._recordWrite = db.prepare(`\n INSERT INTO write_audit (note_id, op, previous_hash, new_hash, expected_hash, client_id, diff_summary, at, is_memory_sink_write)\n VALUES (@note_id, @op, @previous_hash, @new_hash, @expected_hash, @client_id, @diff_summary, @at, @is_memory_sink_write)\n `);\n }\n\n startRun(input: StartRunInput): number {\n const info = this._startRun.run({\n run_id: input.runId,\n vault_name: input.vaultName,\n model_id: input.modelId,\n started_at: Date.now(),\n trigger: input.trigger,\n });\n return Number(info.lastInsertRowid);\n }\n\n finishRun(runId: string, stats: FinishRunStats): void {\n this._finishRun.run({\n run_id: runId,\n finished_at: Date.now(),\n notes_indexed: stats.notesIndexed,\n chunks_created: stats.chunksCreated,\n notes_updated: stats.notesUpdated,\n notes_deleted: stats.notesDeleted,\n error: stats.error ?? null,\n });\n }\n\n listRuns(limit = 50): IndexRunRow[] {\n return this._listRuns.all(limit);\n }\n\n /** True iff at least one index_runs row in this vault has finished_at IS NULL. */\n isIndexing(): boolean {\n return (this._isIndexing.get()?.c ?? 0) > 0;\n }\n\n recordWrite(input: RecordWriteInput): void {\n this._recordWrite.run({\n note_id: input.noteId,\n op: input.op,\n previous_hash: input.previousHash,\n new_hash: input.newHash,\n expected_hash: input.expectedHash,\n client_id: input.clientId,\n diff_summary: input.diffSummary,\n at: Date.now(),\n // Phase 1 call sites that have not been threaded with the flag default\n // to 0 (non-memory write) — backwards-compatible with migration 009's\n // ALTER default. Memory-routed writes (record_observation, supersede)\n // pass `isMemorySinkWrite: true`.\n is_memory_sink_write: input.isMemorySinkWrite ? 1 : 0,\n });\n }\n\n listWrites(filter: ListWritesFilter = {}): WriteAuditRow[] {\n const where: string[] = [];\n const params: (string | number)[] = [];\n if (filter.noteId !== undefined) {\n where.push(\"note_id = ?\");\n params.push(filter.noteId);\n }\n if (filter.op !== undefined) {\n where.push(\"op = ?\");\n params.push(filter.op);\n }\n if (filter.since !== undefined) {\n where.push(\"at >= ?\");\n params.push(filter.since);\n }\n if (filter.isMemorySinkWrite !== undefined) {\n where.push(\"is_memory_sink_write = ?\");\n params.push(filter.isMemorySinkWrite ? 1 : 0);\n }\n const limit = filter.limit ?? 100;\n const whereSql = where.length > 0 ? `WHERE ${where.join(\" AND \")}` : \"\";\n const sql = `SELECT * FROM write_audit ${whereSql} ORDER BY id DESC LIMIT ?`;\n params.push(limit);\n return this.db.prepare(sql).all(...params);\n }\n\n /**\n * Plan 02-06 (MEM-09): epoch-ms timestamp of the most recent memory-sink\n * write to a note whose path begins with `pathPrefix`, or `null` if no\n * such row exists. Backed by the `idx_write_audit_memory` partial index\n * (migration 009).\n *\n * Looks up via the `notes.path` value joined to `write_audit.note_id`.\n * Returns null when the note row was hard-deleted (FK SET NULL) or\n * when no audit row matches.\n */\n lastMemoryWriteAtForPathPrefix(pathPrefix: string): number | null {\n const row = this.db\n .prepare<[string], { at: number }>(\n `SELECT wa.at AS at\n FROM write_audit AS wa\n JOIN notes AS n ON n.id = wa.note_id\n WHERE wa.is_memory_sink_write = 1\n AND n.path LIKE ? ESCAPE '\\\\'\n ORDER BY wa.at DESC\n LIMIT 1`,\n )\n .get(escapeAuditLikePrefix(pathPrefix) + \"%\");\n return row?.at ?? null;\n }\n}\n\n/** Mirror of notes.ts `escapeLikePrefix` — local copy to avoid a cross-file dep. */\nfunction escapeAuditLikePrefix(prefix: string): string {\n return prefix.replace(/\\\\/g, \"\\\\\\\\\").replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\");\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { ModelRow } from \"../../types.js\";\n\nexport interface UpsertModelInput {\n name: string;\n provider: string;\n dim: number;\n /** When true (default), newly-inserted rows are marked active=1, matching\n * the historical contract: the first model to index a vault is the\n * active one. Set to false to register a shadow / secondary model\n * without disturbing the currently-active one. Existing rows keep\n * their active flag — upsert never flips active. */\n active?: boolean;\n}\n\nexport class ModelsQueries {\n private readonly _selectByName: BetterSqlite3.Statement<[string], ModelRow>;\n private readonly _selectActive: BetterSqlite3.Statement<[], ModelRow>;\n private readonly _selectById: BetterSqlite3.Statement<[number], ModelRow>;\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deactivateAll: BetterSqlite3.Statement;\n private readonly _activate: BetterSqlite3.Statement<[number]>;\n private readonly _listAll: BetterSqlite3.Statement<[], ModelRow>;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._selectByName = db.prepare<[string], ModelRow>(\"SELECT * FROM models WHERE name = ?\");\n this._selectActive = db.prepare<[], ModelRow>(\n \"SELECT * FROM models WHERE active = 1 ORDER BY id DESC LIMIT 1\",\n );\n this._selectById = db.prepare<[number], ModelRow>(\"SELECT * FROM models WHERE id = ?\");\n this._insert = db.prepare(`\n INSERT INTO models (name, provider, dim, created_at, active)\n VALUES (@name, @provider, @dim, @created_at, @active)\n `);\n this._deactivateAll = db.prepare(\"UPDATE models SET active = 0\");\n this._activate = db.prepare<[number]>(\"UPDATE models SET active = 1 WHERE id = ?\");\n this._listAll = db.prepare<[], ModelRow>(\"SELECT * FROM models ORDER BY id\");\n }\n\n upsert(input: UpsertModelInput): ModelRow {\n const existing = this._selectByName.get(input.name);\n if (existing) return existing;\n const info = this._insert.run({\n name: input.name,\n provider: input.provider,\n dim: input.dim,\n created_at: Date.now(),\n active: input.active === false ? 0 : 1,\n });\n const row = this._selectById.get(Number(info.lastInsertRowid));\n if (!row) {\n throw new Error(\"models.upsert: row vanished after insert\");\n }\n return row;\n }\n\n getById(modelId: number): ModelRow | null {\n return this._selectById.get(modelId) ?? null;\n }\n\n getByName(name: string): ModelRow | null {\n return this._selectByName.get(name) ?? null;\n }\n\n getActive(): ModelRow | null {\n return this._selectActive.get() ?? null;\n }\n\n setActive(modelId: number): void {\n const tx = this.db.transaction(() => {\n this._deactivateAll.run();\n this._activate.run(modelId);\n });\n tx();\n }\n\n listAll(): ModelRow[] {\n return this._listAll.all();\n }\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\n\nexport interface BM25Hit {\n chunkId: number;\n /**\n * Positive relevance score (higher = better). SQLite FTS5 `bm25()` returns a\n * negative number — we flip the sign for downstream consumers so the score\n * is monotonically increasing in \"goodness of match\".\n */\n score: number;\n /** Optional snippet of the chunk with the query terms highlighted. */\n snippet?: string;\n}\n\ninterface BM25Row {\n chunkId: number;\n score: number;\n}\n\ninterface BM25RowWithSnippet extends BM25Row {\n snippet: string;\n}\n\n/**\n * Full-text BM25 search over `chunks_fts` (the FTS5 virtual table mirroring\n * `chunks.text` — see `INITIAL_SCHEMA`). The triggers on `chunks` keep the\n * index in sync automatically, so consumers only need to insert chunks the\n * usual way and can search here.\n */\nexport class FtsQueries {\n private readonly _search: BetterSqlite3.Statement<[string, number], BM25Row>;\n private readonly _searchWithSnippet: BetterSqlite3.Statement<\n [string, number],\n BM25RowWithSnippet\n >;\n /**\n * Phase 3 / 03-05 (M4 fix): same FTS5 BM25 search but JOINed against\n * `chunks → notes` so the candidate list excludes any chunk whose\n * owning note has `notes.status = 'superseded'`.\n *\n * Filter runs at the SQL level so the v1-default path (which passes\n * `excludeSuperseded = false` from `searchOneVault`) is byte-identical\n * to v1, and the new default-hide path performs zero per-candidate\n * frontmatter parses. The `notes_status` partial index from migration\n * 010 keeps the JOIN cheap (only rows with a non-null status are\n * indexed).\n */\n private readonly _searchExclSup: BetterSqlite3.Statement<[string, number], BM25Row>;\n\n constructor(db: BetterSqlite3.Database) {\n this._search = db.prepare<[string, number], BM25Row>(\n `SELECT rowid AS chunkId, bm25(chunks_fts) AS score\n FROM chunks_fts\n WHERE chunks_fts MATCH ?\n ORDER BY bm25(chunks_fts) ASC\n LIMIT ?`,\n );\n this._searchWithSnippet = db.prepare<[string, number], BM25RowWithSnippet>(\n `SELECT\n rowid AS chunkId,\n bm25(chunks_fts) AS score,\n snippet(chunks_fts, 0, '', '', '...', 64) AS snippet\n FROM chunks_fts\n WHERE chunks_fts MATCH ?\n ORDER BY bm25(chunks_fts) ASC\n LIMIT ?`,\n );\n // 03-05 M4: SQL-level superseded filter. The JOIN against\n // `chunks → notes` references the denormalized `notes.status` column\n // (migration 010 part B) so we never re-parse the JSON frontmatter\n // blob for filtering. `notes.status IS NULL` covers notes with no\n // frontmatter status (the common case) — those are NOT superseded.\n this._searchExclSup = db.prepare<[string, number], BM25Row>(\n `SELECT chunks_fts.rowid AS chunkId, bm25(chunks_fts) AS score\n FROM chunks_fts\n JOIN chunks ON chunks.id = chunks_fts.rowid\n JOIN notes ON notes.id = chunks.note_id\n WHERE chunks_fts MATCH ?\n AND (notes.status IS NULL OR notes.status != 'superseded')\n ORDER BY bm25(chunks_fts) ASC\n LIMIT ?`,\n );\n }\n\n /**\n * Run BM25 over `chunks_fts`.\n *\n * @param query user query (sanitized internally)\n * @param topK max rows to return\n * @param withSnippet when true, include FTS5 `snippet(...)` output\n * (mutually exclusive with excludeSuperseded —\n * snippets are debug/UI only, not the search path)\n * @param excludeSuperseded when true (03-05 M4), JOIN-and-filter against\n * `notes.status` so candidates from superseded\n * docs never reach the caller. v1-default path\n * passes `false` and stays byte-identical.\n */\n search(query: string, topK: number, withSnippet = false, excludeSuperseded = false): BM25Hit[] {\n const sanitized = FtsQueries.sanitize(query);\n if (sanitized.length === 0) return [];\n\n if (withSnippet) {\n // Snippet path is debug/UI only — keep it on the v1 statement so\n // 03-05 doesn't need to prepare a third statement just for the\n // rarely-used branch. If a future caller needs `snippet + exclude\n // superseded`, prepare a fourth statement here.\n const rows = this._searchWithSnippet.all(sanitized, topK);\n return rows.map((r) => ({\n chunkId: r.chunkId,\n score: -r.score,\n snippet: r.snippet,\n }));\n }\n const stmt = excludeSuperseded ? this._searchExclSup : this._search;\n const rows = stmt.all(sanitized, topK);\n return rows.map((r) => ({ chunkId: r.chunkId, score: -r.score }));\n }\n\n /**\n * Conservative sanitizer for FTS5 MATCH input.\n *\n * Strategy: strip characters that have special FTS5 meaning when the user\n * likely didn't intend them, while preserving advanced syntax for users\n * who know what they're doing (AND/OR/NOT, NEAR, trailing `*` prefix).\n *\n * - Double quotes are removed unless balanced (unbalanced quote → phrase\n * parse error). We strip them all unconditionally to keep this simple\n * and predictable — phrase queries can be re-introduced by callers that\n * construct queries programmatically.\n * - Parentheses are kept only when balanced; otherwise stripped.\n * - Colons (column filters) are stripped — `chunks_fts` only has one\n * column, so column filters are never useful and cause errors.\n * - Tokens containing FTS5-reserved punctuation that doesn't have a sane\n * meaning here (`-`, `/`, `?`, `.`, `!`) are wrapped in double quotes so\n * FTS5 treats them as literal phrases. This is what makes natural\n * queries like \"LAG-EPIX\", \"Netzwerk/Personen\", or \"Wer ist X?\" work.\n * See the v0.6.0 retrieval eval (vault note `_research/vault-memory-eval.md`)\n * for the discovered crash triggers.\n * - Leading operator tokens at fragment boundaries are dropped (FTS5\n * errors on a trailing `AND`/`OR`).\n * - Whitespace is normalized.\n *\n * If the cleaned result is empty, returns \"\".\n */\n static sanitize(userQuery: string): string {\n let s = userQuery.replace(/\"/g, \" \").replace(/:/g, \" \");\n\n // Balance parens — if mismatched, strip all parens.\n let depth = 0;\n let balanced = true;\n for (const ch of s) {\n if (ch === \"(\") depth++;\n else if (ch === \")\") {\n depth--;\n if (depth < 0) {\n balanced = false;\n break;\n }\n }\n }\n if (!balanced || depth !== 0) {\n s = s.replace(/[()]/g, \" \");\n }\n\n // Normalize whitespace.\n s = s.replace(/\\s+/g, \" \").trim();\n if (s.length === 0) return \"\";\n\n // Drop trailing operator tokens that would error.\n const trailingOpRe = /\\s+(AND|OR|NOT|NEAR)$/;\n while (trailingOpRe.test(s)) {\n s = s.replace(trailingOpRe, \"\");\n }\n // Drop leading operator tokens.\n s = s.replace(/^(AND|OR|NOT|NEAR)\\s+/, \"\");\n s = s.trim();\n if (s.length === 0) return \"\";\n\n // Phrase-wrap any token that contains FTS5-meaningful punctuation. Keep\n // operator keywords (AND/OR/NOT/NEAR) and lone wildcards (*) untouched\n // so power-user syntax still works. Tokens that *contain* a wildcard\n // alongside other content (e.g. \"foo*bar\") are phrase-wrapped — the\n // prefix-match semantics only fire on a token-trailing star anyway.\n //\n // The character class matches: hyphen, slash, dot, question mark,\n // exclamation, backslash. Asterisks are handled separately below.\n const needsPhrase = /[-/.?!\\\\]/;\n const isOperator = /^(AND|OR|NOT|NEAR)$/;\n const isPrefixStar = /^[^*\\s]+\\*$/; // \"word*\" — leave alone.\n\n const tokens = s.split(/\\s+/).map((t) => {\n if (t.length === 0) return t;\n if (isOperator.test(t)) return t;\n if (isPrefixStar.test(t)) return t;\n if (needsPhrase.test(t)) return `\"${t}\"`;\n return t;\n });\n\n return tokens.filter((t) => t.length > 0).join(\" \");\n }\n}\n","/**\n * AliasesQueries — note_aliases CRUD + lookup by alias.\n *\n * Case-insensitive matching: `alias_norm` is `alias.trim().toLowerCase()`.\n * Stored separately from the raw alias so display retains the original.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\n\nexport interface AliasResolveHit {\n note_id: number;\n path: string;\n alias: string; // original-case\n}\n\nexport interface AliasListAllRow {\n note_id: number;\n path: string;\n alias: string;\n alias_norm: string;\n}\n\nexport class AliasesQueries {\n private readonly setStmt: BetterSqlite3.Statement<[number, string, string]>;\n private readonly deleteStmt: BetterSqlite3.Statement<[number]>;\n private readonly listForNoteStmt: BetterSqlite3.Statement<[number]>;\n private readonly resolveStmt: BetterSqlite3.Statement<[string]>;\n private readonly listAllStmt: BetterSqlite3.Statement<[]>;\n\n constructor(db: BetterSqlite3.Database) {\n this.setStmt = db.prepare(\n `INSERT OR IGNORE INTO note_aliases (note_id, alias, alias_norm)\n VALUES (?, ?, ?)`,\n );\n this.deleteStmt = db.prepare(`DELETE FROM note_aliases WHERE note_id = ?`);\n this.listForNoteStmt = db.prepare(\n `SELECT alias FROM note_aliases WHERE note_id = ? ORDER BY id ASC`,\n );\n this.resolveStmt = db.prepare(\n `SELECT na.note_id AS note_id, n.path AS path, na.alias AS alias\n FROM note_aliases na\n JOIN notes n ON n.id = na.note_id\n WHERE na.alias_norm = ?\n ORDER BY length(n.path) ASC\n LIMIT 1`,\n );\n // Phase 4 / 04-02 / GRA-04 (D-03): the mention extractor needs the\n // full alias inventory once per indexer run to build the candidate\n // regex. Ordered by alias_norm for deterministic regex alternation\n // (mitigates T-04-02-04 — see plan threat model).\n this.listAllStmt = db.prepare(\n `SELECT na.note_id AS note_id, n.path AS path,\n na.alias AS alias, na.alias_norm AS alias_norm\n FROM note_aliases na\n JOIN notes n ON n.id = na.note_id\n ORDER BY na.alias_norm ASC`,\n );\n }\n\n /**\n * Phase 4 / 04-02 / GRA-04 (D-03): full alias inventory for the\n * mention extractor's per-run candidate set. Result is sorted by\n * `alias_norm` ASC so regex alternation ordering is deterministic\n * across runs (T-04-02-04 mitigation).\n */\n listAll(): AliasListAllRow[] {\n return this.listAllStmt.all() as AliasListAllRow[];\n }\n\n /**\n * Replace all aliases for a note with the given list (atomic).\n * Empty list → clears all aliases for the note.\n */\n setForNote(noteId: number, aliases: readonly string[]): void {\n this.deleteStmt.run(noteId);\n for (const a of aliases) {\n const trimmed = a.trim();\n if (trimmed.length === 0) continue;\n this.setStmt.run(noteId, trimmed, AliasesQueries.normalize(trimmed));\n }\n }\n\n /**\n * Find the note that owns the given alias (case-insensitive).\n * If multiple notes claim the same alias, the one with the shortest\n * path wins (mirrors Obsidian's heuristic).\n */\n resolve(alias: string): AliasResolveHit | null {\n const norm = AliasesQueries.normalize(alias);\n if (norm.length === 0) return null;\n return (this.resolveStmt.get(norm) as AliasResolveHit | undefined) ?? null;\n }\n\n listForNote(noteId: number): string[] {\n const rows = this.listForNoteStmt.all(noteId) as Array<{ alias: string }>;\n return rows.map((r) => r.alias);\n }\n\n static normalize(alias: string): string {\n return alias.trim().toLowerCase();\n }\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { InsertSectionRow, SectionRow } from \"../../types.js\";\n\n/**\n * Phase 3 — `sections` table query namespace (migration 010).\n *\n * Mirrors the `ChunksQueries` (`src/db/queries/chunks.ts`) shape:\n * - Prepared statements held as private fields.\n * - `insertMany` batches in a single transaction for amortized cost.\n * - `deleteByNote` matches the chunker's re-index pattern.\n *\n * `parent_id` is the FK pointer derived at insert time from\n * `SectionInfo.parent_index` (an array index) — the caller maps\n * indices → IDs after each row gets its `lastInsertRowid`.\n *\n * `heading_path` is stored as JSON-stringified text (so callers see\n * the storage shape explicitly at the call site).\n */\nexport class SectionsQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deleteByNote: BetterSqlite3.Statement<[number]>;\n private readonly _getByNote: BetterSqlite3.Statement<[number], SectionRow>;\n private readonly _getByAnchor: BetterSqlite3.Statement<[number, string], SectionRow>;\n private readonly _getByIdentity: BetterSqlite3.Statement<[number, string, string], SectionRow>;\n private readonly _findContainingChunk: BetterSqlite3.Statement<\n [number, number, number],\n SectionRow\n >;\n private readonly _countByNote: BetterSqlite3.Statement<[number], { c: number }>;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n // INSERT OR IGNORE: section identity is (note_id, heading_path, anchor)\n // per ADR-032 (revised). Two sibling sections only collide when they are\n // byte-identical (same anchor) AND in the same context (same heading_path)\n // — i.e. genuinely duplicated content in one place. Differently-placed\n // byte-identical sections (e.g. `Q1 > Risks` vs `Q2 > Risks` with the same\n // body) have different heading_path → distinct rows. `OR IGNORE` makes the\n // first sibling win on a true same-context collision; callers needing the\n // surviving id for parent linkage use `insertOneResolving`. Mirrors\n // src/sections/backfill.ts.\n this._insert = db.prepare(`\n INSERT OR IGNORE INTO sections\n (note_id, anchor, heading_path, heading_text, level,\n parent_id, ord, chunk_id_first, chunk_id_last, created_at)\n VALUES\n (@note_id, @anchor, @heading_path, @heading_text, @level,\n @parent_id, @ord, @chunk_id_first, @chunk_id_last, @created_at)\n `);\n this._deleteByNote = db.prepare(\"DELETE FROM sections WHERE note_id = ?\");\n this._getByNote = db.prepare<[number], SectionRow>(\n // parent_id ASC NULLS FIRST lets callers build the tree top-down\n // in one pass. SQLite NULLs sort first by default for ASC.\n \"SELECT * FROM sections WHERE note_id = ? ORDER BY parent_id IS NULL DESC, parent_id ASC, ord ASC\",\n );\n this._getByAnchor = db.prepare<[number, string], SectionRow>(\n \"SELECT * FROM sections WHERE note_id = ? AND anchor = ?\",\n );\n // Collision resolution keys on the FULL identity (note_id, heading_path,\n // anchor) so insertOneResolving finds the exact surviving row, not just\n // any same-anchor sibling in a different context.\n this._getByIdentity = db.prepare<[number, string, string], SectionRow>(\n \"SELECT * FROM sections WHERE note_id = ? AND heading_path = ? AND anchor = ?\",\n );\n this._findContainingChunk = db.prepare<[number, number], SectionRow>(\n // `chunk_id` is monotonically increasing per note; chunk_id_first\n // and chunk_id_last carve disjoint ranges (or both NULL for a\n // heading with no body content). We require both range bounds\n // to be NON-NULL — sections with NULL ranges contain zero chunks.\n `SELECT * FROM sections\n WHERE note_id = ?\n AND chunk_id_first IS NOT NULL\n AND chunk_id_last IS NOT NULL\n AND chunk_id_first <= ?\n AND chunk_id_last >= ?\n ORDER BY (chunk_id_last - chunk_id_first) ASC\n LIMIT 1`,\n );\n this._countByNote = db.prepare<[number], { c: number }>(\n \"SELECT COUNT(*) AS c FROM sections WHERE note_id = ?\",\n );\n }\n\n /**\n * Batch insert. Returns the new `id` for each row in the same order\n * as the input. The transaction wraps the whole batch so a mid-batch\n * failure rolls back cleanly.\n */\n insertMany(rows: InsertSectionRow[]): number[] {\n if (rows.length === 0) return [];\n const ids: number[] = [];\n const now = Date.now();\n const tx = this.db.transaction((rs: InsertSectionRow[]) => {\n for (const r of rs) {\n const info = this._insert.run({\n note_id: r.note_id,\n anchor: r.anchor,\n heading_path: r.heading_path,\n heading_text: r.heading_text,\n level: r.level,\n parent_id: r.parent_id,\n ord: r.ord,\n chunk_id_first: r.chunk_id_first,\n chunk_id_last: r.chunk_id_last,\n created_at: now,\n });\n ids.push(Number(info.lastInsertRowid));\n }\n });\n tx(rows);\n return ids;\n }\n\n /**\n * Insert one section, collision-safe. Returns the id of the row that now\n * owns the identity (note_id, heading_path, anchor): the freshly inserted\n * row, or — when a same-context byte-identical sibling already won the\n * unique slot — that surviving row's id (so callers can resolve parent_id\n * linkage). Per ADR-032 (revised), a collision now requires BOTH same anchor\n * AND same heading_path, so differently-placed identical sections persist as\n * distinct rows. Mirrors src/sections/backfill.ts. The live indexer uses\n * this instead of `insertMany` so duplicate sibling headings can't abort the\n * whole index run (see ISSUE-indexer-duplicate-anchor.md).\n */\n insertOneResolving(r: InsertSectionRow): number | null {\n const info = this._insert.run({\n note_id: r.note_id,\n anchor: r.anchor,\n heading_path: r.heading_path,\n heading_text: r.heading_text,\n level: r.level,\n parent_id: r.parent_id,\n ord: r.ord,\n chunk_id_first: r.chunk_id_first,\n chunk_id_last: r.chunk_id_last,\n created_at: Date.now(),\n });\n if (info.changes > 0) return Number(info.lastInsertRowid);\n // Collision on UNIQUE(note_id, heading_path, anchor): reuse the surviving\n // row's id. Look up by the full identity so we get the exact row, not a\n // same-anchor sibling that lives under a different heading_path.\n const existing = this._getByIdentity.get(r.note_id, r.heading_path, r.anchor);\n return existing ? Number(existing.id) : null;\n }\n\n deleteByNote(noteId: number): number {\n return this._deleteByNote.run(noteId).changes;\n }\n\n /**\n * Returns all sections for the note in tree order: top-level rows\n * (parent_id IS NULL) first, then deeper rows; within the same\n * parent, ord ASC.\n */\n getByNote(noteId: number): SectionRow[] {\n return this._getByNote.all(noteId);\n }\n\n getByAnchor(noteId: number, anchor: string): SectionRow | null {\n return this._getByAnchor.get(noteId, anchor) ?? null;\n }\n\n /**\n * Return the most-specific section whose chunk range contains\n * `chunkId`. \"Most specific\" = smallest range (innermost section).\n */\n findContainingChunk(noteId: number, chunkId: number): SectionRow | null {\n return this._findContainingChunk.get(noteId, chunkId, chunkId) ?? null;\n }\n\n countByNote(noteId: number): number {\n return this._countByNote.get(noteId)?.c ?? 0;\n }\n}\n","/**\n * BriefSourcesQueries — Phase 5 / BRF-* / D-06 reverse-index substrate.\n *\n * Mirrors `src/db/queries/wikilinks.ts` verbatim in structure. Populated\n * when a brief is written via `compile_brief` (one row per chunk in the\n * brief's `source_hashes` map) and removed when the brief is\n * deleted/superseded.\n *\n * UPSERT discipline: `INSERT OR IGNORE` against\n * `UNIQUE(brief_doc_id, chunk_id_fragment)` makes re-population on a\n * partial-state recompile idempotent.\n *\n * Staleness check on a `ChangeEvent` for `doc_id D`:\n * SELECT brief_doc_id FROM brief_sources\n * WHERE chunk_doc_id = D\n * AND recorded_hash != \n * → O(log N) lookup instead of O(B·S) scan of every brief's\n * `source_hashes` property.\n *\n * Adapter-seam discipline: no `fs`/`path`/`gray-matter`/`chokidar`\n * imports. `scripts/lint-adapters.sh` enforces.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\n\nexport interface BriefSourceInput {\n /** First 7 hex chars of the chunk's content hash (D-04 / D-05). */\n chunkIdFragment: string;\n /** DocId of the document containing the cited chunk. */\n chunkDocId: string;\n /** Full hash recorded at brief-compile time (`\"sha256:\"`). */\n recordedHash: string;\n}\n\nexport interface BriefSourceRow {\n briefDocId: string;\n chunkIdFragment: string;\n chunkDocId: string;\n recordedHash: string;\n}\n\nexport class BriefSourcesQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deleteByBrief: BetterSqlite3.Statement<[string]>;\n private readonly _listBriefDocIds: BetterSqlite3.Statement<[], { brief_doc_id: string }>;\n private readonly _briefsForChunkDoc: BetterSqlite3.Statement<\n [string],\n {\n brief_doc_id: string;\n chunk_id_fragment: string;\n chunk_doc_id: string;\n recorded_hash: string;\n }\n >;\n private readonly _sourcesForBrief: BetterSqlite3.Statement<\n [string],\n {\n brief_doc_id: string;\n chunk_id_fragment: string;\n chunk_doc_id: string;\n recorded_hash: string;\n }\n >;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._insert = db.prepare(`\n INSERT OR IGNORE INTO brief_sources\n (brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash)\n VALUES (@brief_doc_id, @chunk_id_fragment, @chunk_doc_id, @recorded_hash)\n `);\n this._deleteByBrief = db.prepare(\"DELETE FROM brief_sources WHERE brief_doc_id = ?\");\n this._listBriefDocIds = db.prepare(\"SELECT DISTINCT brief_doc_id FROM brief_sources\");\n this._briefsForChunkDoc = db.prepare(\n `SELECT brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash\n FROM brief_sources\n WHERE chunk_doc_id = ?`,\n );\n this._sourcesForBrief = db.prepare(\n `SELECT brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash\n FROM brief_sources\n WHERE brief_doc_id = ?`,\n );\n }\n\n /**\n * Batch insert. Idempotent: `INSERT OR IGNORE` against the UNIQUE\n * `(brief_doc_id, chunk_id_fragment)` constraint means re-running the\n * same batch is a no-op. Mirrors `WikilinksQueries.insertBatch`\n * (`wikilinks.ts:74-87`).\n */\n insertBatch(briefDocId: string, sources: BriefSourceInput[]): void {\n const tx = this.db.transaction((xs: BriefSourceInput[]) => {\n for (const x of xs) {\n this._insert.run({\n brief_doc_id: briefDocId,\n chunk_id_fragment: x.chunkIdFragment,\n chunk_doc_id: x.chunkDocId,\n recorded_hash: x.recordedHash,\n });\n }\n });\n tx(sources);\n }\n\n deleteByBrief(briefDocId: string): number {\n return this._deleteByBrief.run(briefDocId).changes;\n }\n\n listBriefDocIds(): string[] {\n return this._listBriefDocIds.all().map((r) => r.brief_doc_id);\n }\n\n briefsForChunkDoc(chunkDocId: string): BriefSourceRow[] {\n return this._briefsForChunkDoc.all(chunkDocId).map((r) => ({\n briefDocId: r.brief_doc_id,\n chunkIdFragment: r.chunk_id_fragment,\n chunkDocId: r.chunk_doc_id,\n recordedHash: r.recorded_hash,\n }));\n }\n\n sourcesForBrief(briefDocId: string): BriefSourceRow[] {\n return this._sourcesForBrief.all(briefDocId).map((r) => ({\n briefDocId: r.brief_doc_id,\n chunkIdFragment: r.chunk_id_fragment,\n chunkDocId: r.chunk_doc_id,\n recordedHash: r.recorded_hash,\n }));\n }\n}\n","/**\n * DaemonStateQueries — Phase 5 / D-09 staleness daemon cursor.\n *\n * Single-row-per-vault state (`vault_name TEXT PRIMARY KEY`). Used by\n * the staleness daemon (Plan 05-03) for the hybrid replay strategy:\n *\n * 1. Startup full scan (correctness floor).\n * 2. Read `last_seen_doc_mtime` cursor (steady-state diagnostic).\n * 3. After processing each ChangeEvent, bump cursor.\n *\n * The cursor is a **diagnostic hint** — never the sole correctness\n * guarantee. The startup scan is the floor regardless of cursor value.\n * Departure from the recommended \"mtime-only\" option per CONTEXT D-09.\n *\n * UPSERT idiom: `INSERT ... ON CONFLICT(vault_name) DO UPDATE SET`\n * keeps the single-row invariant via the PRIMARY KEY.\n *\n * Adapter-seam discipline: no `fs`/`path`/`gray-matter`/`chokidar`\n * imports. `scripts/lint-adapters.sh` enforces.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\n\nexport class DaemonStateQueries {\n private readonly _getCursor: BetterSqlite3.Statement<[string], { last_seen_doc_mtime: number }>;\n private readonly _setCursor: BetterSqlite3.Statement;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._getCursor = db.prepare(\n \"SELECT last_seen_doc_mtime FROM daemon_state WHERE vault_name = ?\",\n );\n this._setCursor = db.prepare(`\n INSERT INTO daemon_state (vault_name, last_seen_doc_mtime)\n VALUES (@vault_name, @mtime)\n ON CONFLICT(vault_name) DO UPDATE SET last_seen_doc_mtime = @mtime\n `);\n }\n\n /**\n * Returns the cursor for `vaultName`, or `null` if no row exists yet\n * (fresh vault, daemon has never run). Callers treat `null` as\n * \"perform the startup full scan\" — the cursor is a steady-state\n * efficiency hint, never a correctness floor.\n */\n getCursor(vaultName: string): number | null {\n const row = this._getCursor.get(vaultName);\n return row?.last_seen_doc_mtime ?? null;\n }\n\n setCursor(vaultName: string, mtime: number): void {\n this._setCursor.run({ vault_name: vaultName, mtime });\n }\n}\n","/**\n * ContractAuditQueries — Phase 6 / Q-AUD orchestration audit substrate.\n *\n * Mirrors `src/db/queries/audit.ts` and `src/db/queries/brief_sources.ts`\n * (Phase 5) in structure. Populated by `src/contracts/audit.ts` writers\n * (`recordContractStep` / `recordContractLoadError`) — one row per step,\n * no batch insert (orchestration writes step-by-step).\n *\n * Column shape (migration 014):\n * id INTEGER PRIMARY KEY AUTOINCREMENT\n * kind TEXT NOT NULL -- 'contract_step' | 'contract_load_error'\n * contract TEXT -- nullable (load errors have no contract context)\n * verb TEXT -- nullable\n * step_alias TEXT -- nullable\n * vault TEXT -- nullable\n * ts INTEGER NOT NULL -- epoch ms\n * error_message TEXT -- nullable\n *\n * Security pattern (ADR-006 Invariant C-5): rows store ONLY the columns\n * above — NEVER step output payloads. Peer-MCP outputs may contain\n * sensitive data; we explicitly do not capture them. The\n * `ContractAuditRow` input type does not declare an `output` field, so\n * TypeScript strict-mode rejects any attempt to add one at the call\n * site (`src/contracts/audit.ts`).\n *\n * Adapter-seam discipline: no `fs`/`path`/`gray-matter`/`chokidar`\n * imports. `scripts/lint-adapters.sh` enforces.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\n\nexport interface ContractAuditRow {\n kind: \"contract_step\" | \"contract_load_error\";\n contract?: string;\n verb?: string;\n stepAlias?: string;\n vault?: string;\n ts: number;\n errorMessage?: string;\n}\n\nexport interface ListByKindOptions {\n limit?: number;\n vault?: string;\n}\n\nexport interface VerbUsageRow {\n verb: string;\n invocation_count: number;\n last_seen: number;\n}\n\ninterface ContractAuditDbRow {\n id: number;\n kind: string;\n contract: string | null;\n verb: string | null;\n step_alias: string | null;\n vault: string | null;\n ts: number;\n error_message: string | null;\n}\n\nexport class ContractAuditQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _listByKindAll: BetterSqlite3.Statement<[string, number], ContractAuditDbRow>;\n private readonly _listByKindAndVault: BetterSqlite3.Statement<\n [string, string, number],\n ContractAuditDbRow\n >;\n // Q-AUD: `kind = 'contract_step'` is a CONSTANT filter (D-A2b semantics) —\n // the aggregator counts ONLY step rows, never load_error rows.\n private readonly _aggregate: BetterSqlite3.Statement<\n [string],\n { verb: string; invocation_count: number; last_seen: number }\n >;\n\n constructor(db: BetterSqlite3.Database) {\n this._insert = db.prepare(`\n INSERT INTO contract_audit\n (kind, contract, verb, step_alias, vault, ts, error_message)\n VALUES\n (@kind, @contract, @verb, @step_alias, @vault, @ts, @error_message)\n `);\n this._listByKindAll = db.prepare<[string, number], ContractAuditDbRow>(\n \"SELECT * FROM contract_audit WHERE kind = ? ORDER BY ts DESC LIMIT ?\",\n );\n this._listByKindAndVault = db.prepare<[string, string, number], ContractAuditDbRow>(\n \"SELECT * FROM contract_audit WHERE kind = ? AND vault = ? ORDER BY ts DESC LIMIT ?\",\n );\n this._aggregate = db.prepare<\n [string],\n { verb: string; invocation_count: number; last_seen: number }\n >(\n `SELECT verb, COUNT(*) AS invocation_count, MAX(ts) AS last_seen\n FROM contract_audit\n WHERE kind = 'contract_step' AND vault = ? AND verb IS NOT NULL\n GROUP BY verb\n ORDER BY invocation_count DESC`,\n );\n }\n\n insert(row: ContractAuditRow): void {\n this._insert.run({\n kind: row.kind,\n contract: row.contract ?? null,\n verb: row.verb ?? null,\n step_alias: row.stepAlias ?? null,\n vault: row.vault ?? null,\n ts: row.ts,\n error_message: row.errorMessage ?? null,\n });\n }\n\n listByKind(kind: string, opts: ListByKindOptions = {}): ContractAuditRow[] {\n const limit = opts.limit ?? 100;\n const rows: ContractAuditDbRow[] =\n opts.vault !== undefined\n ? this._listByKindAndVault.all(kind, opts.vault, limit)\n : this._listByKindAll.all(kind, limit);\n return rows.map(toContractAuditRow);\n }\n\n aggregateVerbUsage(vault: string): VerbUsageRow[] {\n return this._aggregate.all(vault);\n }\n}\n\nfunction toContractAuditRow(row: ContractAuditDbRow): ContractAuditRow {\n const out: ContractAuditRow = {\n kind: row.kind as ContractAuditRow[\"kind\"],\n ts: row.ts,\n };\n if (row.contract !== null) out.contract = row.contract;\n if (row.verb !== null) out.verb = row.verb;\n if (row.step_alias !== null) out.stepAlias = row.step_alias;\n if (row.vault !== null) out.vault = row.vault;\n if (row.error_message !== null) out.errorMessage = row.error_message;\n return out;\n}\n","import BetterSqlite3 from \"better-sqlite3\";\nimport * as sqliteVec from \"sqlite-vec\";\n\nimport { MIGRATIONS, type MigrationContext } from \"./schema.js\";\nimport { NotesQueries } from \"./queries/notes.js\";\nimport { ChunksQueries } from \"./queries/chunks.js\";\nimport { EmbeddingsQueries } from \"./queries/embeddings.js\";\nimport { WikilinksQueries } from \"./queries/wikilinks.js\";\nimport { EdgesQueries } from \"./queries/edges.js\";\nimport { AuditQueries } from \"./queries/audit.js\";\nimport { ModelsQueries } from \"./queries/models.js\";\nimport { FtsQueries } from \"./queries/fts.js\";\nimport { AliasesQueries } from \"./queries/aliases.js\";\nimport { SectionsQueries } from \"./queries/sections.js\";\nimport { BriefSourcesQueries } from \"./queries/brief_sources.js\";\nimport { DaemonStateQueries } from \"./queries/daemon_state.js\";\nimport { ContractAuditQueries } from \"./queries/contract-audit.js\";\n\n/**\n * SQLite wrapper for a single vault.\n *\n * One Database instance corresponds to one vault DB file (or `:memory:` for tests).\n * Construction is synchronous; the static `open()` is provided for symmetry\n * with future async hooks (e.g. migration backups) — it currently just wraps\n * the constructor + migrate().\n */\nexport class Database {\n readonly handle: BetterSqlite3.Database;\n\n readonly notes: NotesQueries;\n readonly chunks: ChunksQueries;\n readonly embeddings: EmbeddingsQueries;\n readonly wikilinks: WikilinksQueries;\n /** Phase 4 / 04-01 / GRA-04: typed-edge substrate (`vault.db.edges`). */\n readonly edges: EdgesQueries;\n readonly audit: AuditQueries;\n readonly models: ModelsQueries;\n readonly fts: FtsQueries;\n readonly aliases: AliasesQueries;\n /** Phase 3 / 03-01: materialized `sections` table query namespace. */\n readonly sections: SectionsQueries;\n /** Phase 5 / BRF-* / D-06: brief→chunk reverse-index query namespace. */\n readonly briefSources: BriefSourcesQueries;\n /** Phase 5 / D-09: staleness-daemon cursor query namespace. */\n readonly daemonState: DaemonStateQueries;\n /** Phase 6 / Q-AUD: task-contract orchestration audit query namespace. */\n readonly contractAudit: ContractAuditQueries;\n\n /**\n * Name of the vault this DB belongs to, or `undefined` for `:memory:` /\n * unrecognised paths. Threaded into function-style migrations as\n * `MigrationContext.vaultName` so migration 008 can derive\n * `obsidian-fs:///` (RESEARCH §doc_uri Dual-Column Migration,\n * plan 01-02).\n */\n readonly vaultName: string | undefined;\n\n constructor(dbPath: string, vaultName?: string) {\n this.vaultName = vaultName ?? deriveVaultNameFromPath(dbPath);\n this.handle = new BetterSqlite3(dbPath);\n // WAL is invalid for :memory: databases — skip it there.\n if (dbPath !== \":memory:\") {\n this.handle.pragma(\"journal_mode = WAL\");\n }\n this.handle.pragma(\"foreign_keys = ON\");\n this.handle.pragma(\"synchronous = NORMAL\");\n\n loadSqliteVec(this.handle);\n\n // Apply schema BEFORE preparing statements — query classes prepare against\n // tables that must already exist.\n this.migrateInternal();\n\n this.notes = new NotesQueries(this.handle);\n this.chunks = new ChunksQueries(this.handle);\n // models must be constructed before embeddings — embeddings looks up\n // dim via models.getById() for routing to the correct embeddings_\n // virtual table.\n this.models = new ModelsQueries(this.handle);\n this.embeddings = new EmbeddingsQueries(this.handle, this.models);\n this.wikilinks = new WikilinksQueries(this.handle);\n // Phase 4 / 04-01 / GRA-04 (D-01): edges substrate. Only prepares\n // statements; construction order is independent of other namespaces.\n this.edges = new EdgesQueries(this.handle);\n this.audit = new AuditQueries(this.handle);\n this.fts = new FtsQueries(this.handle);\n this.aliases = new AliasesQueries(this.handle);\n this.sections = new SectionsQueries(this.handle);\n // Phase 5 / BRF-* / D-06 + D-09: brief reverse-index + daemon\n // cursor. Construction is independent — only prepares statements\n // against tables already created by migration 013.\n this.briefSources = new BriefSourcesQueries(this.handle);\n this.daemonState = new DaemonStateQueries(this.handle);\n // Phase 6 / Q-AUD: contract orchestration audit. Construction is\n // independent — only prepares statements against the table already\n // created by migration 014.\n this.contractAudit = new ContractAuditQueries(this.handle);\n }\n\n static async open(dbPath: string, vaultName?: string): Promise {\n return new Database(dbPath, vaultName);\n }\n\n close(): void {\n this.handle.close();\n }\n\n getSchemaVersion(): number {\n const row = this.handle.pragma(\"user_version\") as Array<{\n user_version: number;\n }>;\n return row[0]?.user_version ?? 0;\n }\n\n /**\n * Idempotent: applies pending migrations and bumps PRAGMA user_version.\n * Called automatically during construction; safe to call again.\n */\n migrate(): void {\n this.migrateInternal();\n }\n\n private migrateInternal(): void {\n const current = this.getSchemaVersion();\n const pending = MIGRATIONS.filter((m) => m.version > current).sort(\n (a, b) => a.version - b.version,\n );\n if (pending.length === 0) return;\n\n // SQLite's recommended table-rebuild pattern (CREATE *_new, INSERT,\n // DROP, RENAME) trips foreign-key checks mid-transaction even when\n // the data itself is consistent. The official guidance is to disable\n // FKs around the migration and verify with PRAGMA foreign_key_check\n // afterwards. PRAGMA foreign_keys cannot be toggled inside an active\n // transaction, so the toggle wraps the transactional batch.\n const fkWasOn = (this.handle.pragma(\"foreign_keys\", { simple: true }) as number) === 1;\n if (fkWasOn) this.handle.pragma(\"foreign_keys = OFF\");\n\n let highest = current;\n const ctx: MigrationContext = { vaultName: this.vaultName };\n try {\n const tx = this.handle.transaction(() => {\n for (const m of pending) {\n if (\"sql\" in m) {\n this.handle.exec(m.sql);\n } else {\n m.run(this.handle, ctx);\n }\n highest = m.version;\n }\n });\n tx();\n // Verify referential integrity post-migration. Any violation raises\n // a sqlite-error here; the migration is already committed, but at\n // least we know about the inconsistency.\n const violations = this.handle.pragma(\"foreign_key_check\") as unknown[];\n if (violations.length > 0) {\n throw new Error(\n `Migration to v${highest} produced foreign-key violations: ${JSON.stringify(violations)}`,\n );\n }\n // PRAGMA cannot be bound; safe because `highest` is a number we control.\n this.handle.pragma(`user_version = ${highest}`);\n } finally {\n if (fkWasOn) this.handle.pragma(\"foreign_keys = ON\");\n }\n }\n\n transaction(fn: () => T): T {\n return this.handle.transaction(fn)();\n }\n}\n\n/**\n * Best-effort vault-name derivation from the dbPath. Standard layout is\n * `/.vault-memory/vaults/.db` (see VaultManager.dbPathFor).\n * Returns `undefined` for `:memory:`, empty strings, or any path whose\n * basename doesn't match `.db`. Callers can override by passing an\n * explicit `vaultName` to the Database constructor (the normal path —\n * VaultManager always passes `vault.config.name`).\n */\nfunction deriveVaultNameFromPath(dbPath: string): string | undefined {\n if (!dbPath || dbPath === \":memory:\") return undefined;\n // basename: split on POSIX or Windows separator\n const segs = dbPath.split(/[\\\\/]/);\n const base = segs[segs.length - 1];\n if (!base) return undefined;\n if (!base.endsWith(\".db\")) return undefined;\n const name = base.slice(0, -3);\n if (!name) return undefined;\n return name;\n}\n\nfunction loadSqliteVec(db: BetterSqlite3.Database): void {\n try {\n sqliteVec.load(db);\n } catch (err) {\n const arch = process.arch;\n const platform = process.platform;\n const msg =\n `Failed to load sqlite-vec extension (platform=${platform}, arch=${arch}). ` +\n `Ensure the matching prebuilt binary (sqlite-vec-${platform}-${arch}) is installed. ` +\n `On Apple Silicon, install sqlite-vec-darwin-arm64.`;\n throw new Error(`${msg}\\nOriginal: ${(err as Error).message}`);\n }\n}\n","export { Database } from \"./database.js\";\nexport { INITIAL_SCHEMA, MIGRATIONS } from \"./schema.js\";\nexport type { Migration } from \"./schema.js\";\nexport type { IndexRunRow, WriteAuditRow } from \"./types.js\";\n\nexport { NotesQueries } from \"./queries/notes.js\";\nexport type { UpsertNoteInput } from \"./queries/notes.js\";\n\nexport { ChunksQueries } from \"./queries/chunks.js\";\nexport type { ChunkInput } from \"./queries/chunks.js\";\n\nexport { EmbeddingsQueries } from \"./queries/embeddings.js\";\nexport type { EmbeddingInput, SemanticHit } from \"./queries/embeddings.js\";\n\nexport { WikilinksQueries } from \"./queries/wikilinks.js\";\nexport type {\n WikilinkInput,\n BacklinkRow,\n ForwardLinkRow,\n BrokenLinkRow,\n} from \"./queries/wikilinks.js\";\n\nexport { AuditQueries } from \"./queries/audit.js\";\nexport type {\n StartRunInput,\n FinishRunStats,\n RecordWriteInput,\n ListWritesFilter,\n} from \"./queries/audit.js\";\n\nexport { ModelsQueries } from \"./queries/models.js\";\nexport type { UpsertModelInput } from \"./queries/models.js\";\n\nexport { FtsQueries } from \"./queries/fts.js\";\nexport type { BM25Hit } from \"./queries/fts.js\";\n\nexport { AliasesQueries } from \"./queries/aliases.js\";\nexport type { AliasResolveHit } from \"./queries/aliases.js\";\n","/**\n * Vault Manager — holds one Database per configured vault.\n *\n * Responsibilities:\n * - Open DBs on demand under ~/.vault-memory/vaults/.db\n * - Apply migrations on first open\n * - Provide resolved Vault objects (config + db handle) to consumers\n * - Clean shutdown\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { mkdir } from \"node:fs/promises\";\nimport { Database } from \"../db/index.js\";\nimport type { VaultConfig } from \"../types.js\";\n\nexport interface Vault {\n readonly config: VaultConfig;\n readonly db: Database;\n readonly dbPath: string;\n}\n\nexport class VaultManager {\n private readonly vaults = new Map();\n\n static dbDirectory(): string {\n return join(homedir(), \".vault-memory\", \"vaults\");\n }\n\n static dbPathFor(vaultName: string): string {\n return join(VaultManager.dbDirectory(), `${vaultName}.db`);\n }\n\n /**\n * Initialize all vaults from config. Creates DB files if missing, runs\n * migrations. Idempotent — safe to call multiple times.\n */\n async loadAll(configs: readonly VaultConfig[]): Promise {\n await mkdir(VaultManager.dbDirectory(), { recursive: true });\n\n for (const cfg of configs) {\n if (this.vaults.has(cfg.name)) continue;\n\n const dbPath = VaultManager.dbPathFor(cfg.name);\n // Pass vault name explicitly so migration 008 (doc_uri backfill) can\n // derive `obsidian-fs:///` without parsing dbPath.\n const db = new Database(dbPath, cfg.name);\n db.migrate();\n\n this.vaults.set(cfg.name, { config: cfg, db, dbPath });\n }\n }\n\n get(name: string): Vault | null {\n return this.vaults.get(name) ?? null;\n }\n\n /**\n * Get a vault or throw with a helpful message.\n */\n require(name: string): Vault {\n const v = this.vaults.get(name);\n if (!v) {\n const known = [...this.vaults.keys()].join(\", \") || \"(none)\";\n throw new Error(`Unknown vault: \"${name}\". Configured vaults: ${known}`);\n }\n return v;\n }\n\n list(): Vault[] {\n return [...this.vaults.values()];\n }\n\n closeAll(): void {\n for (const v of this.vaults.values()) {\n v.db.close();\n }\n this.vaults.clear();\n }\n}\n","export { VaultManager } from \"./manager.js\";\nexport type { Vault } from \"./manager.js\";\n","/**\n * Exponential backoff retry helper.\n *\n * Retries an async function with exponential backoff + jitter.\n * By default retries on any thrown error; callers can opt out via `shouldRetry`.\n */\n\nexport interface RetryOptions {\n retries: number;\n baseDelayMs?: number;\n maxDelayMs?: number;\n shouldRetry?: (error: unknown) => boolean;\n}\n\nconst DEFAULT_BASE_DELAY_MS = 100;\nconst DEFAULT_MAX_DELAY_MS = 5000;\n\nfunction sleep(ms: number): Promise {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction computeDelay(attempt: number, baseDelayMs: number, maxDelayMs: number): number {\n const exp = baseDelayMs * Math.pow(2, attempt);\n const jitter = Math.floor(Math.random() * 100);\n return Math.min(exp + jitter, maxDelayMs);\n}\n\nexport async function withRetry(fn: () => Promise, options: RetryOptions): Promise {\n const retries = options.retries;\n const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;\n const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const shouldRetry = options.shouldRetry ?? (() => true);\n\n let lastError: unknown;\n // attempts = retries + 1 total invocations (initial + retries)\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err;\n if (attempt === retries) break;\n if (!shouldRetry(err)) break;\n const delay = computeDelay(attempt, baseDelayMs, maxDelayMs);\n await sleep(delay);\n }\n }\n throw lastError;\n}\n","/**\n * Ollama HTTP client for embedding generation.\n *\n * Talks to a local (or remote) Ollama server's REST API:\n * - POST /api/embed — generate embeddings\n * - GET /api/tags — list loaded models\n *\n * Splits large batches, retries transient failures with exponential backoff,\n * and enforces per-request timeouts via AbortController.\n */\n\nimport { z } from \"zod\";\nimport type { EmbedRequest, EmbedResponse, OllamaClientOptions } from \"../types.js\";\nimport { withRetry } from \"./retry.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\nconst DEFAULT_ENDPOINT = \"http://localhost:11434\";\nconst DEFAULT_BATCH_SIZE = 10;\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_RETRIES = 3;\n\nconst EmbedResponseSchema = z.object({\n embeddings: z.array(z.array(z.number())),\n model: z.string().optional(),\n});\n\nconst TagsResponseSchema = z.object({\n models: z.array(\n z.object({\n name: z.string(),\n }),\n ),\n});\n\n/**\n * Phase 5 / D-10 tier 2 — `/api/chat` REST response.\n *\n * Mirrors `EmbedResponseSchema` shape: Zod-validated at the HTTP boundary,\n * thrown errors percolate up through `withRetry` so the same isRetryable\n * predicate (network + 5xx + AbortError) drives the same retry policy.\n *\n * Optional fields (`done`, `total_duration`, `eval_count`) appear on\n * non-streaming responses and are kept as opaque metadata; the brief\n * compile path only consults `message.content` + `model`.\n */\nconst ChatResponseSchema = z.object({\n model: z.string(),\n message: z.object({\n role: z.literal(\"assistant\"),\n content: z.string(),\n }),\n done: z.boolean().optional(),\n total_duration: z.number().optional(),\n eval_count: z.number().optional(),\n});\n\n/**\n * Chat-message role union. Same shape Ollama and the MCP Sampling spec\n * use; the brief LLM ladder builds tier-2 requests with one `system`\n * message and one `user` message.\n */\nexport interface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\n/**\n * Chat request shape (POST `/api/chat`). Mirrors the v1 `embed()`\n * request shape: pure data, no internal client state. `stream: false`\n * is set inside `chat()` (we do not expose streaming on this surface).\n *\n * `options.num_predict` maps to Ollama's max-tokens equivalent and is\n * how the LLM ladder forwards `max_tokens` from `compile_brief`.\n */\nexport interface ChatRequest {\n model: string;\n messages: ChatMessage[];\n options?: {\n num_predict?: number;\n temperature?: number;\n };\n}\n\nexport interface ChatResponse {\n model: string;\n message: ChatMessage;\n}\n\n/**\n * Error thrown for non-2xx HTTP responses. Retried automatically for 5xx.\n */\nexport class OllamaHttpError extends Error {\n public readonly status: number;\n constructor(status: number, message: string) {\n super(message);\n this.name = \"OllamaHttpError\";\n this.status = status;\n }\n}\n\nfunction isRetryable(err: unknown): boolean {\n if (err instanceof OllamaHttpError) {\n return err.status >= 500 && err.status < 600;\n }\n // AbortError (timeout) — retry\n if (err instanceof Error && err.name === \"AbortError\") return true;\n // Network errors (TypeError from fetch on connection failures)\n if (err instanceof TypeError) return true;\n return false;\n}\n\nfunction stripTag(name: string): string {\n const idx = name.indexOf(\":\");\n return idx === -1 ? name : name.slice(0, idx);\n}\n\nexport class OllamaClient {\n private readonly endpoint: string;\n private readonly batchSize: number;\n private readonly timeoutMs: number;\n private readonly retries: number;\n\n constructor(options: OllamaClientOptions = {}) {\n this.endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/+$/, \"\");\n this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.retries = options.retries ?? DEFAULT_RETRIES;\n }\n\n /**\n * Generate embeddings for the request's texts.\n *\n * If `texts.length > batchSize`, splits into multiple parallel HTTP requests\n * and concatenates the resulting vectors in order.\n */\n async embed(request: EmbedRequest): Promise {\n const { model, texts } = request;\n if (texts.length === 0) {\n return { vectors: [], dim: 0, model };\n }\n\n const batches: string[][] = [];\n for (let i = 0; i < texts.length; i += this.batchSize) {\n batches.push(texts.slice(i, i + this.batchSize));\n }\n\n const results = await Promise.all(batches.map((batch) => this.embedBatch(model, batch)));\n\n const vectors: number[][] = [];\n let confirmedModel = model;\n for (const res of results) {\n vectors.push(...res.embeddings);\n if (res.model !== undefined) confirmedModel = res.model;\n }\n\n const first = vectors[0];\n if (first === undefined) {\n // Shouldn't happen — texts was non-empty\n return { vectors, dim: 0, model: confirmedModel };\n }\n const dim = first.length;\n\n return { vectors, dim, model: confirmedModel };\n }\n\n private async embedBatch(\n model: string,\n texts: string[],\n ): Promise<{ embeddings: number[][]; model?: string }> {\n return withRetry(\n async () => {\n const body = JSON.stringify({ model, input: texts });\n const response = await this.fetchWithTimeout(`${this.endpoint}/api/embed`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new OllamaHttpError(\n response.status,\n `Ollama /api/embed returned ${response.status}: ${text}`,\n );\n }\n\n const json: unknown = await response.json();\n const parsed = EmbedResponseSchema.parse(json);\n return { embeddings: parsed.embeddings, model: parsed.model };\n },\n { retries: this.retries, shouldRetry: isRetryable },\n );\n }\n\n /**\n * Phase 5 / D-10 tier 2 — synchronous chat completion via `/api/chat`.\n *\n * Single round-trip, non-streaming (`stream: false`). Mirrors the\n * `embed()` shape verbatim: `withRetry` wrapper, `fetchWithTimeout`,\n * `OllamaHttpError` on non-2xx after retry exhaustion, `isRetryable`\n * predicate (5xx + AbortError + network errors).\n *\n * The LLM ladder (`src/brief/llm-ladder.ts`) is the only production\n * caller; we keep the method on the same class so the shared retry /\n * timeout / endpoint config are honored without re-plumbing.\n */\n async chat(request: ChatRequest): Promise {\n return withRetry(\n async () => {\n const body = JSON.stringify({\n model: request.model,\n messages: request.messages,\n stream: false,\n options: request.options,\n });\n const response = await this.fetchWithTimeout(`${this.endpoint}/api/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new OllamaHttpError(\n response.status,\n `Ollama /api/chat returned ${response.status}: ${text}`,\n );\n }\n\n const json: unknown = await response.json();\n const parsed = ChatResponseSchema.parse(json);\n return { model: parsed.model, message: parsed.message };\n },\n { retries: this.retries, shouldRetry: isRetryable },\n );\n }\n\n /**\n * Check Ollama server liveness and return loaded model names.\n */\n async healthCheck(): Promise<{ ok: boolean; models?: string[]; error?: string }> {\n try {\n const response = await this.fetchWithTimeout(`${this.endpoint}/api/tags`, { method: \"GET\" });\n if (!response.ok) {\n return {\n ok: false,\n error: `HTTP ${response.status}`,\n };\n }\n const json: unknown = await response.json();\n const parsed = TagsResponseSchema.parse(json);\n return { ok: true, models: parsed.models.map((m) => m.name) };\n } catch (err) {\n const message = errorMessage(err);\n return { ok: false, error: message };\n }\n }\n\n /**\n * True iff `modelName` is loaded on the server.\n *\n * Matches both fully-qualified names (\"qwen3-embedding:latest\") and\n * tag-less names (\"qwen3-embedding\"): each is matched against the other\n * after stripping the `:tag` suffix.\n */\n async modelExists(modelName: string): Promise {\n const health = await this.healthCheck();\n if (!health.ok || health.models === undefined) return false;\n const wantBase = stripTag(modelName);\n for (const name of health.models) {\n if (name === modelName) return true;\n if (stripTag(name) === wantBase) return true;\n }\n return false;\n }\n\n private async fetchWithTimeout(url: string, init: RequestInit): Promise {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timer);\n }\n }\n}\n","export { OllamaClient, OllamaHttpError } from \"./client.js\";\nexport type { ChatMessage, ChatRequest, ChatResponse } from \"./client.js\";\nexport { withRetry } from \"./retry.js\";\nexport type { RetryOptions } from \"./retry.js\";\n","/**\n * Adapter Registry — the single minting point for branded DocIds and\n * the lookup surface for `SourceConnector` / `DeliveryAdapter` /\n * `ChangeFeed` triples (ADR-002 §Registry).\n *\n * # Branded-DocId minting (ADP-05, RESEARCH §Pattern 2)\n *\n * `DocId` is a nominal type — `string & { readonly __brand: \"DocId\" }` —\n * so raw `string` values cannot be assigned to a `DocId` parameter at\n * compile time. The brand-cast escape hatch lives ONLY inside the\n * IIFE below; this file is the SOLE module that performs it, and the\n * unsafe `mint` closure cannot leak across module boundaries (RESEARCH\n * §Pattern 2 lines 336–352). Only the validating `parseDocId` is\n * exported. The negative test `tests/types/docid-brand.test-d.ts`\n * proves the brand at compile time.\n *\n * `SourceHandle` follows the same pattern (`://`,\n * no resource path).\n *\n * # Registry shape (ADR-002 lines 256–267)\n *\n * Three independent maps — sources, deliveries, change-feeds — keyed\n * by `SourceHandle`. The registry does NOT enforce a one-to-one\n * relationship between the three roles for a given handle; an adapter\n * may register for only one or two roles. The conformance suite\n * (Plans 01-03..05) asserts the obsidian-fs adapter registers all\n * three roles under the same handle.\n *\n * # Resolver semantics\n *\n * `resolveSource(handle)` mirrors `VaultManager.require()` — throws\n * with a helpful message on miss. Use the predicate-style accessor\n * (none exposed in Phase 1; add `hasSource(handle): boolean` later if\n * a use case appears) to avoid the throw.\n *\n * # Lifecycle\n *\n * The registry is constructed once at server bootstrap and lives for\n * the process lifetime. Adapters self-register at construction time;\n * the registry does NOT own adapter lifetimes (no `close()` cascade) —\n * each adapter's owner closes it directly.\n */\n\nimport type { DocId, SourceHandle } from \"../types.js\";\nimport type { SourceConnector } from \"./source/types.js\";\nimport type { DeliveryAdapter } from \"./delivery/types.js\";\nimport type { ChangeFeed } from \"./change-feed/types.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// DocId minting — IIFE-closed per RESEARCH §Pattern 2\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Canonical DocId shape: `:///`.\n *\n * - scheme: lowercase ASCII, alphanumeric + dashes, starts with letter\n * - authority: one or more non-slash chars\n * - resource: one or more chars\n *\n * Examples that PASS: `obsidian-fs://my-vault/notes/foo.md`,\n * `notion-api://workspace-abc/page-123`.\n * Examples that FAIL: `not-a-uri` (no scheme), `OBSIDIAN://X/y`\n * (uppercase), `123://x/y` (digit-leading scheme),\n * `obsidian://` (empty authority + resource),\n * `obsidian-fs:/foo` (missing slash).\n */\nexport const DOC_ID_PATTERN = /^[a-z][a-z0-9-]*:\\/\\/[^/]+\\/.+$/;\n\n/**\n * Bare `://` — no resource path, no trailing slash.\n * Used to name an adapter triple in the registry. Same scheme rules as\n * `DOC_ID_PATTERN`; authority is one or more non-slash chars.\n */\nconst SOURCE_HANDLE_PATTERN = /^[a-z][a-z0-9-]*:\\/\\/[^/]+$/;\n\nconst { parseDocId } = (() => {\n // `mint` is the ONLY unsafe brand cast in the codebase; closed inside\n // this IIFE so it cannot escape. Per RESEARCH §Pattern 2. We do NOT\n // return it — only the validating `parse` is exported.\n const mint = (s: string): DocId => s as DocId;\n const parse = (s: string): DocId => {\n if (!DOC_ID_PATTERN.test(s)) {\n throw new Error(\n `Invalid DocId: ${JSON.stringify(s)}. ` +\n `Expected :/// ` +\n `(scheme: lowercase letter + alnum/dashes; authority: non-slash; resource: non-empty).`,\n );\n }\n return mint(s);\n };\n return { parseDocId: parse };\n})();\n\nexport { parseDocId };\n\n/**\n * Construct a DocId from its components and validate via `parseDocId`.\n * Convenience helper so callers do not concatenate by hand.\n */\nexport function formatDocId(scheme: string, authority: string, resource: string): DocId {\n return parseDocId(`${scheme}://${authority}/${resource}`);\n}\n\n/**\n * Split a canonical `DocId` into its three components. Pure split —\n * defensively re-validates via `parseDocId` so a stale brand-cast cannot\n * leak malformed input through. Re-uses the SAME `DOC_ID_PATTERN` as\n * `parseDocId`; there is no second regex (single source of truth per\n * ADR-001 §I-6 canonical-serialization).\n *\n * The split is intentionally a pure string operation (`indexOf(\"://\")` +\n * `indexOf(\"/\")`) rather than a regex capture-group, because the\n * resource portion can contain `/`-separated segments that a single\n * capture group would have to greedy-match — the explicit split keeps\n * the behavior obviously correct and avoids regex-engine surprises with\n * unicode or extreme inputs.\n *\n * Used by Phase 2's `MemorySinkRegistry.findSinkContaining(docId)` and\n * by any downstream tool that needs the scheme/authority/resource parts\n * without re-validating the DocId from scratch.\n *\n * @internal Perf note (IN-01): the defensive `parseDocId(docId)` call\n * regex-tests the input on every invocation. The DocId is branded and\n * valid by construction at every call site under typecheck, so the\n * regex test is purely defense against `as DocId` smuggling in test\n * code. `MemorySinkRegistry.findSinkContaining` calls this per\n * registered sink per validator call — a measurable cost emerges only\n * if (a) the sink count grows past tens, OR (b) validator calls hit a\n * tight loop. Neither is true in v2.0.0. If it becomes true, memoize\n * here rather than dropping the defense.\n */\nexport function decomposeDocId(docId: DocId): {\n scheme: string;\n authority: string;\n resource: string;\n} {\n // Defensive: assert canonical shape via the existing parser. Cheap\n // (one regex test) and means a stale brand-cast cannot smuggle a\n // malformed value through this helper.\n parseDocId(docId);\n const schemeEnd = docId.indexOf(\"://\");\n const scheme = docId.slice(0, schemeEnd);\n const rest = docId.slice(schemeEnd + 3);\n const authoritySlash = rest.indexOf(\"/\");\n const authority = rest.slice(0, authoritySlash);\n const resource = rest.slice(authoritySlash + 1);\n return { scheme, authority, resource };\n}\n\n/**\n * Validate and brand a `SourceHandle` — bare `://`,\n * no resource path. Throws on malformed input.\n */\nexport function parseSourceHandle(s: string): SourceHandle {\n if (!SOURCE_HANDLE_PATTERN.test(s)) {\n throw new Error(\n `Invalid SourceHandle: ${JSON.stringify(s)}. ` +\n `Expected :// with no resource path or trailing slash.`,\n );\n }\n return s as SourceHandle;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AdapterRegistry — handle → adapter resolver triad\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Registry of adapter triples, keyed by `SourceHandle`. Mirrors\n * `VaultManager` shape (`src/vault/manager.ts:23–78`).\n *\n * Adapters self-register at construction time; the registry does not\n * own adapter lifetimes. Lookups throw with a helpful message on miss.\n */\nexport class AdapterRegistry {\n private readonly sources = new Map();\n private readonly deliveries = new Map();\n private readonly changeFeeds = new Map();\n\n // ── source ────────────────────────────────────────────────────────────────\n\n /** Register a source. Overwrites any prior registration under the same handle. */\n registerSource(handle: SourceHandle, adapter: SourceConnector): void {\n this.sources.set(handle, adapter);\n }\n\n /** Resolve a source. Throws with a helpful message on miss. */\n resolveSource(handle: SourceHandle): SourceConnector {\n const a = this.sources.get(handle);\n if (!a) {\n const known = [...this.sources.keys()].join(\", \") || \"(none)\";\n throw new Error(`Unknown source handle: \"${handle}\". Registered sources: ${known}`);\n }\n return a;\n }\n\n /** List registered source handles. */\n listSources(): SourceHandle[] {\n return [...this.sources.keys()];\n }\n\n // ── delivery ──────────────────────────────────────────────────────────────\n\n registerDelivery(handle: SourceHandle, adapter: DeliveryAdapter): void {\n this.deliveries.set(handle, adapter);\n }\n\n resolveDelivery(handle: SourceHandle): DeliveryAdapter {\n const a = this.deliveries.get(handle);\n if (!a) {\n const known = [...this.deliveries.keys()].join(\", \") || \"(none)\";\n throw new Error(`Unknown delivery handle: \"${handle}\". Registered deliveries: ${known}`);\n }\n return a;\n }\n\n listDeliveries(): SourceHandle[] {\n return [...this.deliveries.keys()];\n }\n\n // ── change-feed ───────────────────────────────────────────────────────────\n\n registerChangeFeed(handle: SourceHandle, feed: ChangeFeed): void {\n this.changeFeeds.set(handle, feed);\n }\n\n resolveChangeFeed(handle: SourceHandle): ChangeFeed {\n const f = this.changeFeeds.get(handle);\n if (!f) {\n const known = [...this.changeFeeds.keys()].join(\", \") || \"(none)\";\n throw new Error(`Unknown change-feed handle: \"${handle}\". Registered feeds: ${known}`);\n }\n return f;\n }\n\n listChangeFeeds(): SourceHandle[] {\n return [...this.changeFeeds.keys()];\n }\n}\n","/**\n * Graph operations — high-level edge queries for MCP tool handlers.\n *\n * ── Phase 4 / 04-01 / GRA-04 (D-01, D-04): switch reads to edges table ──\n *\n * Reads route through `vault.db.edges`; writes stay on\n * `vault.db.wikilinks` until Plan 04-02 lands the unified extractor.\n * The `type` field on result rows is strictly additive: pre-backfill no\n * row existed, post-backfill every row is `type='wikilink'`, and Plan\n * 04-02 starts producing the other three types in the same column.\n *\n * Default behavior is unchanged from v1: with no edge-type filter the\n * tools return all rows from `edges` for the given doc, which — after\n * the migration 011 backfill — equals the v1 behavior plus the new\n * edge types once the indexer populates them.\n *\n * Thin layer above `vault.db.edges`. Returns enriched results with\n * source/target paths and titles so callers don't need to re-query\n * notes.\n */\n\nimport type { EdgeType } from \"../db/queries/edges.js\";\nimport type { Vault } from \"../vault/index.js\";\n\nexport interface BacklinkResult {\n sourcePath: string;\n sourceTitle: string;\n lineNumber: number | null;\n linkText: string | null;\n /**\n * Phase 4 / 04-01 (D-04) — additive edge type. Post-backfill every\n * row is `'wikilink'`; Plan 04-02 widens to the other three\n * `Edge.type` literals once the indexer populates them.\n *\n * `linkText` is NOT yet carried on the edges table (Plan 04-02\n * adds it). For now `linkText` stays `null` on reads from\n * `vault.db.edges.*`; the existing field shape is preserved so\n * downstream callers don't break.\n */\n type: EdgeType;\n}\n\nexport interface ForwardLinkResult {\n targetPath: string;\n resolved: boolean;\n targetTitle: string | null;\n anchor: string | null;\n linkText: string | null;\n /** Phase 4 / 04-01 (D-04) — additive edge type. */\n type: EdgeType;\n}\n\nexport interface BrokenLinkResult {\n sourcePath: string;\n sourceTitle: string;\n targetPath: string;\n lineNumber: number | null;\n /** Phase 4 / 04-01 (D-04) — additive edge type. */\n type: EdgeType;\n}\n\n/**\n * Get all notes that link TO a given note.\n *\n * @throws if `notePath` does not resolve to a known note.\n */\nexport function listBacklinks(vault: Vault, notePath: string): BacklinkResult[] {\n const note = vault.db.notes.getByPath(notePath);\n if (!note) {\n throw new Error(`Note not found: ${notePath}`);\n }\n\n // ── Phase 4 / 04-01 / GRA-04 (D-01, D-04): switch reads to edges table ──\n //\n // Post-backfill every row has type='wikilink'; Plan 04-02 starts\n // producing the other three types.\n const rows = vault.db.edges.getBacklinks(note.id);\n const results: BacklinkResult[] = [];\n for (const row of rows) {\n const src = vault.db.notes.getById(row.sourceNoteId);\n if (!src) continue; // FK should prevent this, but be defensive.\n results.push({\n sourcePath: src.path,\n sourceTitle: src.title,\n lineNumber: row.lineNumber,\n linkText: row.linkText,\n type: row.type,\n });\n }\n return results;\n}\n\n/**\n * Get all forward links FROM a given note.\n *\n * @param includeBroken include unresolved links (default: true)\n * @throws if `notePath` does not resolve to a known note.\n */\nexport function listForwardLinks(\n vault: Vault,\n notePath: string,\n includeBroken: boolean = true,\n): ForwardLinkResult[] {\n const note = vault.db.notes.getByPath(notePath);\n if (!note) {\n throw new Error(`Note not found: ${notePath}`);\n }\n\n // ── Phase 4 / 04-01 / GRA-04 (D-01, D-04): switch reads to edges table ──\n const rows = vault.db.edges.getForwardLinks(note.id);\n const results: ForwardLinkResult[] = [];\n for (const row of rows) {\n const resolved = row.targetNoteId !== null;\n if (!resolved && !includeBroken) continue;\n\n let targetTitle: string | null = null;\n if (resolved && row.targetNoteId !== null) {\n const target = vault.db.notes.getById(row.targetNoteId);\n targetTitle = target?.title ?? null;\n }\n\n results.push({\n // For hyperlink / external edges the target is a URL string; for\n // wikilinks it's the original path. Either way `target_path` on\n // the edges row preserves the v1 wikilinks.target_path shape.\n // When `target_path` is NULL (resolved internal-edge with no\n // raw target string), surface the empty string — preserves the\n // existing `targetPath: string` contract.\n targetPath: row.targetPath ?? \"\",\n resolved,\n targetTitle,\n anchor: row.anchor,\n linkText: row.linkText,\n type: row.type,\n });\n }\n return results;\n}\n\n/**\n * List all broken links in the vault (where `target_doc IS NULL`).\n *\n * ── Phase 4 / 04-01 / GRA-04 (D-01, D-04): switch reads to edges table ──\n *\n * Reads route through `vault.db.edges.resolveBrokenLinks()`, which now\n * carries `line_number` directly (unlike the prior v1\n * `vault.db.wikilinks.resolveBrokenLinks()` which omitted it). Existing\n * call sites that observed `lineNumber === null` continue to receive\n * `null` for any pre-04-01 row that didn't capture a line; new rows\n * (post-04-02 unified extractor) will carry real line numbers.\n */\nexport function findBrokenLinks(vault: Vault): BrokenLinkResult[] {\n const rows = vault.db.edges.resolveBrokenLinks();\n if (rows.length === 0) return [];\n\n const noteCache = new Map();\n\n const results: BrokenLinkResult[] = [];\n for (const row of rows) {\n let src = noteCache.get(row.sourceNoteId);\n if (!src) {\n const n = vault.db.notes.getById(row.sourceNoteId);\n if (!n) continue;\n src = { path: n.path, title: n.title };\n noteCache.set(row.sourceNoteId, src);\n }\n\n results.push({\n sourcePath: src.path,\n sourceTitle: src.title,\n // `target_path` is NULLABLE on the edges row — only broken\n // wikilinks (and external hyperlinks) carry a raw target.\n targetPath: row.targetPath ?? \"\",\n // v1 behavior: findBrokenLinks always returned `lineNumber: null`\n // (the v1 wikilinks.resolveBrokenLinks query omitted the column).\n // Plan 04-01 preserves that contract to keep the result shape\n // byte-identical; Plan 04-02 may surface `row.lineNumber` directly\n // once the unified extractor lands.\n lineNumber: null,\n type: row.type,\n });\n }\n return results;\n}\n\n// Re-export EdgeType so consumers of the graph barrel can type-check\n// against the same union as the underlying edges table.\nexport type { EdgeType };\n","/**\n * Citation Packet — the Phase 3 ASM-05 packet shape, pinned at the\n * Phase 2 floor.\n *\n * D-01 mandates an 8-field packet:\n *\n * { doc_id, source_handle, title, heading_path, mtime, hash,\n * display_url, properties }\n *\n * Two notes on field names:\n *\n * - `source_handle` on the packet maps to `Document.source` on the\n * canonical content type (`src/types.ts`). The mapper transcribes\n * accordingly — the packet uses the more explicit name because\n * consumers (recall callers, Phase 3 assembly tools) see the packet\n * surface, not the internal `Document` shape.\n *\n * - `hash` on the packet IS the read-side `Document.hash` (canonical\n * content hash returned by `SourceConnector.readDocument`). This is\n * DISTINCT from the write-side `WriteSuccess.newHash` returned by\n * `record_observation` / `supersede`. Both are correct names in\n * their respective domains; the packet uses the read-side name\n * because citation packets are READ artifacts.\n *\n * `heading_path` is a packet-only D-01 field. The canonical `Document`\n * type does not carry one (Phase 3 may add it via the BlockNode tree);\n * the mapper accepts an optional `heading_path` on the input shape and\n * defaults to an empty array when not present. The mapper deep-copies\n * the array so caller mutations do not leak into the source `Document`.\n *\n * `properties` is shallow-copied for the same reason — a `{...obj}`\n * spread is sufficient because callers should never mutate the inner\n * property values, only add/remove keys at the top level.\n *\n * Phase 3 ASM-05 will import `CitationPacket` from this module to keep\n * the recall (Phase 2) and assembly (Phase 3) surfaces in lockstep.\n */\n\nimport type { DocId, Document, SourceHandle } from \"../types.js\";\n\n/**\n * D-01 packet shape — exactly 8 fields. Phase 3 may extend additively;\n * Phase 2 ships all 8 as the floor.\n */\nexport interface CitationPacket {\n /** Opaque, branded DocId — the document's identity. */\n doc_id: DocId;\n /** Adapter handle that produced this document. */\n source_handle: SourceHandle;\n /** Short human-readable title. */\n title: string;\n /** Heading-path array (root → leaf); empty when the doc has no heading. */\n heading_path: string[];\n /** Last-modified time, epoch ms. */\n mtime: number;\n /** Read-side content hash from `Document.hash`. */\n hash: string;\n /** Adapter-provided deep-link URL (`displayUrlFor(doc.id)` for obsidian-fs). */\n display_url: string;\n /** Untyped property bag (YAML frontmatter, typed properties, …). */\n properties: Record;\n}\n\n/**\n * Attach the denormalized `status` / `superseded_by` extras to a base\n * `CitationPacket` (or any subtype). Reads from the packet's REQUIRED\n * `properties` bag (`Record`, always populated) — no\n * null guards needed for `properties` itself, only for the inner keys.\n *\n * Generic so callers that pass a `CitationPacket` subtype (e.g. a packet\n * already carrying `relation`) keep their extra fields. Returns a fresh\n * object; does not mutate the input packet.\n *\n * Shared by `assembleDossier` (anchor + linked docs) and `assembleBundle`\n * (anchor) — both denormalize the same two property keys identically.\n */\nexport function withPropertyExtras(\n packet: T,\n): T & { status?: string; superseded_by?: string } {\n const out: T & { status?: string; superseded_by?: string } = { ...packet };\n const status = packet.properties.status;\n if (typeof status === \"string\") out.status = status;\n const supersededBy = packet.properties.superseded_by;\n if (typeof supersededBy === \"string\") out.superseded_by = supersededBy;\n return out;\n}\n\n/**\n * Map a `Document` (or its read-side fields) into a `CitationPacket`.\n *\n * Field transcription:\n * - `doc.id` → `packet.doc_id`\n * - `doc.source` → `packet.source_handle` (renamed for the packet surface)\n * - `doc.title` → `packet.title`\n * - `doc.heading_path` (optional) → `packet.heading_path` (defaults to `[]`)\n * - `doc.mtime` → `packet.mtime`\n * - `doc.hash` → `packet.hash` (read-side; not `newHash`)\n * - `displayUrl` → `packet.display_url` (callers compute via `displayUrlFor`)\n * - `doc.properties` → `packet.properties` (shallow-copied)\n *\n * Caller mutations on the returned packet's `heading_path` array or\n * `properties` object cannot leak back into the source `Document` — the\n * array is spread-copied and the property bag is spread-copied at the\n * top level.\n */\nexport function toCitationPacket(\n doc: Pick & {\n heading_path?: string[];\n },\n displayUrl: string,\n): CitationPacket {\n return {\n doc_id: doc.id,\n source_handle: doc.source,\n title: doc.title,\n heading_path: doc.heading_path ? [...doc.heading_path] : [],\n mtime: doc.mtime,\n hash: doc.hash,\n display_url: displayUrl,\n properties: { ...doc.properties },\n };\n}\n\n/**\n * Compute a display URL for a `DocId` via the adapter's\n * `formatDisplayUrl` seam (ADR-002 §SourceConnector).\n *\n * This thin wrapper preserves the seam: adapter-specific URL literals\n * live in the source adapter (the single licensed site per the I-5b\n * lint rule). A future Notion / Slack adapter publishes its own\n * deep-link convention; recall does not encode any URL scheme inline.\n *\n * Contract: `formatDisplayUrl` is OPTIONAL on the `SourceConnector`\n * interface (some adapters may not have deep links). When the adapter\n * omits the method or returns `null`, this helper falls back to the\n * DocId string itself so callers always get a non-null `display_url`\n * on the citation packet.\n */\nexport function displayUrlFor(\n docId: DocId,\n source: { formatDisplayUrl?: (id: DocId) => string | null },\n): string {\n return source.formatDisplayUrl?.(docId) ?? docId;\n}\n","/**\n * `expand()` — Phase 4 / 04-03 / GRA-01 typed-edge BFS retrieval.\n *\n * Returns a flat, dedup'd array of citation packets reachable from\n * `seed_doc_ids` within `hops` (1 or 2). Each packet carries an additive\n * `via: { seed_doc_id, hop, edge_type, direction }` provenance trace.\n *\n * Locked contracts (Phase 4 CONTEXT.md):\n * - D-05 Hops hard-capped at 2 (enforced by Zod literal union at the\n * tool boundary; this function trusts the bound).\n * - D-06 `direction` defaults to `\"both\"` (forward+backward).\n * - D-07 Shortest-path dedup via `isShorterPath` comparator.\n * Tie-breakers: lower hop → lower seed_doc_id (lex) → lower\n * edge_type (alpha) → forward over backward.\n * - D-08 `filter_properties` is strict equality on the hydrated\n * packet's `properties` bag. `include_superseded` defaults\n * false; superseded docs are dropped at hydration time via the\n * Phase 2 D-03 forward-only supersede property.\n * - D-09 Module lives in `src/graph/` alongside `graph.ts`.\n *\n * `_memory` opacity rule (ADR-004 §\"memory namespace is sacrosanct\" +\n * Phase 4 RESEARCH.md Pitfall 3):\n * A `_memory/...` doc surfaces in the result set ONLY when an inbound\n * edge in the BFS visited record originates from a non-`_memory`\n * source (a user note that already linked to it). 2-hop traversal MAY\n * NOT surface a `_memory/...` doc via an internal `_memory → _memory`\n * chain that does not pass through a user note first. We track the\n * `inboundSourceNoteId` for each visited node as the BFS expands so\n * the opacity check is O(1) per candidate at hydration time (no\n * second DB pass — T-04-03-01 mitigation).\n *\n * Pitfall 4 (RESEARCH.md lines 536–541): `isShorterPath` is exported as\n * a pure function and unit-tested directly. The comparator pins the\n * tie-breaker order so the `via` field is deterministic across runs\n * (T-04-03-05 mitigation).\n *\n * Unknown seed_doc_ids return as `warnings: [{seed_doc_id, reason:\n * \"unknown_doc\"}]` — soft warning shape, NOT a hard throw (Phase 4\n * CONTEXT §\"Claude's Discretion\" — error semantics on broken seeds).\n *\n * Adapter-seam discipline (Phase 1 Pattern A): zero imports of `fs`,\n * `path.join`, `gray-matter`, or `chokidar`. The hydration path goes\n * through the injected `SourceConnector.readDocument` seam; all SQL\n * reads go through `vault.db.edges` / `vault.db.notes`.\n */\n\nimport { decomposeDocId, formatDocId, parseDocId } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport type { EdgeType } from \"../db/queries/edges.js\";\nimport { type CitationPacket, displayUrlFor, toCitationPacket } from \"../memory/citation-packet.js\";\nimport type { DocId, Document } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\n\n// ─── public types ────────────────────────────────────────────────────────────\n\n/**\n * Direction of edge traversal. Default `\"both\"` per D-06.\n *\n * - `\"forward\"` — traverse outbound edges (seed → target).\n * - `\"backward\"` — traverse inbound edges (source → seed).\n * - `\"both\"` — both directions; results merge with shortest-path\n * dedup applied per D-07.\n */\nexport type ExpandDirection = \"forward\" | \"backward\" | \"both\";\n\n/**\n * Input shape for `expand()`. The Zod schema in `tool-registry.ts`\n * mirrors this verbatim; this interface is the runtime contract.\n */\nexport interface ExpandOptions {\n /** 1+ branded DocIds (URI-style, e.g. `obsidian-fs://vault/path.md`). */\n seed_doc_ids: DocId[];\n /** Hard-capped at 2 per D-05 (Zod literal union enforces this). */\n hops: 1 | 2;\n /** Direction per D-06; default `\"both\"`. */\n direction?: ExpandDirection;\n /** Optional edge-type filter; default = all four types. */\n edge_types?: EdgeType[];\n /**\n * Strict-equality predicate on `Document.properties`. No operators.\n * D-08 mirrors Phase 3 dossier convention.\n */\n filter_properties?: Record;\n /** Default false per D-08; drops `properties.status === \"superseded\"`. */\n include_superseded?: boolean;\n}\n\n/**\n * Provenance trace attached to each result packet. Records HOW the\n * neighbor was reached: the seed that originated the BFS, the hop\n * count (1 or 2), the edge type, and the direction of traversal.\n *\n * Determinism: the comparator `isShorterPath` pins which trace wins\n * when multiple paths reach the same target (D-07 tie-breakers).\n */\nexport interface ViaTrace {\n seed_doc_id: DocId;\n hop: 1 | 2;\n edge_type: EdgeType;\n direction: \"forward\" | \"backward\";\n}\n\n/**\n * A citation packet (Phase 3 D-05 locked 8-field shape) with the\n * Phase-4-additive `via` field. None of the existing 8 fields are\n * reshaped; `via` is strictly additive (Pattern E).\n */\nexport interface CitationPacketWithVia extends CitationPacket {\n via: ViaTrace;\n}\n\n/**\n * Output shape: deduplicated `documents` (one per unique target doc,\n * with the shortest-path `via`) + soft `warnings` for unknown seeds.\n */\nexport interface ExpansionResult {\n documents: CitationPacketWithVia[];\n warnings: Array<{ seed_doc_id: string; reason: \"unknown_doc\" }>;\n}\n\n/**\n * Injected dependencies for `expand()`. Mirrors the dossier / bundle\n * dep shape so production wiring + unit tests share one contract.\n */\nexport interface ExpandDeps {\n manager: VaultManager;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\n// ─── pure comparator (Pitfall 4 — unit-tested directly) ─────────────────────\n\n/**\n * True iff `a` is a STRICTLY shorter / preferable path than `b`.\n *\n * Tie-breaker order per D-07:\n * 1. lower `hop` wins (shortest path);\n * 2. lower `seed_doc_id` (lexicographic) wins;\n * 3. lower `edge_type` (alphabetical) wins;\n * 4. `\"forward\"` wins over `\"backward\"`.\n *\n * Returns `false` for identical traces — the comparator is strict, not\n * `<=`. The BFS uses this to decide whether to OVERWRITE an existing\n * visited entry when a shorter path is found.\n *\n * Pitfall 4 mitigation: pure function, no side effects, no DB access.\n * Unit-tested directly (RESEARCH.md lines 536–541).\n */\nexport function isShorterPath(a: ViaTrace, b: ViaTrace): boolean {\n // 1) hop\n if (a.hop !== b.hop) return a.hop < b.hop;\n // 2) seed_doc_id (lex)\n if (a.seed_doc_id !== b.seed_doc_id) return a.seed_doc_id < b.seed_doc_id;\n // 3) edge_type (alpha)\n if (a.edge_type !== b.edge_type) return a.edge_type < b.edge_type;\n // 4) direction — forward beats backward\n if (a.direction !== b.direction) return a.direction === \"forward\";\n return false; // identical → not strictly shorter\n}\n\n// ─── internal helpers ───────────────────────────────────────────────────────\n\nconst MEMORY_PREFIX = \"_memory/\";\n\n/**\n * Resolve a seed_doc_id to its underlying note row.\n *\n * Returns `null` if the DocId is malformed, points at an unknown\n * vault, or names a note that is not indexed. The caller surfaces\n * each `null` as a `warnings: [{seed_doc_id, reason: \"unknown_doc\"}]`\n * entry per the soft-error contract.\n */\nfunction resolveSeed(\n deps: ExpandDeps,\n seedDocId: DocId,\n): { vault: Vault; vaultName: string; noteId: number; notePath: string; scheme: string } | null {\n let scheme: string;\n let vaultName: string;\n let resource: string;\n try {\n const docId = parseDocId(seedDocId);\n ({ scheme, authority: vaultName, resource } = decomposeDocId(docId));\n } catch {\n return null;\n }\n let vault: Vault;\n try {\n vault = deps.manager.require(vaultName);\n } catch {\n return null;\n }\n const note = vault.db.notes.getByPath(resource);\n if (!note) return null;\n return { vault, vaultName, noteId: note.id, notePath: resource, scheme };\n}\n\n/** True iff a note path (vault-relative, forward-slash) lives in `_memory/...`. */\nfunction isMemoryPath(notePath: string): boolean {\n return notePath.startsWith(MEMORY_PREFIX);\n}\n\n/** Mutable BFS bookkeeping entry — one per visited noteId. */\ninterface VisitedEntry {\n via: ViaTrace;\n /**\n * The noteId of the SOURCE doc on the edge that produced this\n * candidate's `via` trace. Used at hydration time by the\n * `_memory` opacity check (Pitfall 3): a `_memory` target survives\n * only when its `inboundSourceNoteId` is a non-`_memory` doc. The\n * seeds themselves do not have an inbound edge — they're skipped\n * for the opacity rule because they are EXPLICITLY requested by\n * the caller (a user-driven action; not silent traversal).\n */\n inboundSourceNoteId: number;\n}\n\n// ─── public entry point ─────────────────────────────────────────────────────\n\n/**\n * Bounded typed-edge BFS retrieval. See file header for the full\n * algorithm. Returns deduplicated citation packets with `via`\n * provenance. Empty `seed_doc_ids` returns\n * `{documents: [], warnings: []}`.\n */\nexport async function expand(deps: ExpandDeps, opts: ExpandOptions): Promise {\n const warnings: ExpansionResult[\"warnings\"] = [];\n\n // Empty seeds → trivial empty result. Matches Phase 3 dossier's\n // \"empty result on no-match\" convention.\n if (opts.seed_doc_ids.length === 0) {\n return { documents: [], warnings };\n }\n\n const direction: ExpandDirection = opts.direction ?? \"both\";\n const hops = opts.hops;\n const edgeTypeFilter =\n opts.edge_types && opts.edge_types.length > 0 ? opts.edge_types : undefined;\n\n // Resolve each seed → noteRow. Misses become warnings; we keep\n // processing the rest. Track seed noteIds so the BFS can short-\n // circuit self-loops (test 17/18).\n interface ResolvedSeed {\n seedDocId: DocId;\n vault: Vault;\n vaultName: string;\n noteId: number;\n notePath: string;\n scheme: string;\n }\n const resolved: ResolvedSeed[] = [];\n const seedNoteIds = new Set();\n for (const id of opts.seed_doc_ids) {\n const r = resolveSeed(deps, id);\n if (!r) {\n warnings.push({ seed_doc_id: id, reason: \"unknown_doc\" });\n continue;\n }\n resolved.push({\n seedDocId: id,\n vault: r.vault,\n vaultName: r.vaultName,\n noteId: r.noteId,\n notePath: r.notePath,\n scheme: r.scheme,\n });\n seedNoteIds.add(r.noteId);\n }\n\n // Group resolved seeds by vault — BFS operates per-vault because\n // `vault.db.edges` is scoped to one vault. Cross-vault expand would\n // require the edges to carry vault-namespaced DocIds; v2.0.0 holds\n // each vault's graph independent (matches `list_backlinks` semantics).\n //\n // Within a vault, `visited` is keyed by noteId so the dedup +\n // opacity check both work in O(1) per node. Per-vault `visited`\n // maps live for the BFS and are read by hydration immediately after.\n interface PerVaultState {\n vault: Vault;\n vaultName: string;\n scheme: string;\n visited: Map;\n /** Seed noteIds that originated this vault's BFS — used for self-loop skip. */\n seedNoteIdsInVault: Set;\n }\n const byVault = new Map();\n for (const r of resolved) {\n if (!byVault.has(r.vaultName)) {\n byVault.set(r.vaultName, {\n vault: r.vault,\n vaultName: r.vaultName,\n scheme: r.scheme,\n visited: new Map(),\n seedNoteIdsInVault: new Set(),\n });\n }\n byVault.get(r.vaultName)?.seedNoteIdsInVault.add(r.noteId);\n }\n\n // ── BFS per seed ─────────────────────────────────────────────────────────\n //\n // For each resolved seed, run up to two single-direction BFS sweeps\n // (forward + backward when direction === 'both'). Frontier elements\n // carry `{noteId, depth}`. At each depth < hops, query the typed-\n // edge namespace for outbound / inbound rows, apply edge-type\n // filter, skip self-loops + unresolved hyperlinks, then record the\n // candidate in `visited` IF (it's new) OR (the new path is shorter\n // per `isShorterPath`).\n //\n // The seed itself is never added to `visited` — it is the BFS root,\n // not an \"expansion result\". Test 17/18 pin this.\n for (const seed of resolved) {\n const state = byVault.get(seed.vaultName);\n if (!state) continue; // unreachable — we just set it.\n const directionsToWalk: Array<\"forward\" | \"backward\"> =\n direction === \"both\" ? [\"forward\", \"backward\"] : [direction];\n for (const dir of directionsToWalk) {\n let frontier: Array<{ noteId: number; depth: number }> = [{ noteId: seed.noteId, depth: 0 }];\n while (frontier.length > 0) {\n const next: Array<{ noteId: number; depth: number }> = [];\n for (const node of frontier) {\n const newHop: 1 | 2 = (node.depth + 1) as 1 | 2;\n if (newHop > hops) continue; // depth bound\n const rows =\n dir === \"forward\"\n ? seed.vault.db.edges.getForwardLinks(node.noteId, edgeTypeFilter)\n : seed.vault.db.edges.getBacklinks(node.noteId, edgeTypeFilter);\n for (const row of rows) {\n // Resolve the neighbor noteId. For forward edges, the\n // neighbor is `target_doc` (null = unresolved hyperlink —\n // skip; Phase 4 BFS only traverses resolved edges). For\n // backward edges, the neighbor is `source_doc` (always\n // non-null — every edge has a source).\n const targetNoteId =\n dir === \"forward\"\n ? // EdgeForwardLinkRow shape\n (row as { targetNoteId: number | null }).targetNoteId\n : (row as { sourceNoteId: number }).sourceNoteId;\n if (targetNoteId === null) continue; // unresolved hyperlink\n // Self-loop guard (test 17/18): a seed cannot appear in\n // its own results regardless of edge presence.\n if (targetNoteId === seed.noteId) continue;\n // Also skip ANY seed appearing as a 1/2-hop neighbor —\n // seeds are the BFS roots, not results. The plan §\n // describes this as \"seeds are NOT added to visited\".\n // We DO allow OTHER seeds in the result set when expanded\n // from a non-seed source? Spec is ambiguous; the safer\n // reading is: a seed is never a RESULT of expand. Tests\n // 7/8 model the multi-seed case where one seed is reached\n // from another — but those tests assert dedup by hop, not\n // appearance. Re-reading test 8: \"a doc reachable in 1 hop\n // from seed B and 2 hops from seed A appears with via.\n // seed_doc_id === B and via.hop === 1.\" The doc is NOT a\n // seed itself in that test — it's a separate doc. So\n // skipping ALL seeds from the result set matches the\n // expected behavior (and matches recall/dossier semantics\n // where the query input is never echoed back).\n if (state.seedNoteIdsInVault.has(targetNoteId)) continue;\n const candidate: ViaTrace = {\n seed_doc_id: seed.seedDocId,\n hop: newHop,\n edge_type: row.type,\n direction: dir,\n };\n const existing = state.visited.get(targetNoteId);\n if (!existing || isShorterPath(candidate, existing.via)) {\n state.visited.set(targetNoteId, {\n via: candidate,\n inboundSourceNoteId: node.noteId,\n });\n // Only push into next frontier if more hops remain.\n if (newHop < hops) {\n next.push({ noteId: targetNoteId, depth: newHop });\n }\n }\n }\n }\n frontier = next;\n }\n }\n }\n\n // ── Hydration + filters ──────────────────────────────────────────────────\n //\n // For each visited noteId in each vault, load the source `Document`\n // via the injected SourceConnector seam, build the canonical 8-field\n // citation packet, then layer the additive `via` field. Apply the\n // three filters: `_memory` opacity, `include_superseded`, and\n // `filter_properties`.\n //\n // Stale rows (note deleted between BFS and hydration, or read fails)\n // are silently dropped — same defensive posture as dossier + recall.\n const documents: CitationPacketWithVia[] = [];\n for (const [, state] of byVault) {\n // Pre-compute the set of `_memory` noteIds in this vault's visited\n // map so the opacity check is a Set lookup. The check is per\n // candidate (O(1)). Seeds themselves are not in `visited` and so\n // do not participate; only candidates need the rule applied.\n const memoryVisited = new Set();\n for (const [noteId] of state.visited) {\n const row = state.vault.db.notes.getById(noteId);\n if (row && isMemoryPath(row.path)) memoryVisited.add(noteId);\n }\n\n for (const [noteId, entry] of state.visited) {\n const noteRow = state.vault.db.notes.getById(noteId);\n if (!noteRow) continue; // stale BFS row — drop defensively.\n\n // ── _memory opacity rule (ADR-004 + Pitfall 3) ─────────────────\n //\n // A `_memory/...` doc surfaces in the result set ONLY when its\n // inbound BFS edge originates from a non-`_memory` source. The\n // edge's source is captured at frontier expansion as\n // `entry.inboundSourceNoteId` (no second DB query).\n //\n // Concretely:\n // - Candidate is non-`_memory` → always include (subject to\n // other filters).\n // - Candidate is `_memory` AND inbound source is also `_memory`\n // → drop (silent traversal through the memory namespace is\n // forbidden). Cite ADR-004 §\"memory namespace is sacrosanct\"\n // and Pitfall 3.\n // - Candidate is `_memory` AND inbound source is a non-\n // `_memory` user note → include (the user note already\n // references the memory doc; surfacing it does not break\n // opacity).\n //\n // Note: seeds are never `_memory` candidates here — they are\n // BFS roots and are not added to `visited`. If a user explicitly\n // requests a `_memory/...` seed, that's their call (a user-driven\n // action, not silent traversal), and the BFS expands from it\n // normally; but the seed itself is filtered out of results by\n // the seedNoteIdsInVault guard above.\n if (memoryVisited.has(noteId)) {\n const inboundSourceRow = state.vault.db.notes.getById(entry.inboundSourceNoteId);\n const inboundIsMemory = inboundSourceRow != null && isMemoryPath(inboundSourceRow.path);\n if (inboundIsMemory) continue;\n }\n\n // Load the canonical Document via the adapter seam (ADR-002 I-5b).\n const docId = formatDocId(state.scheme, state.vaultName, noteRow.path);\n const source = (() => {\n try {\n return deps.sourceConnectorFor(state.vaultName);\n } catch {\n return null;\n }\n })();\n if (!source) continue;\n let doc: Document;\n try {\n doc = await source.readDocument(docId);\n } catch {\n continue;\n }\n const packet = toCitationPacket(doc, displayUrlFor(docId, source));\n\n // ── include_superseded filter (D-08) ────────────────────────────\n //\n // Default false drops docs whose `properties.status === \"superseded\"`.\n // Forward-only supersede per Phase 2 D-03 means this is a pure\n // property check; no additional graph traversal needed.\n if (!opts.include_superseded && packet.properties.status === \"superseded\") {\n continue;\n }\n\n // ── filter_properties strict equality (D-08) ────────────────────\n //\n // Each key/value pair in `filter_properties` must match the\n // packet's `properties` strictly via `===`. No operators (no\n // $in, no $contains). Mirrors Plan 03 dossier convention.\n if (opts.filter_properties) {\n let match = true;\n for (const [key, want] of Object.entries(opts.filter_properties)) {\n if (packet.properties[key] !== want) {\n match = false;\n break;\n }\n }\n if (!match) continue;\n }\n\n documents.push({ ...packet, via: entry.via });\n }\n }\n\n return { documents, warnings };\n}\n","/**\n * `cluster()` — Phase 4 / 04-05 / GRA-02 Louvain community detection.\n *\n * Runs modularity-maximizing community detection (Blondel et al. 2008)\n * over the typed-edge graph via `graphology` + `graphology-communities-\n * louvain`. Returns one entry per community with deterministic\n * `cluster_id = smallest member DocId` (D-12, D-14).\n *\n * Locked contracts (Phase 4 CONTEXT.md):\n * - D-10 Algorithm: Louvain modularity-maximizing (over Label\n * Propagation / Connected Components).\n * - D-11 Implementation: pure-JS ESM via graphology + graphology-\n * communities-louvain (no native bindings, no LLM).\n * - D-12 Determinism contract — same input produces byte-identical\n * `cluster_id` assignment. Enforced by:\n * 1. Sort node DocIds lexicographically BEFORE insertion.\n * 2. Insert into `new Graph({type:\"undirected\", multi:false})`\n * in sorted order.\n * 3. Pass `seedrandom(\"vault-memory-cluster-v1\")` as\n * Louvain's `rng` option (Pitfall 1).\n * 4. `cluster_id = smallest member DocId per community`.\n * 5. Sort returned clusters by `cluster_id` ascending.\n * - D-13 Hard cap at 5000 nodes; structured error return; `force: true`\n * override.\n * - D-14 Per-cluster output:\n * { cluster_id, size, members: CitationPacket[],\n * summary: { top_types, top_titles, edge_density } }\n * All pure-deterministic — NO LLM enrichment (Phase 5 brief\n * layer owns LLM coupling over cluster output).\n * - D-15a `query` path composes existing primitives:\n * search_hybrid({query, limit: query_top_k ?? 50})\n * → expand({seed_doc_ids: top_k, hops: 1, direction: \"both\"})\n * → cluster the union.\n * `seed_doc_ids` path: cluster exactly that set + its induced\n * 1-hop neighborhood. Both `query` AND `seed_doc_ids` present\n * → return `{ok:false, reason:\"both_seeds_and_query\"}`.\n *\n * `_memory` opacity rule (ADR-004) is INHERITED from `expand()` (Plan\n * 04-03): `cluster()` calls expand() to compute the neighborhood; the\n * opacity filter applies there. This module does NOT re-implement the\n * rule — Test 10 in cluster.test.ts verifies inheritance.\n *\n * Pitfall 1 (RESEARCH.md §\"Louvain non-determinism\"): a second\n * `Math.random()` call site inside the louvain library would defeat\n * the seeded RNG. The determinism snapshot test in cluster.test.ts is\n * the regression gate that would catch any future library drift on\n * this assumption.\n *\n * Adapter-seam discipline: `graphology`, `graphology-communities-\n * louvain`, and `seedrandom` are imported ONLY in this file (per Plan\n * 04-05 Pattern A). No `fs`, `path`, `gray-matter`, or `chokidar`\n * imports. The library imports are pure-JS ESM with zero native\n * bindings, so the adapter-seam invariants are not weakened.\n *\n * References:\n * - Blondel et al. 2008, \"Fast unfolding of communities in large\n * networks\" — original Louvain paper.\n * - graphology / graphology-communities-louvain: Yomguithereal et al.,\n * MIT-licensed, https://graphology.github.io/.\n */\n\nimport Graph from \"graphology\";\nimport louvain from \"graphology-communities-louvain\";\nimport seedrandom from \"seedrandom\";\n\nimport { decomposeDocId, formatDocId, parseDocId } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport type { EdgeType } from \"../db/queries/edges.js\";\nimport { type CitationPacket, displayUrlFor, toCitationPacket } from \"../memory/citation-packet.js\";\nimport type { DocId, Document, SearchHit } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\nimport { expand } from \"./expand.js\";\n\n// ─── public types ────────────────────────────────────────────────────────────\n\n/**\n * Cluster() input — discriminated by which of `query` / `seed_doc_ids`\n * is present. Both-present is a runtime error (D-15a) returned as\n * `{ok:false, reason:\"both_seeds_and_query\"}`; we accept either at the\n * type level and validate at call time so callers can use a single\n * `ClusterOptions` variable.\n */\nexport type ClusterOptions =\n | {\n query: string;\n method: \"edge-community\";\n /**\n * Vault name to scope the `query` search against. CR-02: required\n * on multi-vault setups so the query path is deterministic; on\n * single-vault setups the dispatcher (server.ts) and the runtime\n * cluster() entry below default to the lone configured vault, so\n * single-vault callers can still omit it.\n *\n * Mirrors how `recall` and `search_sections` handle the same\n * constraint at the controller layer.\n */\n vault?: string;\n query_top_k?: number;\n force?: boolean;\n seed_doc_ids?: undefined;\n }\n | {\n seed_doc_ids: DocId[];\n method: \"edge-community\";\n force?: boolean;\n query?: undefined;\n vault?: undefined;\n };\n\n/**\n * Per-cluster output shape (D-14). All fields are pure-deterministic.\n * NO LLM enrichment — that's Phase 5 brief layer's job.\n */\nexport interface Cluster {\n /** Smallest member DocId in this community (lexicographic). */\n cluster_id: DocId;\n /** Member count — `members.length`. */\n size: number;\n /** Hydrated citation packets, one per member. */\n members: CitationPacket[];\n /** Pure-deterministic summary fields. */\n summary: ClusterSummary;\n}\n\nexport interface ClusterSummary {\n /** Top 5 `properties.type` values by count; ties broken alpha. */\n top_types: Array<{ type: string; count: number }>;\n /** Top 3 member titles by intra-cluster degree; ties broken by DocId asc. */\n top_titles: Array<{ title: string; degree: number }>;\n /** Intra-cluster edges ÷ (size choose 2). Zero when `size ≤ 1`. */\n edge_density: number;\n}\n\n/**\n * cluster() return — discriminated union. Hard-cap and mutual-exclusion\n * errors return `{ok:false, ...}`; success returns `{ok:true, clusters,\n * node_count}` with clusters sorted by `cluster_id` ascending.\n */\nexport type ClusterResult =\n | {\n ok: false;\n reason: \"node_count_exceeded\";\n node_count: number;\n threshold: 5000;\n hint: string;\n }\n | { ok: false; reason: \"both_seeds_and_query\"; hint: string }\n | {\n ok: false;\n reason: \"vault_required\";\n hint: string;\n configured_vaults: string[];\n }\n | { ok: true; clusters: Cluster[]; node_count: number };\n\n/**\n * Dependencies injected at call time. Mirrors `ExpandDeps` shape\n * (Plan 04-03) so production wiring and unit tests share one contract.\n *\n * `hybridSearch` is injected as a thin callback rather than imported\n * directly to avoid the `src/search/ → src/graph/cluster.ts →\n * src/search/hybrid.ts` circular dependency. The MCP tool dispatcher in\n * `src/server.ts` binds the real `hybridSearch` at call time.\n */\nexport interface ClusterDeps {\n manager: VaultManager;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n hybridSearch: (vault: Vault, query: string, limit: number) => Promise;\n}\n\n// ─── constants (D-13) ───────────────────────────────────────────────────────\n\n/** Hard-cap on node count (D-13). `force: true` overrides. */\nconst NODE_CAP = 5000;\n/** Louvain seed string. Bump version when changing the determinism contract. */\nconst LOUVAIN_SEED = \"vault-memory-cluster-v1\";\n\n// ─── public entry point ─────────────────────────────────────────────────────\n\n/**\n * Cluster the union of seeds + their 1-hop neighborhood via Louvain\n * community detection. See file header for the full contract.\n */\nexport async function cluster(deps: ClusterDeps, opts: ClusterOptions): Promise {\n // ── D-15a mutual exclusion ────────────────────────────────────────────\n if (opts.query !== undefined && opts.seed_doc_ids !== undefined) {\n return {\n ok: false,\n reason: \"both_seeds_and_query\",\n hint: \"Pass exactly one of `query` or `seed_doc_ids`; not both.\",\n };\n }\n if (opts.query === undefined && opts.seed_doc_ids === undefined) {\n return {\n ok: false,\n reason: \"both_seeds_and_query\",\n hint: \"Pass exactly one of `query` or `seed_doc_ids`.\",\n };\n }\n\n // ── Resolve seeds (D-15a) ─────────────────────────────────────────────\n //\n // `query` path: search_hybrid → take top-K doc_ids → use as expand seeds.\n // `seed_doc_ids` path: use provided DocIds directly.\n let seedDocIds: DocId[] = [];\n let vault: Vault | null = null;\n let vaultName: string | null = null;\n let scheme: string | null = null;\n\n if (opts.query !== undefined) {\n // CR-02: resolve the working vault EXPLICITLY. The query path\n // historically scoped to `deps.manager.list()[0]` which silently\n // restricted multi-vault setups to whichever vault sorted first in\n // VaultManager insertion order — non-deterministic across users and\n // silently incomplete. We now require `opts.vault` on multi-vault\n // setups; single-vault setups still accept omission (the lone vault\n // is the only well-defined target).\n //\n // This mirrors how `recall` and `search_sections` enforce the same\n // constraint at the controller layer (server.ts).\n const limit = opts.query_top_k ?? 50;\n const allVaults = deps.manager.list();\n if (allVaults.length === 0) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n let workingVault: Vault | null = null;\n if (opts.vault !== undefined) {\n // Caller specified a vault — resolve via manager.require(), which\n // throws on unknown name. We translate that into a structured\n // {ok:false, reason:\"vault_required\"} (with the caller-supplied\n // name surfaced in `hint`) so the MCP boundary keeps a consistent\n // error envelope; unknown-vault is a caller mistake, not a\n // crash-worthy condition.\n try {\n workingVault = deps.manager.require(opts.vault);\n } catch {\n return {\n ok: false,\n reason: \"vault_required\",\n hint: `Unknown vault: \"${opts.vault}\". Pass one of the configured vault names.`,\n configured_vaults: allVaults.map((v) => v.config.name),\n };\n }\n } else if (allVaults.length === 1) {\n // Single configured vault — omission is fine, scope to that vault.\n workingVault = allVaults[0] ?? null;\n } else {\n // Multi-vault setup without an explicit `vault` filter — reject.\n // This is the CR-02 fix: silent first-vault-wins is replaced with\n // a clear error.\n return {\n ok: false,\n reason: \"vault_required\",\n hint: \"cluster() with `query` requires an explicit `vault:` parameter when multiple vaults are configured.\",\n configured_vaults: allVaults.map((v) => v.config.name),\n };\n }\n if (!workingVault) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n const hits = await deps.hybridSearch(workingVault, opts.query, limit);\n const ids: DocId[] = [];\n for (const h of hits) {\n if (h.doc_id !== undefined) ids.push(h.doc_id);\n }\n seedDocIds = ids;\n } else {\n seedDocIds = (opts.seed_doc_ids ?? []) as DocId[];\n }\n\n // Trivial empty case — no seeds → no clusters.\n if (seedDocIds.length === 0) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n\n // ── 1-hop expansion (D-15a \"induced 1-hop neighborhood\") ──────────────\n //\n // The `_memory` opacity rule is inherited from expand() — we do NOT\n // re-filter here. expand() returns CitationPacketWithVia[] (already\n // filtered for opacity + superseded). For cluster() we only need the\n // doc_ids; we re-hydrate properties/titles separately below to keep\n // the per-cluster member shape consistent for both the seed path AND\n // the query path.\n const expansion = await expand(\n {\n manager: deps.manager,\n sourceConnectorFor: deps.sourceConnectorFor,\n },\n { seed_doc_ids: seedDocIds, hops: 1, direction: \"both\" },\n );\n\n // Union: seeds ∪ 1-hop expansion (deduplicated, sorted).\n const allDocIdsSet = new Set();\n for (const s of seedDocIds) allDocIdsSet.add(s);\n for (const d of expansion.documents) allDocIdsSet.add(d.doc_id);\n const sortedDocIds = Array.from(allDocIdsSet).sort() as DocId[];\n\n // Resolve the working vault from the first seed (or the first\n // expansion doc). All DocIds inside a single cluster() invocation are\n // assumed to share a vault (the typed-edge BFS is per-vault per Plan\n // 04-03); cross-vault clustering is out of scope for v2.0.0.\n if (vault === null) {\n const firstId = sortedDocIds[0];\n if (firstId === undefined) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n try {\n const parsed = parseDocId(firstId);\n const dec = decomposeDocId(parsed);\n vault = deps.manager.require(dec.authority);\n vaultName = dec.authority;\n scheme = dec.scheme;\n } catch {\n // Malformed DocId or unknown vault — return empty success rather\n // than crash. The expand() call above would have already returned\n // its own warnings for unknown seeds.\n return { ok: true, clusters: [], node_count: 0 };\n }\n }\n\n // Map DocIds ↔ noteIds for the SQL edge lookup. Skip DocIds that do\n // not resolve to a known note row (defensive — expand() may have\n // returned a doc whose note row was deleted between BFS and our\n // re-resolution).\n const docIdToNoteId = new Map();\n const noteIdToDocId = new Map();\n for (const docId of sortedDocIds) {\n try {\n const parsed = parseDocId(docId);\n const dec = decomposeDocId(parsed);\n if (dec.authority !== vaultName) continue; // skip cross-vault\n const note = vault.db.notes.getByPath(dec.resource);\n if (!note) continue;\n docIdToNoteId.set(docId, note.id);\n noteIdToDocId.set(note.id, docId);\n } catch {\n continue;\n }\n }\n\n // The actual node set is the docIds we successfully resolved.\n const resolvedDocIds = Array.from(docIdToNoteId.keys()).sort() as DocId[];\n\n // ── D-13 hard cap ─────────────────────────────────────────────────────\n if (resolvedDocIds.length > NODE_CAP && !opts.force) {\n return {\n ok: false,\n reason: \"node_count_exceeded\",\n node_count: resolvedDocIds.length,\n threshold: NODE_CAP,\n hint: \"pass force:true to compute\",\n };\n }\n\n // Empty / single node — nothing to cluster.\n if (resolvedDocIds.length === 0) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n if (resolvedDocIds.length === 1) {\n const singleId = resolvedDocIds[0];\n if (singleId === undefined) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n // Hydrate the lone member into a single-element cluster.\n const cp = await hydratePacket(deps, scheme!, vaultName!, singleId, vault);\n if (cp === null) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n return {\n ok: true,\n node_count: 1,\n clusters: [\n {\n cluster_id: singleId,\n size: 1,\n members: [cp],\n summary: { top_types: [], top_titles: [], edge_density: 0 },\n },\n ],\n };\n }\n\n // ── Build graphology graph (D-12 step 1–3) ────────────────────────────\n //\n // Sorted DocId insertion is the FIRST determinism gate. The graph is\n // undirected (Louvain operates on undirected graphs) and `multi: false`\n // — parallel edges are collapsed to one. Self-loops are skipped.\n const g = new Graph({ type: \"undirected\", multi: false });\n for (const docId of resolvedDocIds) g.addNode(docId);\n\n const sortedNoteIds = resolvedDocIds.map((d) => docIdToNoteId.get(d)!);\n const edges = vault.db.edges.getAllForNodes(sortedNoteIds);\n for (const e of edges) {\n const srcDocId = noteIdToDocId.get(e.sourceDoc);\n const tgtDocId = noteIdToDocId.get(e.targetDoc);\n if (!srcDocId || !tgtDocId) continue;\n if (srcDocId === tgtDocId) continue; // skip self-loops defensively\n // Normalize endpoint order so (a,b) and (b,a) collapse to one.\n const a = srcDocId < tgtDocId ? srcDocId : tgtDocId;\n const b = srcDocId < tgtDocId ? tgtDocId : srcDocId;\n if (g.hasEdge(a, b)) continue;\n g.addEdge(a, b, { weight: 1 });\n }\n\n // ── Louvain with seeded RNG (D-12 step 4) ─────────────────────────────\n //\n // `randomWalk: true` keeps the algorithm's documented behavior; `rng`\n // overrides the library's internal `Math.random` use. See Pitfall 1.\n const rng = seedrandom(LOUVAIN_SEED);\n const detailed = louvain.detailed(g, {\n rng,\n randomWalk: true,\n });\n\n // `detailed.communities` maps nodeId (DocId) → community index.\n const communities = detailed.communities as Record;\n\n // ── Group nodes by community → compute cluster_id + summary ──────────\n const byCommunity = new Map();\n for (const [nodeId, communityIdx] of Object.entries(communities)) {\n const docId = nodeId as DocId;\n const arr = byCommunity.get(communityIdx);\n if (arr === undefined) byCommunity.set(communityIdx, [docId]);\n else arr.push(docId);\n }\n\n const clusters: Cluster[] = [];\n for (const [, memberDocIds] of byCommunity) {\n const sortedMembers = [...memberDocIds].sort() as DocId[];\n const firstMember = sortedMembers[0];\n if (firstMember === undefined) continue;\n const clusterId = firstMember;\n\n // Hydrate each member into a CitationPacket. Drop members that fail\n // to hydrate (note row deleted between BFS and now).\n const members: CitationPacket[] = [];\n for (const docId of sortedMembers) {\n const cp = await hydratePacket(deps, scheme!, vaultName!, docId, vault);\n if (cp !== null) members.push(cp);\n }\n if (members.length === 0) continue;\n\n const summary = computeSummary(members, sortedMembers, g);\n clusters.push({\n cluster_id: clusterId,\n size: members.length,\n members,\n summary,\n });\n }\n\n // ── D-12 step 5: sort clusters by cluster_id ascending ────────────────\n clusters.sort((a, b) => (a.cluster_id < b.cluster_id ? -1 : a.cluster_id > b.cluster_id ? 1 : 0));\n\n return { ok: true, clusters, node_count: resolvedDocIds.length };\n}\n\n// ─── helpers ────────────────────────────────────────────────────────────────\n\n/**\n * Hydrate a single DocId into a CitationPacket via the adapter seam.\n * Returns `null` when the source connector is unavailable or the read\n * fails — callers drop the member silently (same defensive posture as\n * expand()).\n */\nasync function hydratePacket(\n deps: ClusterDeps,\n scheme: string,\n vaultName: string,\n docId: DocId,\n _vault: Vault,\n): Promise {\n const source = (() => {\n try {\n return deps.sourceConnectorFor(vaultName);\n } catch {\n return null;\n }\n })();\n if (!source) return null;\n const canonicalDocId = formatDocId(scheme, vaultName, decomposeDocId(parseDocId(docId)).resource);\n let doc: Document;\n try {\n doc = await source.readDocument(canonicalDocId);\n } catch {\n return null;\n }\n return toCitationPacket(doc, displayUrlFor(canonicalDocId, source));\n}\n\n/**\n * D-14 summary computation — pure-deterministic, no LLM.\n *\n * - `top_types`: histogram over `members[*].properties.type`, sorted\n * by count desc; ties broken alphabetically; capped at 5.\n * - `top_titles`: per-member intra-cluster degree (count of edges in\n * `g` to OTHER cluster members); sorted by degree desc; ties broken\n * by DocId ascending; capped at 3.\n * - `edge_density`: |intra-cluster edges| / C(size, 2); 0 when\n * `size ≤ 1`.\n */\nfunction computeSummary(\n members: CitationPacket[],\n sortedDocIds: DocId[],\n g: Graph,\n): ClusterSummary {\n const size = members.length;\n\n // top_types histogram.\n const typeCounts = new Map();\n for (const m of members) {\n const t = m.properties.type;\n if (typeof t !== \"string\") continue;\n typeCounts.set(t, (typeCounts.get(t) ?? 0) + 1);\n }\n const topTypes = Array.from(typeCounts.entries())\n .sort((a, b) => {\n if (a[1] !== b[1]) return b[1] - a[1]; // count desc\n return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0; // alpha asc\n })\n .slice(0, 5)\n .map(([type, count]) => ({ type, count }));\n\n // Intra-cluster degree per member. A node's degree within the cluster\n // is the count of its graphology neighbors that are ALSO in the\n // sortedDocIds set.\n const memberSet = new Set(sortedDocIds);\n const degreeByDocId = new Map();\n for (const docId of sortedDocIds) {\n if (!g.hasNode(docId)) {\n degreeByDocId.set(docId, 0);\n continue;\n }\n let d = 0;\n for (const neighbor of g.neighbors(docId)) {\n if (memberSet.has(neighbor)) d += 1;\n }\n degreeByDocId.set(docId, d);\n }\n\n // Member → title + degree. Sort desc by degree, ties by DocId asc.\n const titleEntries = members.map((m) => ({\n doc_id: m.doc_id,\n title: m.title,\n degree: degreeByDocId.get(m.doc_id) ?? 0,\n }));\n titleEntries.sort((a, b) => {\n if (a.degree !== b.degree) return b.degree - a.degree;\n return a.doc_id < b.doc_id ? -1 : a.doc_id > b.doc_id ? 1 : 0;\n });\n const topTitles = titleEntries.slice(0, 3).map(({ title, degree }) => ({ title, degree }));\n\n // edge_density.\n let edgeDensity = 0;\n if (size >= 2) {\n let intraEdgeCount = 0;\n // Count unique edges where both endpoints are in the cluster. We\n // iterate the cluster's nodes and count each (a,b) once by requiring\n // a < b in DocId order.\n for (const docId of sortedDocIds) {\n if (!g.hasNode(docId)) continue;\n for (const neighbor of g.neighbors(docId)) {\n if (!memberSet.has(neighbor)) continue;\n if (docId < neighbor) intraEdgeCount += 1;\n }\n }\n const possible = (size * (size - 1)) / 2;\n edgeDensity = possible > 0 ? intraEdgeCount / possible : 0;\n }\n\n return { top_types: topTypes, top_titles: topTitles, edge_density: edgeDensity };\n}\n","export { listBacklinks, listForwardLinks, findBrokenLinks } from \"./graph.js\";\nexport type { BacklinkResult, ForwardLinkResult, BrokenLinkResult, EdgeType } from \"./graph.js\";\n\n// ── Phase 4 / 04-03 / GRA-01: typed-edge BFS retrieval (`expand`) ──\nexport { expand, isShorterPath } from \"./expand.js\";\nexport type {\n ExpandOptions,\n ExpandDirection,\n ExpandDeps,\n ExpansionResult,\n ViaTrace,\n CitationPacketWithVia,\n} from \"./expand.js\";\n\n// ── Phase 4 / 04-05 / GRA-02: Louvain community detection (`cluster`) ──\nexport { cluster } from \"./cluster.js\";\nexport type {\n Cluster,\n ClusterDeps,\n ClusterOptions,\n ClusterResult,\n ClusterSummary,\n} from \"./cluster.js\";\n","/**\n * Hybrid search via Reciprocal Rank Fusion (RRF).\n *\n * Runs semantic (sqlite-vec L2 over embeddings) and BM25 (FTS5 over chunk text)\n * searches in parallel per vault, then merges their rankings using RRF — a\n * rank-only fusion technique that requires no score normalization between\n * methods.\n *\n * RRF formula (Cormack et al., 2009):\n * rrf_score(item) = Σ_R 1 / (k + rank_R(item))\n * where R ranges over input rankings and rank is 1-based; items missing from\n * a ranking contribute 0 from that ranking.\n *\n * Two-stage fan-out across vaults:\n * - Embed query once per distinct model name (vaults sharing a model share\n * the vector).\n * - Per vault, fire semantic + BM25 in parallel, RRF-merge their chunk-id\n * lists, hydrate hits, then global-sort across vaults and take topK.\n */\n\nimport type { OllamaClient } from \"../ollama/index.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { DocId, SearchHit, SourceHandle } from \"../types.js\";\nimport type { Reranker } from \"../rerank/index.js\";\nimport { formatDocId, parseSourceHandle } from \"../adapters/registry.js\";\nimport {\n expand,\n type CitationPacketWithVia,\n type ExpandDeps,\n type ExpandDirection,\n} from \"../graph/index.js\";\nimport type { EdgeType } from \"../db/queries/edges.js\";\n\nexport interface HybridSearchOptions {\n query: string;\n /** Pre-computed embedding model name. Used for two purposes:\n * 1) look up the active model_id in each vault's DB\n * 2) ensure the query is embedded with the same model the index used */\n embeddingModel: string;\n ollama: OllamaClient;\n vaults: readonly Vault[];\n topK?: number;\n /** RRF constant. Standard: 60. Higher = less emphasis on top ranks. */\n rrfK?: number;\n /** Whether to include the per-method scores in the breakdown. Default true. */\n includeBreakdown?: boolean;\n /**\n * Optional cross-encoder reranker. When provided, hybridSearch fans out\n * `topK × rerankFanOut` candidates from the RRF stage, runs the reranker\n * on those, then resorts by rerank score and returns the new topK.\n *\n * On reranker failure (throw), the un-reranked RRF order is returned.\n */\n reranker?: Reranker;\n /** Candidate pool size as a multiple of topK. Default 5.\n *\n * Sizing rationale: BGE-M3 cosine distances on prose vaults form tight\n * plateaus (all top-N within ~0.02 score). The reranker needs a wide\n * enough pool to include semantically-on-target chunks that the\n * embedding ranks just below the plateau crest. At topK=10, a fanOut\n * of 5 produces a 50-chunk pool — empirically enough to catch chunks\n * the bi-encoder placed in rank 30-50 due to plateau noise.\n *\n * Limitations: a wider pool cannot rescue chunks the bi-encoder ranks\n * beyond the pool. Cross-lingual queries against a model with weak\n * recall on the target language (e.g. BGE-M3 on EN→DE for some terms)\n * can place the relevant chunk past rank 150. The fix there is a model\n * switch, not a larger pool — pool growth costs reranker inference\n * linearly while the marginal recall gain plateaus.\n *\n * Diagnostic: when the highest rerank score across the pool stays\n * below ~0.1, that is a signal that the desired chunk was never in\n * the pool. See `vault-memory-eval-v3-results.md` for the BGE-M3\n * cross-lingual case study. */\n rerankFanOut?: number;\n // ── Phase 3 / 03-05 (D-07, D-08, ASM-07, ASM-08): post-RRF rescore + filter ──\n //\n // All four params are strictly optional with defaults that vanish\n // when unused. The v1-default path (none of these set) is\n // byte-identical to v1 by construction:\n // - recencyWeight=0 + authorityWeight=0 → rescore block short-circuits\n // - includeSuperseded=false + no superseded fixture → SQL filter is a no-op\n //\n /** Additive recency term: `recencyWeight × exp(-age_days / halfLifeDays)`.\n * Default 0 (term contributes nothing — v1 invariance). */\n recencyWeight?: number;\n /** Additive authority term: `authorityWeight × 1` for docs with\n * `frontmatter.authoritative === true`, `× 0` otherwise. Default 0. */\n authorityWeight?: number;\n /** Recency half-life (days). Default 30 (D-07). Exposed so tests can\n * set short half-lives for deterministic age math. */\n halfLifeDays?: number;\n /** When false (default), exclude chunks whose note is `status: superseded`\n * at SQL level via the FTS JOIN + vec0 post-filter (03-05 M4). */\n includeSuperseded?: boolean;\n /** Clock injection seam — defaults to `Date.now`. Mirrors the recall\n * controller's idiom (`src/memory/tools/recall.ts:~205`). */\n clock?: () => number;\n /**\n * Phase 3 / 03-05 (ASM-06): display-URL resolver seam.\n *\n * `hybridSearch` is L0 substrate and is not allowed to mint adapter\n * URL strings (ADR-002 §I-5b — `obsidian://` literals live only in // vault-memory:claude-ok\n * the source adapter or registry). Bootstrap supplies a closure that\n * delegates to the registered `SourceConnector.formatDisplayUrl` for\n * the relevant vault; tests can omit it (no `display_url` populated).\n */\n displayUrlFor?: (vaultName: string, notePath: string) => string;\n // ── Phase 4 / 04-04 / GRA-03 (D-15, D-16): additive auto-expansion ──\n //\n // When `opts.expand` is undefined (the v1/v2 default), this guard\n // short-circuits entirely — zero new DB reads, zero new computation,\n // preserving v1-baseline byte-identical behavior. Expand runs AFTER\n // Phase 3 recency/authority rescore so that expansions attach to the\n // RESCORED top-K (D-16). Expand never participates in score\n // computation; top-K ranking is stable.\n //\n // `expand` and `expandDeps` MUST be supplied together. When only\n // one is set, the guard silently no-ops (defensive: callers wiring\n // this up incrementally see no behavior change until both are\n // provided). The dependency injection mirrors `displayUrlFor` — the\n // graph-traversal seam stays out of hybrid.ts's transitive imports\n // by surfacing it as an optional dep on the call site.\n /** Optional auto-expansion settings (D-15). When set, each hit\n * gains an additive `expansions: CitationPacketWithVia[]` field. */\n expand?: {\n hops: 1 | 2;\n direction?: ExpandDirection;\n edge_types?: EdgeType[];\n };\n /** Injected dependencies required by `expand()` — `manager` and\n * `sourceConnectorFor`. Required whenever `expand` is set; ignored\n * otherwise. */\n expandDeps?: ExpandDeps;\n // ── Alias-aware query expansion (ISSUE-aliases-not-in-fulltext-retrieval) ──\n //\n // A note's frontmatter alias (e.g. `JHE` → \"Jörg Herbers\") lives in\n // `note_aliases`, NOT in `chunks_fts`. So `search_hybrid(\"JHE\")` ranks\n // notes whose BODY contains the token \"JHE\" and never surfaces the\n // person note the alias points to. When the trimmed query EXACTLY\n // matches a known alias in a searched vault, inject/promote that\n // target note to the top of the result list. Surgical by design:\n // only fires on an exact alias match, never touches BM25/semantic\n // scoring, so non-alias queries are byte-identical to before (no FTS\n // re-baseline). Default ON; set false to restore pre-fix behavior.\n aliasExpansion?: boolean;\n}\n\nconst DEFAULT_TOP_K = 10;\nconst DEFAULT_RRF_K = 60;\n/** Minimum non-whitespace chars a chunk must contain to be sent to the\n * reranker. Defends against degenerate near-empty chunks that survived\n * the chunker (e.g. cross-version DBs) — they produce a constant rerank\n * score across the pool and dilute the top-k. */\nconst MIN_RERANK_TRIM_CHARS = 20;\n\n/**\n * Internal: ranked list of opaque item identifiers + the raw scores that\n * produced the ranking. Items must already be in best→worst order.\n */\nexport interface RankedList {\n /** Items in best→worst order (rank 1 = items[0]). */\n items: readonly T[];\n /** Raw score per item, parallel to `items`. Optional — only used for\n * breakdowns; RRF itself ignores it. */\n scores?: ReadonlyMap;\n}\n\nexport interface RrfMergeResult {\n item: T;\n rrf: number;\n /** 1-based rank in each input list, or undefined if the item was absent. */\n ranks: (number | undefined)[];\n}\n\n/**\n * Pure RRF merge over N ranked lists. Exported for unit testing.\n *\n * Result is sorted by rrf desc; ties broken by lower minimum rank.\n */\nexport function rrfMerge(\n rankings: ReadonlyArray>,\n k: number = DEFAULT_RRF_K,\n): RrfMergeResult[] {\n const scores = new Map();\n\n rankings.forEach((list, listIdx) => {\n list.items.forEach((item, i) => {\n const rank = i + 1;\n const contribution = 1 / (k + rank);\n const existing = scores.get(item);\n if (existing) {\n existing.rrf += contribution;\n existing.ranks[listIdx] = rank;\n } else {\n const ranks: (number | undefined)[] = new Array(rankings.length).fill(undefined);\n ranks[listIdx] = rank;\n scores.set(item, { rrf: contribution, ranks });\n }\n });\n });\n\n const out: RrfMergeResult[] = [];\n for (const [item, v] of scores) {\n out.push({ item, rrf: v.rrf, ranks: v.ranks });\n }\n out.sort((a, b) => {\n if (b.rrf !== a.rrf) return b.rrf - a.rrf;\n return minDefined(a.ranks) - minDefined(b.ranks);\n });\n return out;\n}\n\nfunction minDefined(xs: (number | undefined)[]): number {\n let m = Number.POSITIVE_INFINITY;\n for (const x of xs) {\n if (x !== undefined && x < m) m = x;\n }\n return m;\n}\n\ninterface PerVaultHit {\n vaultName: string;\n chunkId: number;\n rrf: number;\n semanticScore?: number;\n textScore?: number;\n /** Set when a reranker re-scored this candidate. */\n rerankScore?: number;\n}\n\nexport async function hybridSearch(opts: HybridSearchOptions): Promise {\n const topK = opts.topK ?? DEFAULT_TOP_K;\n const rrfK = opts.rrfK ?? DEFAULT_RRF_K;\n const includeBreakdown = opts.includeBreakdown ?? true;\n const query = opts.query.trim();\n\n if (topK <= 0 || query.length === 0 || opts.vaults.length === 0) {\n return [];\n }\n\n // Per-run query-embedding cache, keyed by model name. Multiple vaults\n // sharing the same embedding model only pay one Ollama round-trip.\n const embedCache = new Map>();\n const getQueryVector = (model: string): Promise => {\n const cached = embedCache.get(model);\n if (cached) return cached;\n const p = (async (): Promise => {\n try {\n const res = await opts.ollama.embed({ model, texts: [query] });\n const v = res.vectors[0];\n return v ?? null;\n } catch {\n return null;\n }\n })();\n embedCache.set(model, p);\n return p;\n };\n\n const rerankFanOut = Math.max(1, opts.rerankFanOut ?? 5);\n // When reranking, we need a wider per-vault pool so the global candidate\n // set is large enough for the cross-encoder to re-order meaningfully.\n const perVaultTopN = opts.reranker ? topK * rerankFanOut : topK;\n\n // 03-05 M4: pass `excludeSuperseded` down to the candidate-list SQL.\n // The flag is read inside `searchOneVault` to pick the JOIN-and-filter\n // FTS statement and to post-filter the vec0 ANN result list via the\n // notes-status partial index. Filter happens at SQL level, not in JS.\n const excludeSuperseded = (opts.includeSuperseded ?? false) === false;\n const perVault = await Promise.all(\n opts.vaults.map((vault) =>\n searchOneVault(\n vault,\n query,\n opts.embeddingModel,\n rrfK,\n perVaultTopN,\n getQueryVector,\n excludeSuperseded,\n ),\n ),\n );\n\n // Global merge: each vault already returned its top-N RRF hits. We\n // re-sort by RRF score across vaults and take the candidate pool.\n const flat: PerVaultHit[] = perVault.flat();\n flat.sort((a, b) => b.rrf - a.rrf);\n\n // ── Phase 3 / 03-05 (D-07, ASM-07, ASM-11): post-RRF additive rescore ──\n //\n // Inserted BEFORE the reranker (the cross-encoder, when active, runs\n // on the rescored pool — rescore shapes the candidate-pool that the\n // reranker sees). When both weights are zero (v1 default), the guard\n // short-circuits entirely and the rescore loop does zero work and\n // zero DB reads — preserving v1 perf exactly.\n //\n // Math (per D-07):\n // final = rrf + recencyWeight × exp(-age_days / halfLifeDays)\n // + authorityWeight × (authoritative ? 1 : 0)\n //\n // Hydration here only fires when rescore weights are non-zero; the\n // `notes.mtime` + `notes.frontmatter` reads are cheap (`getById` is\n // a PK lookup) and only happen for the top-N candidates already in\n // `flat`, never for the full candidate pool. The v1 invariance test\n // (`hybrid.rescore.test.ts`) pins this — same DB-read count as v1.\n const recencyWeight = opts.recencyWeight ?? 0;\n const authorityWeight = opts.authorityWeight ?? 0;\n if (recencyWeight !== 0 || authorityWeight !== 0) {\n const clock = opts.clock ?? Date.now;\n const now = clock();\n const halfLifeMs = (opts.halfLifeDays ?? 30) * 24 * 60 * 60 * 1000;\n const vaultByNameLocal = new Map();\n for (const v of opts.vaults) vaultByNameLocal.set(v.config.name, v);\n for (const h of flat) {\n const vault = vaultByNameLocal.get(h.vaultName);\n if (!vault) continue;\n const chunk = vault.db.chunks.getById(h.chunkId);\n if (!chunk) continue;\n const note = vault.db.notes.getById(chunk.note_id);\n if (!note) continue;\n const ageMs = Math.max(0, now - note.mtime);\n const recencyTerm = recencyWeight * Math.exp(-ageMs / halfLifeMs);\n let authoritative = false;\n if (authorityWeight !== 0 && note.frontmatter) {\n try {\n const fm = JSON.parse(note.frontmatter) as Record;\n authoritative = fm[\"authoritative\"] === true;\n } catch {\n // Malformed JSON in notes.frontmatter is treated as\n // non-authoritative — never throw out of the rescore loop.\n authoritative = false;\n }\n }\n const authorityTerm = authorityWeight * (authoritative ? 1.0 : 0);\n h.rrf += recencyTerm + authorityTerm;\n }\n flat.sort((a, b) => b.rrf - a.rrf);\n }\n\n // Optional cross-encoder rerank: re-score the global top-(topK*fanOut)\n // candidates with the reranker, then resort by rerank score. On any\n // failure, fall back silently to the RRF order.\n let winners: PerVaultHit[];\n if (opts.reranker && flat.length > 0) {\n const poolSize = Math.min(flat.length, topK * rerankFanOut);\n const pool = flat.slice(0, poolSize);\n const vaultByNameLocal = new Map();\n for (const v of opts.vaults) vaultByNameLocal.set(v.config.name, v);\n const texts: string[] = [];\n const indexed: { hit: PerVaultHit; text: string }[] = [];\n for (const h of pool) {\n const vault = vaultByNameLocal.get(h.vaultName);\n if (!vault) continue;\n const chunk = vault.db.chunks.getById(h.chunkId);\n if (!chunk) continue;\n // Skip near-empty chunks: cross-encoder produces a near-constant\n // score for them, which would dilute the pool. They keep their RRF\n // position (still appear in `flat`) but are not re-ranked.\n if (chunk.text.trim().length < MIN_RERANK_TRIM_CHARS) continue;\n indexed.push({ hit: h, text: chunk.text });\n texts.push(chunk.text);\n }\n if (indexed.length === 0) {\n // All pool candidates were filtered as too-short — fall back to RRF\n // order across `flat` rather than calling the reranker on nothing.\n winners = flat.slice(0, topK);\n } else\n try {\n const scores = await opts.reranker.score(query, texts);\n if (scores.length !== indexed.length) {\n throw new Error(`reranker returned ${scores.length} scores for ${indexed.length} chunks`);\n }\n for (let i = 0; i < indexed.length; i++) {\n const entry = indexed[i]!;\n const s = scores[i]!;\n entry.hit.rerankScore = s;\n }\n const reranked = indexed.map((e) => e.hit);\n reranked.sort((a, b) => {\n const ra = a.rerankScore ?? Number.NEGATIVE_INFINITY;\n const rb = b.rerankScore ?? Number.NEGATIVE_INFINITY;\n if (rb !== ra) return rb - ra;\n return b.rrf - a.rrf;\n });\n winners = reranked.slice(0, topK);\n } catch {\n // Reranker failed — fall back to RRF order. Clear any partial\n // rerankScore so the breakdown does not misrepresent the result.\n for (const h of pool) delete h.rerankScore;\n winners = flat.slice(0, topK);\n }\n } else {\n winners = flat.slice(0, topK);\n }\n\n // Hydrate to SearchHit. Look up via the originating vault's DB.\n const vaultByName = new Map();\n for (const v of opts.vaults) vaultByName.set(v.config.name, v);\n\n const hits: SearchHit[] = [];\n for (const h of winners) {\n const vault = vaultByName.get(h.vaultName);\n if (!vault) continue;\n const chunk = vault.db.chunks.getById(h.chunkId);\n if (!chunk) continue;\n const note = vault.db.notes.getById(chunk.note_id);\n if (!note) continue;\n const hit: SearchHit = {\n vault: vault.config.name,\n notePath: note.path,\n noteTitle: note.title,\n chunkText: chunk.text,\n chunkIdx: chunk.idx,\n headingPath: chunk.heading_path,\n // Surface the rerank score as the primary score when present —\n // it's the final order the caller sees.\n score: h.rerankScore ?? h.rrf,\n };\n if (includeBreakdown) {\n const breakdown: NonNullable = {\n rrf: h.rrf,\n };\n if (h.semanticScore !== undefined) breakdown.semantic = h.semanticScore;\n if (h.textScore !== undefined) breakdown.text = h.textScore;\n if (h.rerankScore !== undefined) breakdown.rerank = h.rerankScore;\n hit.scoreBreakdown = breakdown;\n }\n // ── Phase 3 / 03-05 (ASM-06, D-08): hydrate 9 optional citation fields ──\n //\n // All piggyback on the `note` + `chunk` rows already loaded above —\n // no extra DB read for mtime/hash/status/properties. `heading_path`\n // needs one extra indexed lookup via `SectionsQueries.findContainingChunk`\n // (O(log N) on the `sections_chunk_range` index from migration 010).\n //\n // Per D-08 these fields are additive: v1 callers see a SearchHit\n // whose JSON output is byte-identical to v1 because every new field\n // either populates with a value or is left undefined (and omitted\n // from the JSON serialization).\n let docId: DocId | undefined;\n let sourceHandle: SourceHandle | undefined;\n try {\n docId = formatDocId(\"obsidian-fs\", vault.config.name, note.path);\n sourceHandle = parseSourceHandle(`obsidian-fs://${vault.config.name}`);\n } catch {\n // Malformed vault-name / path → keep doc_id / source_handle\n // undefined rather than failing the whole hit.\n }\n if (docId !== undefined) hit.doc_id = docId;\n if (sourceHandle !== undefined) hit.source_handle = sourceHandle;\n hit.mtime = note.mtime;\n hit.hash = note.hash;\n // Display URL via the injected resolver — keeps the URL minting\n // confined to the source adapter (ADR-002 §I-5b). When the resolver\n // is omitted (test fixtures, smoke tests), display_url stays\n // undefined and is omitted from the JSON response.\n if (opts.displayUrlFor !== undefined) {\n try {\n hit.display_url = opts.displayUrlFor(vault.config.name, note.path);\n } catch {\n // Resolver throws (e.g. unknown vault) → leave display_url\n // unset rather than fail the whole hit.\n }\n }\n // Frontmatter parse — best-effort. Stored as JSON-stringified text\n // by the indexer (src/indexer/indexer.ts:176); malformed JSON\n // produces undefined `properties` rather than throwing.\n let props: Record | undefined;\n if (note.frontmatter) {\n try {\n props = JSON.parse(note.frontmatter) as Record;\n } catch {\n props = undefined;\n }\n }\n if (props !== undefined) hit.properties = props;\n // Read denormalized status directly from notes table — single column\n // lookup, no JSON parse. Falls through to props-derived only when the\n // denormalized column is null (legacy / pre-backfill rows).\n const status = vault.db.notes.getStatus(note.id);\n if (typeof status === \"string\") {\n hit.status = status;\n } else if (typeof props?.status === \"string\") {\n hit.status = props.status;\n }\n if (typeof props?.[\"superseded_by\"] === \"string\") {\n hit.superseded_by = props[\"superseded_by\"] as string;\n }\n // Section heading path — promote chunk → enclosing section when one\n // exists. The query is indexed (`sections_chunk_range`) and runs at\n // most once per result hit, so the cost stays bounded by topK.\n const section = vault.db.sections.findContainingChunk(note.id, chunk.id);\n if (section) {\n try {\n hit.heading_path = JSON.parse(section.heading_path) as string[];\n } catch {\n // Malformed JSON heading_path → leave heading_path undefined.\n }\n }\n hits.push(hit);\n }\n\n // Alias-aware query expansion runs BEFORE expand so an injected alias-target\n // hit is part of the seed set and receives `expansions` like any other hit\n // (ISSUE-aliases-not-in-fulltext-retrieval). No-op for non-alias queries.\n injectAliasHits(hits, opts, query, includeBreakdown);\n\n // ── Phase 4 / 04-04 / GRA-03 (D-15, D-16): post-rescore expand attachment ──\n //\n // When `opts.expand` is undefined (the v1/v2 default), this guard\n // short-circuits entirely — zero new DB reads, zero new computation,\n // preserving v1-baseline byte-identical behavior. Expand runs AFTER\n // Phase 3 recency/authority rescore and AFTER hit hydration so that\n // expansions attach to the RESCORED top-K (D-16). Expand never\n // participates in score computation; top-K ranking is stable.\n //\n // Deviation from plan § pseudocode (Rule 3 - Blocking):\n // the plan referenced `expand(vault, {...})` but the actual\n // `expand()` signature is `expand(deps, opts)` where `deps =\n // {manager, sourceConnectorFor}` (locked by Plan 04-03). We use the\n // real signature and inject deps via `opts.expandDeps`. A single\n // expand() call handles ALL hit seeds — `expand()` already groups\n // seeds by vault internally (see `src/graph/expand.ts` `byVault`\n // map), so cross-vault traversal is already prevented at the\n // expand() boundary (T-04-04-02 mitigation: per-vault BFS isolation\n // happens inside expand()).\n if (opts.expand && opts.expandDeps && hits.length > 0) {\n const seedDocIds: DocId[] = [];\n for (const hit of hits) {\n if (hit.doc_id !== undefined) seedDocIds.push(hit.doc_id);\n }\n if (seedDocIds.length > 0) {\n try {\n const expansionInput: Parameters[1] = {\n seed_doc_ids: seedDocIds,\n hops: opts.expand.hops,\n direction: opts.expand.direction ?? \"both\",\n };\n if (opts.expand.edge_types !== undefined) {\n expansionInput.edge_types = opts.expand.edge_types;\n }\n const result = await expand(opts.expandDeps, expansionInput);\n // Group by `via.seed_doc_id` (D-15). One pass; O(n) where n is\n // the total expansion-doc count.\n const bySeed = new Map();\n for (const doc of result.documents) {\n const seedId = doc.via.seed_doc_id;\n const arr = bySeed.get(seedId);\n if (arr) arr.push(doc);\n else bySeed.set(seedId, [doc]);\n }\n for (const hit of hits) {\n if (hit.doc_id !== undefined) {\n hit.expansions = bySeed.get(hit.doc_id) ?? [];\n }\n }\n } catch {\n // Expand failures are silent. The rest of the hybrid result\n // is intact; only the `expansions` field stays unset. This\n // matches the defensive posture of the reranker fallback\n // (lines 342–347 above).\n }\n }\n }\n\n return hits;\n}\n\n/**\n * Alias-aware query expansion (ISSUE-aliases-not-in-fulltext-retrieval).\n *\n * If the exact query string is a known alias in one of the searched vaults,\n * ensure that alias's target note is in the result set, at the top. A note's\n * frontmatter alias lives in `note_aliases`, NOT in `chunks_fts`, so an\n * exact-alias query (e.g. \"JHE\") otherwise never surfaces the target note.\n *\n * Mutates `hits` in place (unshift/promote). Runs BEFORE the expand block so an\n * injected alias hit participates in expand seeding and gains `expansions` like\n * any organically-retrieved hit (preserves the D-16 with/without-expand\n * invariant). Guard: only fires when aliasExpansion !== false AND the query\n * exactly matches an alias — non-alias queries do zero extra DB work.\n */\nfunction injectAliasHits(\n hits: SearchHit[],\n opts: HybridSearchOptions,\n query: string,\n includeBreakdown: boolean,\n): void {\n if ((opts.aliasExpansion ?? true) !== true) return;\n for (const vault of opts.vaults) {\n let resolved;\n try {\n resolved = vault.db.aliases.resolve(query);\n } catch {\n continue; // alias table missing / malformed → skip this vault\n }\n if (!resolved) continue;\n const note = vault.db.notes.getById(resolved.note_id);\n if (!note) continue;\n // Already surfaced organically? Promote it to the front instead of\n // duplicating, so the alias target is the top hit either way.\n const existingIdx = hits.findIndex(\n (h) => h.vault === vault.config.name && h.notePath === note.path,\n );\n if (existingIdx >= 0) {\n const [existing] = hits.splice(existingIdx, 1);\n if (existing) hits.unshift(existing);\n return;\n }\n // Build a hit from the note's first chunk (person/stub notes may have\n // exactly one). If the note has no chunks, synthesize a minimal hit\n // from the note row so the alias still resolves to something useful.\n const firstChunk = vault.db.chunks.getByNote(note.id)[0];\n const aliasHit: SearchHit = {\n vault: vault.config.name,\n notePath: note.path,\n noteTitle: note.title,\n chunkText: firstChunk?.text ?? note.title,\n chunkIdx: firstChunk?.idx ?? 0,\n headingPath: firstChunk?.heading_path ?? null,\n // Alias matches are exact metadata hits — rank above fuzzy results.\n score: 1,\n };\n if (includeBreakdown) {\n aliasHit.scoreBreakdown = { rrf: 1, alias: resolved.alias };\n }\n try {\n aliasHit.doc_id = formatDocId(\"obsidian-fs\", vault.config.name, note.path);\n aliasHit.source_handle = parseSourceHandle(`obsidian-fs://${vault.config.name}`);\n } catch {\n // keep doc_id/source_handle undefined on malformed name/path\n }\n aliasHit.mtime = note.mtime;\n aliasHit.hash = note.hash;\n if (opts.displayUrlFor !== undefined) {\n try {\n aliasHit.display_url = opts.displayUrlFor(vault.config.name, note.path);\n } catch {\n // leave display_url unset on resolver throw\n }\n }\n if (note.frontmatter) {\n try {\n aliasHit.properties = JSON.parse(note.frontmatter) as Record;\n } catch {\n // malformed frontmatter → no properties\n }\n }\n const status = vault.db.notes.getStatus(note.id);\n if (typeof status === \"string\") aliasHit.status = status;\n hits.unshift(aliasHit);\n // One exact-alias match is enough; the shortest-path winner already\n // won inside resolve(). Stop after the first vault that resolves it.\n return;\n }\n}\n\n/**\n * Search a single vault. Resolves semantic + BM25 in parallel, RRF-merges,\n * returns the vault's top-N candidates (we keep topK so the global merge\n * has enough to draw from).\n */\nasync function searchOneVault(\n vault: Vault,\n query: string,\n embeddingModelName: string,\n rrfK: number,\n topK: number,\n getQueryVector: (model: string) => Promise,\n /** 03-05 M4: when true, the FTS path uses the JOIN-and-filter\n * prepared statement against `notes.status`, and the vec0 ANN\n * result list is post-filtered via `getSupersededChunkIds`. When\n * false (the v1 default), both candidate paths are byte-identical\n * to v1. */\n excludeSuperseded = false,\n): Promise {\n const fanK = Math.max(topK * 3, topK);\n\n // Resolve the model to use for semantic search.\n //\n // Phase 7c follow-up (v0.7.2): the *active* model in the DB is the source\n // of truth — `switch_active_model` may have promoted a shadow model that\n // doesn't match the config's `default_embedding_model`. The config-named\n // model is only a fallback used when no active model has been registered\n // yet (fresh vault).\n const activeModel = vault.db.models.getActive();\n const queryModelName = activeModel?.name ?? embeddingModelName;\n const canRunSemantic = activeModel !== null;\n\n const semanticPromise: Promise<{\n chunkIds: number[];\n distances: Map;\n } | null> = canRunSemantic\n ? (async () => {\n const vec = await getQueryVector(queryModelName);\n if (!vec) return null;\n const hits = vault.db.embeddings.searchSemantic(activeModel.id, vec, fanK);\n const distances = new Map();\n const chunkIds: number[] = [];\n for (const h of hits) {\n chunkIds.push(h.chunkId);\n distances.set(h.chunkId, h.distance);\n }\n // 03-05 M4: post-filter vec0 KNN results via the notes_status\n // partial index. vec0 virtual tables don't compose with JOINs\n // the way FTS5 does, so the filter runs as a single follow-up\n // SQL with a parametric IN list. Still SQL-level — zero\n // frontmatter parses. v1 path (excludeSuperseded = false)\n // skips this entirely.\n if (excludeSuperseded && chunkIds.length > 0) {\n const supSet = vault.db.notes.getSupersededChunkIds(chunkIds);\n if (supSet.size > 0) {\n const filtered: number[] = [];\n for (const id of chunkIds) {\n if (!supSet.has(id)) filtered.push(id);\n else distances.delete(id);\n }\n return { chunkIds: filtered, distances };\n }\n }\n return { chunkIds, distances };\n })()\n : Promise.resolve(null);\n\n const bm25Promise: Promise<{\n chunkIds: number[];\n scores: Map;\n }> = Promise.resolve().then(() => {\n const hits = vault.db.fts.search(query, fanK, false, excludeSuperseded);\n const scores = new Map();\n const chunkIds: number[] = [];\n for (const h of hits) {\n chunkIds.push(h.chunkId);\n scores.set(h.chunkId, h.score);\n }\n return { chunkIds, scores };\n });\n\n const [semantic, bm25] = await Promise.all([semanticPromise, bm25Promise]);\n\n const rankings: RankedList[] = [];\n if (semantic && semantic.chunkIds.length > 0) {\n rankings.push({ items: semantic.chunkIds, scores: semantic.distances });\n }\n if (bm25.chunkIds.length > 0) {\n rankings.push({ items: bm25.chunkIds, scores: bm25.scores });\n }\n\n if (rankings.length === 0) return [];\n\n // Track which list is which for breakdown extraction below.\n const semanticListIdx = semantic && semantic.chunkIds.length > 0 ? 0 : -1;\n const bm25ListIdx = rankings.length === 2 ? 1 : semanticListIdx === -1 ? 0 : -1;\n\n const merged = rrfMerge(rankings, rrfK).slice(0, topK);\n\n return merged.map((m) => {\n const hit: PerVaultHit = {\n vaultName: vault.config.name,\n chunkId: m.item,\n rrf: m.rrf,\n };\n if (semanticListIdx !== -1 && m.ranks[semanticListIdx] !== undefined) {\n const d = semantic!.distances.get(m.item);\n if (d !== undefined) hit.semanticScore = d;\n }\n if (bm25ListIdx !== -1 && m.ranks[bm25ListIdx] !== undefined) {\n const s = bm25.scores.get(m.item);\n if (s !== undefined) hit.textScore = s;\n }\n return hit;\n });\n}\n","/**\n * ContextFit CLI wrapper — the pinned subprocess contract (ADR-008).\n *\n * ContextFit (https://github.com/ContextFit/cf) is a Python, CPU-only,\n * token-native retrieval engine. vault-memory is Node/ESM, so we integrate\n * out-of-process by spawning the `contextfit` binary — no daemon, no shell.\n *\n * This module is the SOLE place that knows ContextFit's CLI flags and\n * `--json` output shape. The `parseQueryOutput` contract is asserted by\n * `cli.contract.test.ts` so an upstream change fails loudly rather than\n * silently mis-parsing.\n *\n * # Adapter-seam carve-out (ADR-002)\n * - `child_process` + raw path handling are ALLOWED inside this directory\n * (same class as `src/contracts/mcp-clients.ts` peer-MCP spawning). The\n * rest of the codebase reaches ContextFit only through `ContextFitBackend`.\n *\n * # CLI contract (contextfit 0.1.0, pinned)\n * contextfit --kb ingest --rebuild-index-after-ingest\n * contextfit --kb query \"\" --top-k --method --json\n * contextfit --kb stats\n */\n\n// cross-spawn (not node:child_process) — its spawn wrapper handles the fd /\n// argument edge cases that make raw `spawn` throw `EBADF` when vault-memory\n// runs as an MCP **stdio server** (the SDK transport holds the parent's\n// stdio fds). This is the same library the MCP SDK itself spawns through.\nimport spawn from \"cross-spawn\";\n\n/** A single retrieved chunk from `contextfit query --json` → `chunks[]`. */\nexport interface ContextFitChunk {\n rank: number;\n chunk_id: number;\n score: number;\n level: number;\n parent_id: number | null;\n token_count: number;\n semantic_id?: number[];\n /** `metadata.source` is the ABSOLUTE filesystem path ContextFit ingested. */\n metadata: { source?: string } & Record;\n /** Decoded chunk text preview — used as the SearchHit chunkText. */\n preview: string;\n tokens?: number[];\n}\n\n/** Parsed shape of `contextfit query --json` (only the fields we consume). */\nexport interface ContextFitQueryResult {\n query: string;\n method: string;\n retrieved_chunks: number;\n chunks: ContextFitChunk[];\n}\n\nexport type ContextFitMethod = \"exact\" | \"bm25\" | \"sid\" | \"graph\" | \"hierarchy\" | \"hybrid\";\n\nexport interface ContextFitCliConfig {\n /** The `contextfit` executable (bare name on PATH, or absolute path). */\n command: string;\n /** Knowledge-base / index directory passed via `--kb`. Per-vault. */\n kbPath: string;\n /** Tokenizer (default cl100k_base). Passed via `--tokenizer`. */\n tokenizer?: string;\n /** Spawn timeout per call (ms). Default 120_000 for ingest, callers override. */\n timeoutMs?: number;\n}\n\nexport class ContextFitError extends Error {\n override readonly name = \"ContextFitError\";\n constructor(\n message: string,\n readonly code: \"ENOENT\" | \"NONZERO_EXIT\" | \"BAD_JSON\" | \"TIMEOUT\" = \"NONZERO_EXIT\",\n ) {\n super(message);\n }\n}\n\ninterface RunResult {\n stdout: string;\n stderr: string;\n}\n\n/**\n * Spawn `contextfit` with the given args (no shell). Resolves with stdout on\n * exit code 0; rejects with a typed ContextFitError otherwise. `--kb` and\n * `--tokenizer` are global flags and must precede the subcommand.\n */\nfunction runContextFit(\n cfg: ContextFitCliConfig,\n subcommandArgs: string[],\n timeoutMs: number,\n): Promise {\n const globalArgs = [\"--kb\", cfg.kbPath];\n if (cfg.tokenizer) globalArgs.push(\"--tokenizer\", cfg.tokenizer);\n const args = [...globalArgs, ...subcommandArgs];\n\n return new Promise((resolve, reject) => {\n // Pipe all three streams (via cross-spawn) and close stdin — contextfit\n // reads none. cross-spawn avoids the `spawn EBADF` the raw node spawn hits\n // under the MCP stdio server's fd state.\n let child;\n try {\n child = spawn(cfg.command, args, { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n } catch (err) {\n // `spawn` can throw SYNCHRONOUSLY (e.g. EBADF under heavy fd pressure\n // when many vault watchers are live). Surface a typed error; the caller\n // (runContextFitWithRetry) retries transient EBADF.\n const e = err as NodeJS.ErrnoException;\n reject(\n new ContextFitError(\n `contextfit spawn failed: ${e.message}`,\n e.code === \"ENOENT\" ? \"ENOENT\" : \"NONZERO_EXIT\",\n ),\n );\n return;\n }\n child.stdin?.end();\n let stdout = \"\";\n let stderr = \"\";\n let settled = false;\n\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n child.kill(\"SIGKILL\");\n reject(new ContextFitError(`contextfit timed out after ${timeoutMs}ms`, \"TIMEOUT\"));\n }, timeoutMs);\n\n child.stdout?.on(\"data\", (d: Buffer) => {\n stdout += d.toString();\n });\n child.stderr?.on(\"data\", (d: Buffer) => {\n stderr += d.toString();\n });\n child.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (err.code === \"ENOENT\") {\n reject(\n new ContextFitError(\n `contextfit not found (tried '${cfg.command}'). Install it with ` +\n `\\`pipx install contextfit\\` (or pip), or set the command path.`,\n \"ENOENT\",\n ),\n );\n } else {\n reject(new ContextFitError(`contextfit spawn failed: ${err.message}`));\n }\n });\n child.on(\"close\", (codeNum: number | null) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (codeNum === 0) {\n resolve({ stdout, stderr });\n } else {\n reject(\n new ContextFitError(\n `contextfit exited ${codeNum}: ${stderr.trim() || stdout.trim() || \"(no output)\"}`,\n \"NONZERO_EXIT\",\n ),\n );\n }\n });\n });\n}\n\n/**\n * Run `contextfit` with one retry on a transient EBADF. `uv_spawn` can fail\n * with EBADF when the process is under heavy file-descriptor pressure (e.g.\n * many chokidar vault watchers churning the fd table at the moment of spawn);\n * the condition is transient, so a short-delayed retry usually succeeds. Other\n * errors (ENOENT, non-zero exit, bad JSON) are NOT retried.\n */\nasync function runContextFitWithRetry(\n cfg: ContextFitCliConfig,\n subcommandArgs: string[],\n timeoutMs: number,\n): Promise {\n try {\n return await runContextFit(cfg, subcommandArgs, timeoutMs);\n } catch (err) {\n const isEbadf = err instanceof ContextFitError && /EBADF/.test(err.message);\n if (!isEbadf) throw err;\n await new Promise((r) => setTimeout(r, 50));\n return runContextFit(cfg, subcommandArgs, timeoutMs);\n }\n}\n\n/**\n * `contextfit ingest ` — (re)build the KB from a directory of files.\n * `--rebuild-index-after-ingest` ensures the BM25/SID indexes are queryable\n * immediately. Returns ContextFit's stdout (human-readable stats) for logging.\n */\nexport async function contextFitIngest(\n cfg: ContextFitCliConfig,\n source: string,\n opts: { chunkSize?: number; overlap?: number } = {},\n): Promise {\n const args = [\"ingest\", source, \"--rebuild-index-after-ingest\"];\n if (opts.chunkSize !== undefined) args.push(\"--chunk-size\", String(opts.chunkSize));\n if (opts.overlap !== undefined) args.push(\"--overlap\", String(opts.overlap));\n const { stdout } = await runContextFitWithRetry(cfg, args, cfg.timeoutMs ?? 600_000);\n return stdout;\n}\n\n/**\n * `contextfit query \"\" --json` — retrieve top-k chunks. Parses the\n * `chunks[]` array out of the JSON envelope. ContextFit prints a non-JSON\n * \"Loading LSH from disk...\" preamble to stdout before the JSON object, so we\n * slice from the first `{` to be robust.\n */\nexport async function contextFitQuery(\n cfg: ContextFitCliConfig,\n query: string,\n opts: { topK?: number; method?: ContextFitMethod } = {},\n): Promise {\n const args = [\"query\", query, \"--json\"];\n if (opts.topK !== undefined) args.push(\"--top-k\", String(opts.topK));\n if (opts.method !== undefined) args.push(\"--method\", opts.method);\n const { stdout } = await runContextFitWithRetry(cfg, args, cfg.timeoutMs ?? 30_000);\n return parseQueryOutput(stdout);\n}\n\n/**\n * Parse the JSON object out of `contextfit query --json` stdout. Tolerates a\n * non-JSON preamble (e.g. \"Loading LSH from disk...\") by slicing from the\n * first `{`. Throws ContextFitError(\"BAD_JSON\") on a malformed/empty result.\n * Exported for the contract test.\n */\nexport function parseQueryOutput(stdout: string): ContextFitQueryResult {\n const start = stdout.indexOf(\"{\");\n if (start === -1) {\n throw new ContextFitError(\n `contextfit query produced no JSON: ${stdout.slice(0, 200)}`,\n \"BAD_JSON\",\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(stdout.slice(start));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new ContextFitError(`contextfit query JSON parse failed: ${msg}`, \"BAD_JSON\");\n }\n const obj = parsed as Partial;\n if (!Array.isArray(obj.chunks)) {\n throw new ContextFitError(\n `contextfit query JSON missing 'chunks' array (got keys: ${Object.keys(obj ?? {}).join(\", \")})`,\n \"BAD_JSON\",\n );\n }\n return {\n query: typeof obj.query === \"string\" ? obj.query : \"\",\n method: typeof obj.method === \"string\" ? obj.method : \"hybrid\",\n retrieved_chunks:\n typeof obj.retrieved_chunks === \"number\" ? obj.retrieved_chunks : obj.chunks.length,\n chunks: obj.chunks as ContextFitChunk[],\n };\n}\n\n/** Probe: is the `contextfit` binary runnable? Returns version string or null. */\nexport async function contextFitProbe(cfg: Pick): Promise {\n try {\n await new Promise((resolve, reject) => {\n // All-piped (not \"ignore\") to avoid `spawn EBADF` under the MCP stdio\n // server's fd state — same rationale as runContextFit above.\n const child = spawn(cfg.command, [\"--help\"], { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n child.stdin?.end();\n child.on(\"error\", reject);\n child.on(\"close\", (c: number | null) =>\n c === 0 ? resolve() : reject(new Error(`exit ${c}`)),\n );\n });\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * ContextFitBackend — the CPU-only, token-native retrieval engine (ADR-008).\n *\n * A second retrieval engine selectable per vault via `backend = \"contextfit\"`.\n * Unlike the default Ollama+sqlite-vec path it needs NO embedding model and NO\n * GPU: ContextFit (a Python CLI) ingests the vault's markdown into a per-vault\n * knowledge-base directory and answers queries over it (BM25 + Semantic-IDs).\n *\n * Process model: out-of-process via the `contextfit` CLI (see `./cli.ts`). No\n * daemon — cold-start per call is fine at ContextFit's ~10 ms query latency.\n *\n * This adapter is engine-specific glue; it normalizes ContextFit results into\n * the canonical `SearchHit` so all downstream assembly/citation code stays\n * engine-agnostic.\n */\n\nimport { homedir } from \"node:os\";\nimport { rm } from \"node:fs/promises\";\nimport { join, relative, isAbsolute } from \"node:path\";\nimport type { VaultConfig, SearchHit } from \"../../../types.js\";\nimport {\n contextFitIngest,\n contextFitQuery,\n contextFitProbe,\n type ContextFitCliConfig,\n type ContextFitChunk,\n} from \"./cli.js\";\n\nconst DEFAULT_COMMAND = \"contextfit\";\n\n/** Per-vault ContextFit KB directory: ~/.vault-memory/contextfit//. */\nexport function contextFitKbDir(vaultName: string): string {\n return join(homedir(), \".vault-memory\", \"contextfit\", vaultName);\n}\n\n/** Build the CLI config for a vault from its VaultConfig. */\nexport function cliConfigForVault(vault: VaultConfig): ContextFitCliConfig {\n const cfg: ContextFitCliConfig = {\n command: vault.contextfit?.command ?? DEFAULT_COMMAND,\n kbPath: contextFitKbDir(vault.name),\n };\n if (vault.contextfit?.tokenizer) cfg.tokenizer = vault.contextfit.tokenizer;\n return cfg;\n}\n\nexport interface ContextFitIndexResult {\n status: \"completed\" | \"failed\";\n /** Human-readable stats line from ContextFit's ingest output. */\n stats: string;\n durationMs: number;\n error?: string;\n}\n\n/**\n * Index a vault with ContextFit: spawn `contextfit ingest `. Full\n * (re)build — ContextFit owns its own incremental logic; we always pass the\n * vault root and `--rebuild-index-after-ingest` so the KB is immediately\n * queryable. Throws ContextFitError on spawn/exec failure (caller logs).\n */\nexport async function indexVaultWithContextFit(\n vault: VaultConfig,\n opts: { onProgress?: (msg: string) => void } = {},\n): Promise {\n const log = opts.onProgress ?? (() => {});\n const cfg = cliConfigForVault(vault);\n const start = Date.now();\n\n log(`ContextFit: ingesting ${vault.path} → ${cfg.kbPath}`);\n const available = await contextFitProbe({ command: cfg.command });\n if (!available) {\n return {\n status: \"failed\",\n stats: \"\",\n durationMs: Date.now() - start,\n error:\n `ContextFit CLI not runnable (tried '${cfg.command}'). Install with ` +\n `\\`pipx install contextfit\\` or set [[vaults]].contextfit.command.`,\n };\n }\n\n try {\n // ContextFit refuses to ingest into an existing KB (it finds the manifest\n // and exits non-zero, demanding --resume or a clean dir). Our index\n // semantics are always a FULL rebuild, so clear the KB dir first — this\n // makes re-index / live-reindex / write-refresh / catchup idempotent.\n await rm(cfg.kbPath, { recursive: true, force: true });\n const stats = await contextFitIngest(cfg, vault.path);\n log(stats.trim().split(\"\\n\").slice(-3).join(\" · \"));\n return { status: \"completed\", stats, durationMs: Date.now() - start };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { status: \"failed\", stats: \"\", durationMs: Date.now() - start, error: message };\n }\n}\n\n/**\n * Map a ContextFit `metadata.source` (absolute path ContextFit ingested) back\n * to a vault-relative POSIX path matching the `notes.path` convention. Returns\n * null when the source isn't under the vault root (defensive — skip the hit).\n */\nexport function sourceToNotePath(source: string | undefined, vaultPath: string): string | null {\n if (!source) return null;\n const rel = isAbsolute(source) ? relative(vaultPath, source) : source;\n if (rel.startsWith(\"..\")) return null; // outside the vault root\n return rel.split(/[\\\\/]/).join(\"/\");\n}\n\n/** Map one ContextFit chunk → SearchHit. Returns null for un-addressable hits. */\nfunction chunkToHit(chunk: ContextFitChunk, vault: VaultConfig): SearchHit | null {\n const notePath = sourceToNotePath(chunk.metadata?.source, vault.path);\n if (notePath === null) return null;\n // Derive a display title from the path basename (ContextFit doesn't return\n // a note title); downstream callers that need the real title re-read the note.\n const base = notePath.split(\"/\").pop() ?? notePath;\n const noteTitle = base.replace(/\\.md$/i, \"\");\n const hit: SearchHit = {\n vault: vault.name,\n notePath,\n noteTitle,\n chunkText: chunk.preview ?? \"\",\n chunkIdx: chunk.chunk_id,\n headingPath: null,\n score: chunk.score,\n scoreBreakdown: { contextfit: chunk.score },\n };\n return hit;\n}\n\n/**\n * Search a ContextFit-backed vault. Spawns `contextfit query`, maps the\n * returned chunks to SearchHit[] (vault-relative paths). Engine-agnostic\n * output — the caller treats these identically to Ollama-path hits.\n */\nexport async function searchVaultWithContextFit(\n vault: VaultConfig,\n query: string,\n opts: { topK?: number } = {},\n): Promise {\n const cfg = cliConfigForVault(vault);\n const method = vault.contextfit?.method ?? \"hybrid\";\n const result = await contextFitQuery(cfg, query, {\n topK: opts.topK ?? 10,\n method,\n });\n const hits: SearchHit[] = [];\n for (const chunk of result.chunks) {\n const hit = chunkToHit(chunk, vault);\n if (hit) hits.push(hit);\n }\n return hits;\n}\n","/**\n * searchVaults — engine-dispatching search front-end (ADR-008).\n *\n * vault-memory supports two retrieval engines selectable per vault:\n * - \"ollama\" (default): Ollama embeddings + sqlite-vec + FTS5 hybrid\n * (`hybridSearch`).\n * - \"contextfit\": CPU-only token-native engine via its CLI\n * (`searchVaultWithContextFit`).\n *\n * Callers used to invoke `hybridSearch` directly. `searchVaults` is a\n * drop-in wrapper with the same options that partitions the requested vaults\n * by their configured `backend`, runs each group through the right engine,\n * and merges the results into one `SearchHit[]` sorted by score (descending),\n * truncated to `topK`. Engine-mixing is fine because every engine returns the\n * canonical `SearchHit`; scores are per-engine and only used for intra-result\n * ordering, never cross-engine semantics.\n *\n * When every vault is \"ollama\" (the common case), this delegates straight to\n * `hybridSearch` with zero behavior change.\n */\n\nimport type { SearchHit } from \"../types.js\";\nimport { hybridSearch, type HybridSearchOptions } from \"./hybrid.js\";\n\nfunction isContextFit(vault: HybridSearchOptions[\"vaults\"][number]): boolean {\n return vault.config.backend === \"contextfit\";\n}\n\nexport async function searchVaults(opts: HybridSearchOptions): Promise {\n const topK = opts.topK ?? 10;\n const cfVaults = opts.vaults.filter(isContextFit);\n const ollamaVaults = opts.vaults.filter((v) => !isContextFit(v));\n\n // Fast path: no ContextFit vaults → behave exactly like hybridSearch.\n if (cfVaults.length === 0) {\n return hybridSearch(opts);\n }\n\n const { searchVaultWithContextFit } = await import(\"../adapters/retrieval/contextfit/index.js\");\n\n // Run ContextFit vaults (each via its CLI) and the Ollama group concurrently.\n // A failing ContextFit vault (CLI missing, bad KB) must not take down the\n // whole search — log to stderr and yield no hits for that vault.\n const cfPromise = Promise.all(\n cfVaults.map((v) =>\n searchVaultWithContextFit(v.config, opts.query, { topK }).catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[search:${v.config.name}] ContextFit query failed: ${msg}`);\n return [] as SearchHit[];\n }),\n ),\n );\n const ollamaPromise =\n ollamaVaults.length > 0\n ? hybridSearch({ ...opts, vaults: ollamaVaults })\n : Promise.resolve([] as SearchHit[]);\n\n const [cfResultsNested, ollamaResults] = await Promise.all([cfPromise, ollamaPromise]);\n const cfResults = cfResultsNested.flat();\n\n // Merge + sort by score desc, then truncate to topK. Scores are per-engine;\n // this ordering is best-effort across a heterogeneous result set (rare —\n // most setups are single-engine). Within a single engine the order is exact.\n const merged = [...ollamaResults, ...cfResults];\n merged.sort((a, b) => b.score - a.score);\n return merged.slice(0, topK);\n}\n","/**\n * Minimal glob-pattern matcher for vault-relative paths.\n *\n * Supports the Obsidian/gitignore-style subset we need:\n * - `*` matches zero or more chars except `/`\n * - `**` matches zero or more chars including `/`\n * - `?` matches exactly one char except `/`\n * - Other characters match literally (regex-special chars are escaped)\n *\n * No brace expansion, no character classes, no negation. If we ever need\n * those we'll add picomatch — but every additional dependency in this\n * package costs us npm-install pain (better-sqlite3 already gave us\n * trouble), so we keep it tiny.\n */\n\n/** Convert a glob into an anchored regex source. Cached per pattern. */\nconst cache = new Map();\n\nfunction compile(pattern: string): RegExp {\n const cached = cache.get(pattern);\n if (cached) return cached;\n\n let re = \"\";\n for (let i = 0; i < pattern.length; i++) {\n const ch = pattern[i]!;\n if (ch === \"*\") {\n if (pattern[i + 1] === \"*\") {\n re += \".*\";\n i++;\n } else {\n re += \"[^/]*\";\n }\n } else if (ch === \"?\") {\n re += \"[^/]\";\n } else if (/[.+^${}()|[\\]\\\\]/.test(ch)) {\n re += \"\\\\\" + ch;\n } else {\n re += ch;\n }\n }\n const compiled = new RegExp(`^${re}$`);\n cache.set(pattern, compiled);\n return compiled;\n}\n\n/**\n * True iff `path` matches any of the given glob patterns. Empty pattern\n * list returns false (no exclusion).\n */\nexport function matchesAnyGlob(path: string, patterns: readonly string[]): boolean {\n for (const p of patterns) {\n if (compile(p).test(path)) return true;\n }\n return false;\n}\n","export { hybridSearch, rrfMerge } from \"./hybrid.js\";\nexport type { HybridSearchOptions, RankedList, RrfMergeResult } from \"./hybrid.js\";\n// ADR-008: engine-dispatching search front-end. Drop-in for hybridSearch;\n// routes contextfit-backed vaults to the CPU-only engine, ollama vaults to\n// the embeddings+sqlite-vec hybrid, and merges.\nexport { searchVaults } from \"./dispatch.js\";\nexport { matchesAnyGlob } from \"./glob.js\";\n","/**\n * Cross-encoder reranker (Phase 7d, optional).\n *\n * `Reranker.score(query, chunks)` returns a relevance score per chunk —\n * higher = more relevant. Scores are NOT necessarily normalized between\n * runs; only their relative order matters within a single call.\n *\n * # Strategy\n *\n * Ollama hosts cross-encoder rerankers like `bge-reranker-v2-m3` (BAAI,\n * MIT-licensed, multilingual), but the server only exposes the embedding\n * layer — not the classification head that produces the actual relevance\n * logit. The community workaround\n * (https://github.com/overcuriousity/ollama-utils/tree/main/plugins/reranking-endpoint)\n * is:\n *\n * 1. Feed the model `\"Query: {q}\\n\\nDocument: {d}\\n\\nRelevance:\"` as\n * a single text input via /api/embed.\n * 2. Compute the L2 norm of the returned embedding vector.\n * 3. For bge-reranker models, *lower magnitude = more relevant*, so we\n * negate the magnitude to produce a \"higher = better\" score.\n *\n * This is a proxy, not the true classification logit, but it correlates\n * well enough in practice to be useful as a rerank signal on top of\n * hybrid retrieval. When/if Ollama exposes the classification head, or\n * when we ship an ONNX runtime, the `OllamaReranker` class can be\n * swapped out behind the same interface without API churn.\n *\n * # Failure semantics\n *\n * Reranking is strictly best-effort: any error from Ollama (network,\n * model not loaded, parse failure) causes `score()` to throw, and\n * callers MUST treat the failure as \"no rerank available\" and fall back\n * to the upstream ranking. See `hybridSearch` for the integration.\n */\n\nimport type { OllamaClient } from \"../ollama/index.js\";\n\nexport interface Reranker {\n /**\n * Score each chunk against the query. Returns one score per chunk,\n * in the same order as the input. Higher = more relevant.\n *\n * Throws on transport / parse failure. Callers should catch and fall\n * back to the un-reranked order.\n */\n score(query: string, chunks: readonly string[]): Promise;\n}\n\nexport interface OllamaRerankerOptions {\n ollama: OllamaClient;\n model: string;\n}\n\n/**\n * Reranker backed by Ollama's /api/embed endpoint.\n *\n * Expects a cross-encoder model like `qllama/bge-reranker-v2-m3`. See\n * file header for the magnitude-as-proxy caveat.\n */\nexport class OllamaReranker implements Reranker {\n private readonly ollama: OllamaClient;\n private readonly model: string;\n\n constructor(opts: OllamaRerankerOptions) {\n this.ollama = opts.ollama;\n this.model = opts.model;\n }\n\n async score(query: string, chunks: readonly string[]): Promise {\n if (chunks.length === 0) return [];\n const inputs = chunks.map((c) => formatPair(query, c));\n const res = await this.ollama.embed({ model: this.model, texts: inputs });\n if (res.vectors.length !== chunks.length) {\n throw new Error(`Reranker: expected ${chunks.length} vectors, got ${res.vectors.length}`);\n }\n // For bge-reranker: lower L2 magnitude ⇒ more relevant. Negate so\n // \"higher score = more relevant\" matches the Reranker contract.\n return res.vectors.map((v) => -l2Norm(v));\n }\n}\n\n/**\n * Format a query/document pair as a single string. Matches the prompt\n * shape used by overcuriousity/ollama-utils — keep stable so scores are\n * comparable across runs.\n */\nexport function formatPair(query: string, doc: string): string {\n return `Query: ${query}\\n\\nDocument: ${doc}\\n\\nRelevance:`;\n}\n\nfunction l2Norm(v: readonly number[]): number {\n let sum = 0;\n for (const x of v) sum += x * x;\n return Math.sqrt(sum);\n}\n","/**\n * ONNX-runtime cross-encoder reranker (Phase 8).\n *\n * Replaces the L2-norm proxy from Phase 7d (`OllamaReranker`) with a real\n * cross-encoder forward pass over BAAI/bge-reranker-v2-m3 (ONNX-quantized).\n *\n * # Model files\n *\n * Expects two files in `modelDir`:\n * - `model_quantized.onnx` (≈570 MB, INT8)\n * - `tokenizer.json` (≈17 MB)\n *\n * Both are downloaded by `scripts/download-reranker.sh` (or the\n * `vault-memory download-reranker` CLI subcommand) from\n * https://huggingface.co/onnx-community/bge-reranker-v2-m3-ONNX.\n *\n * # Output semantics\n *\n * The model outputs a single logit per (query, document) pair. We apply\n * sigmoid to map to [0, 1]; higher = more relevant — matching the\n * `Reranker` contract directly (no negation hack).\n *\n * # Lazy loading\n *\n * `onnxruntime-node` and `@huggingface/tokenizers` are imported lazily on\n * the first `score()` call so users who never enable `rerank:true` don't\n * pay the load cost (and so test runs without the model files pass).\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Reranker } from \"./reranker.js\";\n\nexport interface OnnxRerankerOptions {\n /** Directory containing `model_quantized.onnx` + `tokenizer.json`. */\n modelDir: string;\n /** Max sequence length per (query, doc) pair. Default 512. */\n maxLength?: number;\n}\n\ninterface LoadedSession {\n // Kept as `unknown` to avoid pulling the type at module load.\n session: any;\n tokenizer: any;\n ort: any;\n}\n\nexport class OnnxReranker implements Reranker {\n private readonly modelDir: string;\n private readonly maxLength: number;\n private loaded: LoadedSession | null = null;\n private loading: Promise | null = null;\n\n constructor(opts: OnnxRerankerOptions) {\n this.modelDir = opts.modelDir;\n this.maxLength = opts.maxLength ?? 512;\n }\n\n /**\n * Score each chunk against the query. Returns sigmoid(logit) per pair.\n * Throws if the model files are missing (with a copy-pasteable curl\n * command in the error message).\n */\n async score(query: string, chunks: readonly string[]): Promise {\n if (chunks.length === 0) return [];\n const { session, tokenizer, ort } = await this.load();\n\n // Tokenize each (query, chunk) pair separately so we can build the\n // batch with per-row truncation to maxLength, then pad to the longest\n // row in the batch (saves work over padding everything to 512).\n const encoded = chunks.map((chunk) => {\n const enc = tokenizer.encode(query, { text_pair: chunk });\n let ids: number[] = enc.ids;\n let mask: number[] = enc.attention_mask;\n if (ids.length > this.maxLength) {\n ids = ids.slice(0, this.maxLength);\n mask = mask.slice(0, this.maxLength);\n }\n return { ids, mask };\n });\n\n const seqLen = Math.max(...encoded.map((e) => e.ids.length));\n const batch = encoded.length;\n const inputIds = new BigInt64Array(batch * seqLen);\n const attentionMask = new BigInt64Array(batch * seqLen);\n for (let i = 0; i < batch; i++) {\n const row = encoded[i]!;\n for (let j = 0; j < row.ids.length; j++) {\n inputIds[i * seqLen + j] = BigInt(row.ids[j]!);\n attentionMask[i * seqLen + j] = BigInt(row.mask[j]!);\n }\n // Remaining positions stay 0n (pad token id 0 for XLM-R / bge-m3).\n }\n\n const feeds: Record = {\n input_ids: new ort.Tensor(\"int64\", inputIds, [batch, seqLen]),\n attention_mask: new ort.Tensor(\"int64\", attentionMask, [batch, seqLen]),\n };\n const out = await session.run(feeds);\n // The model exports its output as `logits`. Fall back to first key\n // for robustness against minor export variants.\n const logitsTensor = out.logits ?? out[Object.keys(out)[0] as keyof typeof out];\n const data = logitsTensor.data as Float32Array;\n // logits shape: [batch, 1] — one score per pair. Sigmoid → [0, 1].\n const scores: number[] = new Array(batch);\n for (let i = 0; i < batch; i++) {\n scores[i] = sigmoid(data[i]!);\n }\n return scores;\n }\n\n private async load(): Promise {\n if (this.loaded) return this.loaded;\n if (this.loading) return this.loading;\n this.loading = (async () => {\n const modelPath = join(this.modelDir, \"model_quantized.onnx\");\n const tokenizerPath = join(this.modelDir, \"tokenizer.json\");\n if (!existsSync(modelPath)) {\n throw new Error(\n `OnnxReranker: model file not found at ${modelPath}. ` +\n `Run: curl -L https://huggingface.co/onnx-community/bge-reranker-v2-m3-ONNX/resolve/main/onnx/model_quantized.onnx -o ${modelPath}`,\n );\n }\n if (!existsSync(tokenizerPath)) {\n throw new Error(\n `OnnxReranker: tokenizer file not found at ${tokenizerPath}. ` +\n `Run: curl -L https://huggingface.co/onnx-community/bge-reranker-v2-m3-ONNX/resolve/main/tokenizer.json -o ${tokenizerPath}`,\n );\n }\n const [ort, tokMod, tokJson] = await Promise.all([\n import(\"onnxruntime-node\"),\n import(\"@huggingface/tokenizers\"),\n readFile(tokenizerPath, \"utf-8\"),\n ]);\n // @huggingface/tokenizers expects two args: the tokenizer.json object\n // *and* a separate config object with special-token strings (bos/eos/\n // pad/unk). HF distributions ship that as tokenizer_config.json, but\n // for bge-reranker-v2-m3 only tokenizer.json is published. We derive\n // the config from added_tokens — known stable: XLM-RoBERTa schema\n // (=0, =1, =2, =3).\n const tokenizerJson = JSON.parse(tokJson);\n const config = deriveTokenizerConfig(tokenizerJson);\n const tokenizer = new (tokMod as any).Tokenizer(tokenizerJson, config);\n const session = await (ort as any).InferenceSession.create(modelPath);\n const loaded: LoadedSession = { session, tokenizer, ort };\n this.loaded = loaded;\n return loaded;\n })();\n return this.loading;\n }\n}\n\nfunction sigmoid(x: number): number {\n return 1 / (1 + Math.exp(-x));\n}\n\n/**\n * Derive the tokenizer config (special-token strings) from added_tokens.\n * @huggingface/tokenizers needs this as a second constructor arg; HF\n * usually ships it as a separate tokenizer_config.json, but bge-reranker-\n * v2-m3 only publishes tokenizer.json — so we reconstruct from added_tokens.\n *\n * Falls back to XLM-RoBERTa defaults (the reranker's base architecture).\n */\nfunction deriveTokenizerConfig(tokenizerJson: any): Record {\n const added: Array<{ id: number; content: string; special?: boolean }> =\n tokenizerJson.added_tokens ?? [];\n const byContent = new Map(added.map((t) => [t.content, t]));\n const pick = (...candidates: string[]): string => {\n for (const c of candidates) if (byContent.has(c)) return c;\n return candidates[0]!;\n };\n return {\n bos_token: pick(\"\"),\n eos_token: pick(\"\"),\n pad_token: pick(\"\"),\n unk_token: pick(\"\"),\n };\n}\n","export { OllamaReranker, formatPair } from \"./reranker.js\";\nexport type { Reranker, OllamaRerankerOptions } from \"./reranker.js\";\nexport { OnnxReranker } from \"./onnx-reranker.js\";\nexport type { OnnxRerankerOptions } from \"./onnx-reranker.js\";\n","/**\n * MCP response helpers — `ok` / `errorResponse` / `errorResponseJson`.\n *\n * Extracted verbatim from `src/server.ts` (the bootstrap god-file). These\n * shape the `{ content: [{ type: \"text\", text }] }` / `isError` envelopes\n * the MCP SDK expects. Zero closure dependencies, zero runtime imports.\n *\n * # Adapter-seam discipline\n *\n * Pure helpers. No node:path / node:fs / chokidar / gray-matter imports.\n */\n\nexport function ok(data: object): { content: Array<{ type: \"text\"; text: string }> } {\n return {\n content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }],\n };\n}\n\nexport function errorResponse(message: string): {\n isError: true;\n content: Array<{ type: \"text\"; text: string }>;\n} {\n return {\n isError: true,\n content: [{ type: \"text\", text: message }],\n };\n}\n\n/**\n * Structured `isError: true` response — the JSON payload is stringified\n * into the single `text` content block. Used by Phase 3 assembly tools\n * for the `{error: \"doc_not_found\", doc_id}` contract (plan 03-02).\n * Distinct from `errorResponse` (free-text) so callers can pattern-match\n * `JSON.parse(content[0].text).error === \"doc_not_found\"`.\n */\nexport function errorResponseJson(payload: object): {\n isError: true;\n content: Array<{ type: \"text\"; text: string }>;\n} {\n return {\n isError: true,\n content: [{ type: \"text\", text: JSON.stringify(payload) }],\n };\n}\n","/**\n * Pure utility helpers extracted from `src/server.ts` (the bootstrap\n * god-file). None of these close over `serve()` state — they take their\n * inputs as explicit parameters.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. The string\n * `.split(\"/\")` operations in `decodeNoteId` / `defaultBasename` /\n * `normalizeFolderHint` are plain string manipulation, NOT `node:path`.\n * Display-URL routing delegates to the adapter registry seam\n * (`parseSourceHandle` / `formatDocId` / `SourceConnector.formatDisplayUrl`).\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\nimport type { VaultManager } from \"../vault/index.js\";\nimport type { AdapterRegistry } from \"../adapters/registry.js\";\nimport { formatDocId, parseSourceHandle } from \"../adapters/registry.js\";\n\nexport function countWords(content: string): number {\n if (content.length === 0) return 0;\n return content.split(/\\s+/).filter((s) => s.length > 0).length;\n}\n\n/**\n * Resolve which vaults a search should hit.\n *\n * Scope resolution (priority highest first):\n * 1. Explicit `vaultFilter` from the request → exactly those vaults.\n * 2. `activeVault` from VAULT_MEMORY_ACTIVE_VAULT env var → just that one.\n * 3. Neither set → all configured vaults (legacy behaviour).\n *\n * Indexing-status filter:\n * - Vaults whose audit log shows an unfinished index run are excluded\n * ONLY when the caller didn't ask for them explicitly. Idea: implicit\n * cross-vault search shouldn't surface chunks whose embeddings aren't\n * ready yet. Explicit single-vault requests pass through unchanged\n * (caller takes responsibility, gets a `note` field in the response).\n *\n * Returns the resolved targets plus the names of any skipped vaults, so the\n * caller can include a transparency note in the response.\n */\nexport function resolveVaultTargets(\n manager: VaultManager,\n vaultFilter: string[] | undefined,\n activeVault: string | undefined,\n): { targets: ReturnType; skipped: string[] } {\n // Explicit request → honour even if mid-index (caller's choice).\n if (vaultFilter) {\n return { targets: vaultFilter.map((n) => manager.require(n)), skipped: [] };\n }\n const candidates = activeVault ? [manager.require(activeVault)] : manager.list();\n const targets: typeof candidates = [];\n const skipped: string[] = [];\n for (const v of candidates) {\n if (v.db.audit.isIndexing()) {\n skipped.push(v.config.name);\n } else {\n targets.push(v);\n }\n }\n return { targets, skipped };\n}\n\nexport function encodeNoteId(vault: string, path: string): string {\n return `${vault}:${path}`;\n}\n\nexport function decodeNoteId(id: string): { vault: string; path: string } {\n const idx = id.indexOf(\":\");\n if (idx <= 0 || idx === id.length - 1) {\n throw new Error(`Invalid id: ${id}. Expected format :.`);\n }\n return { vault: id.slice(0, idx), path: id.slice(idx + 1) };\n}\n\n/**\n * D-01 (plan 01-04 task 06): the v1 `obsidianUrl(vault, path)` helper was\n * deleted. Display URLs now flow through `SourceConnector.formatDisplayUrl`\n * — the obsidian-fs source mints the same deep-link URL string byte-for-byte\n * (the Obsidian `open` URL scheme; verified same `encodeURIComponent`-per-\n * segment encoding scheme; documented in\n * `.planning/phases/01-…/01-04-SUMMARY.md` §\"URL encoding parity\"). Future\n * adapters (notion-api etc.) can publish their own display URLs without\n * changing core code.\n *\n * The internal helper `displayUrl(registry, vault, path)` below is the\n * routing shim; it resolves the source and delegates.\n */\nexport function displayUrl(registry: AdapterRegistry, vaultName: string, notePath: string): string {\n const source = registry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`));\n const docId = formatDocId(\"obsidian-fs\", vaultName, notePath);\n // `formatDisplayUrl` is optional on the SourceConnector interface; for\n // future adapters that don't expose one, fall back to the raw doc_uri.\n return source.formatDisplayUrl?.(docId) ?? `obsidian-fs://${vaultName}/${notePath}`;\n}\n\nexport function truncateSnippet(text: string, max: number): string {\n const collapsed = text.replace(/\\s+/g, \" \").trim();\n if (collapsed.length <= max) return collapsed;\n return collapsed.slice(0, max - 1).trimEnd() + \"…\";\n}\n\n/**\n * Aggregate the top-N tags across all notes in a vault.\n *\n * Tags can live in two places in our schema: a top-level `tags` array in\n * frontmatter (Obsidian convention) or inline `#tag` hashtags in the body.\n * For v0.9.0 we read the frontmatter form only — it is what the user\n * curates explicitly and what other tools (Datacore queries, dataview)\n * already aggregate. Inline hashtags would need a separate pass through\n * note bodies and are deferred until users ask for it.\n *\n * Implementation uses SQLite's json_each over the stored frontmatter blob.\n * `frontmatter` is TEXT containing a JSON object; we look up the `tags` key\n * and iterate. Notes without frontmatter or without a tags array are\n * silently skipped.\n */\nexport function aggregateTopTags(\n db: BetterSqlite3.Database,\n limit: number,\n): Array<{ tag: string; count: number }> {\n // Real vaults accumulate frontmatter drift: `tags` may be an array,\n // a single string, a nested object, or missing entirely. SQLite's\n // json_each() throws on non-array/object inputs and aborts the whole\n // query — so we pre-filter to rows where `tags` is actually an array.\n // The CROSS JOIN with the JSON table then only sees well-formed inputs.\n const rows = db\n .prepare<[number], { tag: string; count: number }>(\n `\n SELECT je.value AS tag, COUNT(*) AS count\n FROM notes\n JOIN json_each(json_extract(notes.frontmatter, '$.tags')) AS je\n WHERE notes.frontmatter IS NOT NULL\n AND json_type(notes.frontmatter, '$.tags') = 'array'\n AND typeof(je.value) = 'text'\n GROUP BY je.value\n ORDER BY count DESC, tag ASC\n LIMIT ?\n `,\n )\n .all(limit);\n return rows;\n}\n\n/**\n * Aggregate the top-N most common frontmatter keys across all notes.\n * Surfaces the user's schema conventions to an agent on first connect.\n */\nexport function aggregateTopFrontmatterKeys(\n db: BetterSqlite3.Database,\n limit: number,\n): Array<{ key: string; count: number }> {\n // Same filter rationale as aggregateTopTags: a single note with a\n // non-object frontmatter blob (rare, but happens after manual edits)\n // would abort the whole aggregate.\n const rows = db\n .prepare<[number], { key: string; count: number }>(\n `\n SELECT je.key AS key, COUNT(*) AS count\n FROM notes\n JOIN json_each(notes.frontmatter) AS je\n WHERE notes.frontmatter IS NOT NULL\n AND json_type(notes.frontmatter) = 'object'\n GROUP BY je.key\n ORDER BY count DESC, key ASC\n LIMIT ?\n `,\n )\n .all(limit);\n return rows;\n}\n\nexport function safeParseFrontmatter(s: string): Record | null {\n try {\n const parsed = JSON.parse(s) as unknown;\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function defaultBasename(path: string): string {\n const base = path.split(\"/\").pop() ?? path;\n return base.replace(/\\.md$/i, \"\");\n}\n\nexport function normalizeFolderHint(hint: string | undefined): string {\n if (!hint) return \"\";\n let h = hint.trim();\n // Strip leading slash; ensure trailing slash if non-empty.\n if (h.startsWith(\"/\")) h = h.slice(1);\n if (h.length > 0 && !h.endsWith(\"/\")) h = `${h}/`;\n return h;\n}\n","/**\n * Frontmatter query — minimal DSL against the JSON-stored frontmatter column.\n *\n * Uses SQLite's JSON1 extension (built into modern SQLite, no extra load needed).\n *\n * Predicate shapes:\n * { field: scalar } → field equals scalar\n * { field: { $in: [a, b, ...] } } → field is one of\n * { field: { $exists: true } } → field is present (not null/missing)\n * { field: { $exists: false } } → field absent or null\n * { field: { $contains: scalar } } → for arrays: array contains scalar\n *\n * Multiple top-level keys are AND-combined.\n *\n * Field path uses dot-notation: \"class\" or \"tags\" or \"links.0\".\n * Internally we map to JSON1 `json_extract(frontmatter, '$.path')`.\n */\n\nimport type { Vault } from \"../vault/index.js\";\nimport type { NoteRow } from \"../types.js\";\n\ntype Scalar = string | number | boolean | null;\n\nexport type Predicate = Scalar | { $in: Scalar[] } | { $exists: boolean } | { $contains: Scalar };\n\nexport interface QueryFrontmatterInput {\n where: Record;\n limit?: number;\n}\n\ninterface CompiledClause {\n sql: string;\n params: unknown[];\n}\n\nconst MAX_FIELD_DEPTH = 5;\n\nfunction isPlainObject(v: unknown): v is Record {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction buildJsonPath(field: string): string {\n // Reject anything that smells like SQL injection. We only allow\n // [A-Za-z0-9_.] plus simple array indexes.\n if (!/^[A-Za-z_][A-Za-z0-9_.]*$/.test(field)) {\n throw new Error(\n `Invalid frontmatter field: \"${field}\". Use dot.notation with alphanumeric segments.`,\n );\n }\n const parts = field.split(\".\");\n if (parts.length > MAX_FIELD_DEPTH) {\n throw new Error(`Field depth exceeds maximum (${MAX_FIELD_DEPTH}): ${field}`);\n }\n return \"$.\" + parts.map((p) => (/^\\d+$/.test(p) ? `[${p}]` : p)).join(\".\");\n}\n\nfunction compileClause(field: string, predicate: Predicate): CompiledClause {\n const jsonPath = buildJsonPath(field);\n const extract = `json_extract(frontmatter, '${jsonPath}')`;\n\n // Scalar equality\n if (predicate === null || typeof predicate !== \"object\") {\n if (predicate === null) {\n return { sql: `${extract} IS NULL`, params: [] };\n }\n return { sql: `${extract} = ?`, params: [predicate] };\n }\n\n if (isPlainObject(predicate)) {\n if (\"$in\" in predicate) {\n const values = predicate.$in;\n if (!Array.isArray(values) || values.length === 0) {\n // empty $in → never matches\n return { sql: \"0\", params: [] };\n }\n const placeholders = values.map(() => \"?\").join(\", \");\n return { sql: `${extract} IN (${placeholders})`, params: [...values] };\n }\n if (\"$exists\" in predicate) {\n return {\n sql: predicate.$exists ? `${extract} IS NOT NULL` : `${extract} IS NULL`,\n params: [],\n };\n }\n if (\"$contains\" in predicate) {\n // Array contains. Use json_each to scan.\n // Note: this requires the field to actually be a JSON array; if not\n // it just yields no rows.\n return {\n sql: `EXISTS (SELECT 1 FROM json_each(frontmatter, '${jsonPath}') WHERE value = ?)`,\n params: [predicate.$contains],\n };\n }\n }\n\n throw new Error(`Unsupported predicate for field \"${field}\": ${JSON.stringify(predicate)}`);\n}\n\nexport function queryFrontmatter(vault: Vault, input: QueryFrontmatterInput): NoteRow[] {\n const clauses: CompiledClause[] = [];\n for (const [field, predicate] of Object.entries(input.where)) {\n clauses.push(compileClause(field, predicate));\n }\n\n if (clauses.length === 0) {\n // No filters → return everything (capped). Caller probably wants `listAll`.\n return vault.db.notes.listAll(input.limit ?? 100);\n }\n\n const where = clauses.map((c) => `(${c.sql})`).join(\" AND \");\n const params = clauses.flatMap((c) => c.params);\n const limit = Math.min(Math.max(1, input.limit ?? 100), 1000);\n\n const stmt = vault.db.handle.prepare(\n `SELECT * FROM notes WHERE frontmatter IS NOT NULL AND ${where} ORDER BY mtime DESC LIMIT ${limit}`,\n );\n\n return stmt.all(...params);\n}\n","import { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\n\nexport interface ScanOptions {\n excludeGlobs?: string[];\n}\n\nconst DEFAULT_EXCLUDES = [\".obsidian/**\", \".trash/**\", \"node_modules/**\"];\n\n/**\n * Recursively walk `rootPath` and return absolute paths of all `.md` files.\n * Symlinks are NOT followed (loop-safe).\n *\n * Excludes are matched against the *relative* posix path of each file/dir.\n * A directory is pruned if its relative path matches any exclude glob.\n */\nexport async function scanVault(rootPath: string, options?: ScanOptions): Promise {\n const root = path.resolve(rootPath);\n const excludes = options?.excludeGlobs ?? DEFAULT_EXCLUDES;\n const matchers = excludes.map(compileGlob);\n\n const results: string[] = [];\n await walk(root, root, matchers, results);\n results.sort();\n return results;\n}\n\n/**\n * Phase 6 / Plan 06-04 — Enumerate task-contract YAML files directly under\n * `_contracts/` (non-recursive; CONTRACT_PATH_REGEX = `^_contracts/[^/]+\\.yaml$`).\n *\n * Kept separate from `scanVault` because the indexer assumes `scanVault`\n * yields only `.md` files; broadening that would cascade through the\n * markdown parser. Contract YAML enumeration is a separate seam exposed\n * to `ObsidianFsSource.listDocuments` so the contract loader's boot scan\n * sees real YAML on disk.\n *\n * Returns absolute paths sorted lexicographically.\n */\nexport async function scanContractFiles(rootPath: string): Promise {\n const root = path.resolve(rootPath);\n const contractsDir = path.join(root, \"_contracts\");\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(contractsDir, { withFileTypes: true });\n } catch {\n return [];\n }\n const results: string[] = [];\n for (const entry of entries) {\n // Pitfall F3 — non-recursive; `_contracts/memory/*.yaml` belongs to\n // the Phase 2 MemoryContract loader, not the task-contract loader.\n if (!entry.isFile()) continue;\n if (!entry.name.toLowerCase().endsWith(\".yaml\")) continue;\n results.push(path.join(contractsDir, entry.name));\n }\n results.sort();\n return results;\n}\n\nasync function walk(root: string, dir: string, matchers: RegExp[], out: string[]): Promise {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const abs = path.join(dir, entry.name);\n const rel = toPosix(path.relative(root, abs));\n if (rel.length === 0) continue;\n if (isExcluded(rel, matchers)) continue;\n\n if (entry.isSymbolicLink()) {\n // Skip symlinks entirely to avoid loops.\n continue;\n }\n if (entry.isDirectory()) {\n await walk(root, abs, matchers, out);\n } else if (entry.isFile() && abs.toLowerCase().endsWith(\".md\")) {\n out.push(abs);\n }\n }\n}\n\nfunction isExcluded(relPath: string, matchers: RegExp[]): boolean {\n for (const re of matchers) {\n if (re.test(relPath)) return true;\n }\n return false;\n}\n\nfunction toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n\n/**\n * Compile a minimal glob to a RegExp.\n * Supports:\n * - `*` → any chars except `/`\n * - `**` → any chars including `/`\n * - `?` → single char except `/`\n * - everything else literal\n *\n * The pattern matches the whole relative path. To also match descendants of\n * a matched directory (Obsidian convention), if the pattern ends with `/**`,\n * we also match the bare directory prefix.\n */\nexport function compileGlob(glob: string): RegExp {\n // Match descendants too when pattern ends with `/**`.\n const trimmed = glob.replace(/^\\.\\//, \"\");\n const altDir = trimmed.endsWith(\"/**\") ? trimmed.slice(0, -3) : null;\n\n const toRe = (g: string): string => {\n let re = \"\";\n for (let i = 0; i < g.length; i++) {\n const c = g[i];\n if (c === undefined) continue;\n if (c === \"*\") {\n if (g[i + 1] === \"*\") {\n re += \".*\";\n i++;\n } else {\n re += \"[^/]*\";\n }\n } else if (c === \"?\") {\n re += \"[^/]\";\n } else if (/[.+^${}()|[\\]\\\\]/.test(c)) {\n re += \"\\\\\" + c;\n } else {\n re += c;\n }\n }\n return re;\n };\n\n const parts = [toRe(trimmed)];\n if (altDir !== null) parts.push(toRe(altDir));\n return new RegExp(\"^(?:\" + parts.join(\"|\") + \")$\");\n}\n","import type { ParsedWikilink } from \"../../../types.js\";\n\n/**\n * Wikilink extraction.\n *\n * Recognised forms:\n * [[Target]]\n * [[Target|Alias]]\n * [[Target#Anchor]]\n * [[Target#Anchor|Alias]]\n * [[Folder/Sub/Target]]\n * [[Target.md]]\n *\n * Embeds (`![[...]]`) and block-references (`[[Target^block-id]]`) are not\n * specially handled — embeds are skipped (the leading `!` prevents the regex\n * match below since we anchor on a non-`!` preceding char), and block-refs\n * are parsed as a normal link whose target ends up containing the `^`-suffix\n * inside `rawTarget`. This is intentional: keep parsing robust, defer\n * semantics to a later layer.\n *\n * Code-block handling:\n * - Triple-backtick fenced blocks: contents are MASKED (replaced with\n * spaces, newlines preserved) so wikilinks inside them are ignored\n * but line numbers for following content remain correct.\n * - Inline code (single backticks) is NOT masked. We consider this\n * acceptable for now — wikilinks inside inline code are rare and the\n * downstream cost of a false positive is low.\n */\n\nconst WIKILINK_RE = /(^|[^!])\\[\\[([^\\[\\]\\n]+?)\\]\\]/g;\n\n/**\n * Regex variant without the `!`-prefix guard. Frontmatter values are scalars\n * (or arrays of scalars) — there's no embed-syntax to disambiguate against,\n * and the surrounding YAML quoting strips any leading char. So we want a\n * pure `[[...]]` matcher here.\n */\nconst FRONTMATTER_WIKILINK_RE = /\\[\\[([^\\[\\]\\n]+?)\\]\\]/g;\n\nexport function extractWikilinks(content: string): ParsedWikilink[] {\n const masked = maskFencedCodeBlocks(content);\n const results: ParsedWikilink[] = [];\n\n // Precompute newline offsets for fast line lookup.\n const lineStarts: number[] = [0];\n for (let i = 0; i < masked.length; i++) {\n if (masked[i] === \"\\n\") lineStarts.push(i + 1);\n }\n\n WIKILINK_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = WIKILINK_RE.exec(masked)) !== null) {\n const prefix = match[1] ?? \"\";\n const inner = match[2];\n if (inner === undefined) continue;\n // Position of the inner target in the masked string:\n const innerStart = match.index + prefix.length + 2; // skip prefix + \"[[\"\n\n const parsed = parseInner(inner);\n if (parsed === null) continue;\n\n const line = lineOf(lineStarts, innerStart);\n results.push({ ...parsed, line });\n }\n\n return results;\n}\n\ninterface InnerParsed {\n rawTarget: string;\n normalizedTarget: string;\n anchor: string | null;\n alias: string | null;\n}\n\nfunction parseInner(inner: string): InnerParsed | null {\n // Split alias first (everything after the first `|`).\n let target = inner;\n let alias: string | null = null;\n const pipeIdx = inner.indexOf(\"|\");\n if (pipeIdx >= 0) {\n target = inner.slice(0, pipeIdx);\n alias = inner.slice(pipeIdx + 1).trim();\n if (alias.length === 0) alias = null;\n }\n\n // Split anchor (first `#` in target).\n let rawTarget = target;\n let anchor: string | null = null;\n const hashIdx = target.indexOf(\"#\");\n if (hashIdx >= 0) {\n rawTarget = target.slice(0, hashIdx);\n anchor = target.slice(hashIdx + 1).trim();\n if (anchor.length === 0) anchor = null;\n }\n\n rawTarget = rawTarget.trim();\n if (rawTarget.length === 0) return null;\n\n const normalizedTarget = normalizeTarget(rawTarget);\n\n return { rawTarget, normalizedTarget, anchor, alias };\n}\n\nfunction normalizeTarget(raw: string): string {\n // Strip trailing .md (case-insensitive), normalize backslashes to forward.\n let t = raw.replace(/\\\\/g, \"/\");\n t = t.replace(/\\.md$/i, \"\");\n return t;\n}\n\nfunction lineOf(lineStarts: number[], offset: number): number {\n // Binary search the largest lineStart <= offset.\n let lo = 0;\n let hi = lineStarts.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n const v = lineStarts[mid];\n if (v !== undefined && v <= offset) lo = mid;\n else hi = mid - 1;\n }\n return lo + 1; // 1-based\n}\n\n/**\n * Replace contents inside triple-backtick fences with spaces, preserving\n * newlines and overall length. Handles fences like ```lang ... ```.\n */\nfunction maskFencedCodeBlocks(content: string): string {\n const chars = content.split(\"\");\n const fenceRe = /^([ \\t]*)(`{3,}|~{3,})([^\\n]*)$/gm;\n // We'll do a stateful scan line by line for correctness.\n const lines = content.split(\"\\n\");\n let inFence = false;\n let fenceMarker = \"\";\n let absOffset = 0;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n const trimmed = line.trimStart();\n if (!inFence) {\n const m = /^(`{3,}|~{3,})/.exec(trimmed);\n if (m !== null && m[1] !== undefined) {\n inFence = true;\n fenceMarker = m[1][0] ?? \"`\";\n // Do NOT mask the fence line itself — only contents inside.\n }\n } else {\n const m = /^(`{3,}|~{3,})\\s*$/.exec(trimmed);\n if (m !== null && m[1] !== undefined && m[1][0] === fenceMarker) {\n inFence = false;\n } else {\n // Mask this content line: replace every char with space.\n for (let j = 0; j < line.length; j++) {\n chars[absOffset + j] = \" \";\n }\n }\n }\n absOffset += line.length + 1; // +1 for the \"\\n\"\n }\n // suppress unused fenceRe (kept for clarity)\n void fenceRe;\n return chars.join(\"\");\n}\n\n/**\n * Extract wikilinks from a parsed YAML frontmatter object.\n *\n * Walks the frontmatter recursively and collects every `[[Target]]`,\n * `[[Target|Alias]]`, `[[Target#Anchor]]` occurrence found in any string\n * value at any depth. Supports the common Obsidian vault patterns:\n *\n * organisation: \"[[Holger Hoos]]\"\n * members: [\"[[Jörg Herbers]]\", \"[[Oliver Wrede]]\"]\n * affiliated_with:\n * - \"[[INFORM GmbH]]\"\n * - \"[[RWTH Aachen]]\"\n * Teilnehmer: \"[[OWR]], [[JHE]]\"\n *\n * Edge cases handled:\n * - Unquoted YAML wikilinks (`Klient: [[LAG]]`) parse as nested arrays of\n * strings via YAML's flow-sequence syntax — gray-matter delivers\n * `[[\"LAG\"]]`. We treat string-array elements as plain wikilink targets\n * (no anchor/alias parsing — those forms require the bracket syntax to\n * survive YAML, which only happens inside quotes).\n * - Skip the `aliases:` / `alias:` keys entirely — those are alias names,\n * not links to other notes. Body wikilinks may reference an alias as\n * target, but the alias entry itself is not a link.\n *\n * All emitted wikilinks carry `line: 0` to mark \"from frontmatter\" — the\n * frontmatter offset isn't reachable from gray-matter without re-parsing,\n * and consumers (graph queries, broken-link detection) only need source/\n * target/anchor/alias; line numbers are advisory.\n */\nexport function extractFrontmatterWikilinks(\n frontmatter: Record | null,\n): ParsedWikilink[] {\n if (!frontmatter) return [];\n const results: ParsedWikilink[] = [];\n for (const [key, value] of Object.entries(frontmatter)) {\n if (key === \"aliases\" || key === \"alias\") continue;\n collectFromValue(value, results);\n }\n return results;\n}\n\nfunction collectFromValue(value: unknown, out: ParsedWikilink[]): void {\n if (typeof value === \"string\") {\n collectFromString(value, out);\n return;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n collectFromValue(item, out);\n }\n return;\n }\n if (value !== null && typeof value === \"object\") {\n for (const v of Object.values(value as Record)) {\n collectFromValue(v, out);\n }\n }\n // numbers, booleans, null → no wikilink can be hiding here.\n}\n\nfunction collectFromString(s: string, out: ParsedWikilink[]): void {\n FRONTMATTER_WIKILINK_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = FRONTMATTER_WIKILINK_RE.exec(s)) !== null) {\n const inner = match[1];\n if (inner === undefined) continue;\n const parsed = parseInner(inner);\n if (parsed === null) continue;\n out.push({ ...parsed, line: 0 });\n }\n}\n","/**\n * Datacore / Dataview fenced-block handling for indexing (ADR-033).\n *\n * Obsidian notes embed dynamic views as fenced code blocks:\n * ```datacorejsx … ``` (JavaScript/JSX — Datacore)\n * ```datacore … ```\n * ```dataview … ``` (DQL — Dataview)\n * ```dataviewjs … ```\n *\n * These render a DIFFERENT structure (tables/lists) at view time INSIDE\n * Obsidian; the rendered output is never persisted to disk. A headless indexer\n * therefore only sees the block SOURCE — JavaScript / a query DSL — which is\n * noise for retrieval (see ADR-033 §Context).\n *\n * This module is the headless baseline of ADR-033: replace each dynamic-view\n * fence's BODY with a short neutral placeholder so the query source doesn't\n * pollute the index, while leaving surrounding prose + headings intact. The\n * Obsidian plugin (ADR-033 phase 3) later OVERRIDES this with the actually\n * rendered content when Datacore is active.\n *\n * Pure string transform — no fs / Obsidian / network. The transform is applied\n * to the INDEXED projection of a note's body only; the raw body (used for the\n * change-detection hash and for wikilink extraction) is untouched.\n */\n\n/** Fence languages whose body is dynamic-view source, not prose. */\nconst DYNAMIC_VIEW_LANGS = new Set([\n \"datacore\",\n \"datacorejsx\",\n \"datacorejs\",\n \"dataview\",\n \"dataviewjs\",\n]);\n\n/** Placeholder substituted for a stripped dynamic-view block body. */\nexport const DATACORE_PLACEHOLDER = \"[Datacore view]\";\n\nconst FENCE_OPEN_RE = /^(\\s*)(`{3,}|~{3,})\\s*([A-Za-z0-9_-]*)\\s*$/;\n\n/**\n * Replace the body of every Datacore/Dataview fenced block with a neutral\n * placeholder line, preserving everything else byte-for-byte. The fence\n * delimiters are dropped along with the body — the placeholder stands in for\n * the whole block so chunking/sectioning see a short, meaningful token instead\n * of code.\n *\n * Matching rules (CommonMark-ish, sufficient for Obsidian):\n * - An opening fence is ``` or ~~~ (3+) followed by an info string; the block\n * closes on the first line that is a fence of the SAME marker char and at\n * least the same length, with no info string.\n * - Only blocks whose info string (lowercased) is a known dynamic-view lang are\n * replaced. All other code blocks pass through unchanged.\n * - An unterminated dynamic-view fence (no closing fence to EOF) is replaced\n * through end-of-input — defensive against malformed notes.\n *\n * Returns `{ content, replaced }` where `replaced` is the number of blocks\n * substituted (0 ⇒ the input is returned unchanged, so callers can cheaply\n * detect \"no dynamic views\").\n */\nexport function stripDynamicViewBlocks(body: string): { content: string; replaced: number } {\n if (!body.includes(\"```\") && !body.includes(\"~~~\")) {\n return { content: body, replaced: 0 };\n }\n const lines = body.split(\"\\n\");\n const out: string[] = [];\n let replaced = 0;\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i]!;\n const open = FENCE_OPEN_RE.exec(line);\n if (open) {\n const indent = open[1] ?? \"\";\n const marker = open[2] ?? \"\";\n const lang = (open[3] ?? \"\").toLowerCase();\n const markerChar = marker[0]!;\n const isDynamic = DYNAMIC_VIEW_LANGS.has(lang);\n\n // Find the closing fence (same char, length >= opening, empty info).\n let j = i + 1;\n let closed = false;\n while (j < lines.length) {\n const close = FENCE_OPEN_RE.exec(lines[j]!);\n if (\n close &&\n (close[2] ?? \"\")[0] === markerChar &&\n (close[2] ?? \"\").length >= marker.length &&\n (close[3] ?? \"\") === \"\"\n ) {\n closed = true;\n break;\n }\n j++;\n }\n\n if (isDynamic) {\n // Replace the whole block (open..close) with one placeholder line,\n // preserving the opening indent so it reads naturally in context.\n out.push(`${indent}${DATACORE_PLACEHOLDER}`);\n replaced++;\n i = closed ? j + 1 : lines.length; // skip block (or to EOF if unterminated)\n } else {\n // Non-dynamic code block: emit verbatim, including delimiters.\n out.push(line);\n if (closed) {\n for (let k = i + 1; k <= j; k++) out.push(lines[k]!);\n i = j + 1;\n } else {\n for (let k = i + 1; k < lines.length; k++) out.push(lines[k]!);\n i = lines.length;\n }\n }\n } else {\n out.push(line);\n i++;\n }\n }\n\n if (replaced === 0) return { content: body, replaced: 0 };\n return { content: out.join(\"\\n\"), replaced };\n}\n","import { createHash } from \"node:crypto\";\n\n/** SHA-256 hex digest of input string (utf-8). */\nexport function sha256(input: string): string {\n return createHash(\"sha256\").update(input, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Canonical JSON serialization with stable, alphabetically-sorted object keys.\n *\n * Why: JavaScript preserves object-property insertion order, so\n * `JSON.stringify({a:1,b:2})` and `JSON.stringify({b:2,a:1})` produce different\n * strings even though the objects are semantically identical. When this output\n * is fed into the note `hash`, the same note re-parsed with frontmatter keys in\n * a different order would yield a different hash — causing spurious optimistic-\n * concurrency conflicts in `write_note` / `update_frontmatter`.\n *\n * Rules:\n * - Object keys are sorted lexicographically.\n * - Arrays preserve their insertion order (order is semantically meaningful).\n * - Primitives (string/number/boolean) use standard JSON.stringify.\n * - `null` and `undefined` serialize to \"null\".\n * - Recursion through nested objects and arrays.\n *\n * Migration note: existing rows in the SQLite index were hashed with the\n * non-canonical `JSON.stringify`. We intentionally do NOT migrate them — the\n * next time each note is re-indexed (any mtime change or a full re-scan),\n * its hash is recomputed canonically and self-heals.\n */\nexport function canonicalJsonStringify(value: unknown): string {\n if (value === null || value === undefined) return \"null\";\n if (Array.isArray(value)) {\n return \"[\" + value.map((v) => canonicalJsonStringify(v)).join(\",\") + \"]\";\n }\n if (typeof value === \"object\") {\n const obj = value as Record;\n const keys = Object.keys(obj).sort();\n const parts = keys.map((k) => JSON.stringify(k) + \":\" + canonicalJsonStringify(obj[k]));\n return \"{\" + parts.join(\",\") + \"}\";\n }\n // Primitives (string, number, boolean). NaN/Infinity → \"null\" via JSON.stringify.\n const s = JSON.stringify(value);\n return s === undefined ? \"null\" : s;\n}\n\n/**\n * Canonical content-hash for a note: sha256(content + canonicalJson(frontmatter ?? {})).\n *\n * All call sites (reader/parser, write, frontmatter/update) MUST go\n * through this function to guarantee identical hashes across the codebase.\n */\nexport function computeNoteHash(\n content: string,\n frontmatter: Record | null | undefined,\n): string {\n return sha256(content + canonicalJsonStringify(frontmatter ?? {}));\n}\n\n/**\n * Body-only hash: sha256(content) — independent of frontmatter.\n *\n * Used by the indexer to short-circuit chunk + embed work when the body\n * is unchanged but frontmatter differs. See migration 006 for rationale.\n *\n * Note: this is intentionally NOT a substring of computeNoteHash's input —\n * we want it to remain stable when frontmatter changes, which the\n * combined hash explicitly does not.\n */\nexport function computeBodyHash(content: string): string {\n return sha256(content);\n}\n","import { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport matter from \"gray-matter\";\nimport type { ParsedNote } from \"../../../types.js\";\nimport { extractWikilinks, extractFrontmatterWikilinks } from \"./wikilinks.js\";\nimport { stripDynamicViewBlocks } from \"../../../reader/datacore.js\";\nimport { computeNoteHash, computeBodyHash } from \"./hash.js\";\n\n/**\n * Parse a single markdown file into a ParsedNote.\n *\n * `relativePath` is always posix (forward slashes), relative to `vaultRoot`.\n */\nexport async function parseNote(absolutePath: string, vaultRoot: string): Promise {\n const raw = await fs.readFile(absolutePath, \"utf-8\");\n const stat = await fs.stat(absolutePath);\n\n const parsed = matter(raw);\n const content = parsed.content;\n const fmData = parsed.data as Record | undefined;\n const frontmatter: Record | null =\n fmData !== undefined && Object.keys(fmData).length > 0 ? fmData : null;\n\n const title = extractTitle(content) ?? path.basename(absolutePath, \".md\");\n const hash = computeNoteHash(content, frontmatter);\n const bodyHash = computeBodyHash(content);\n const mtime = Math.floor(stat.mtimeMs);\n // Body wikilinks first (richer data: line, alias), then frontmatter.\n // We deduplicate the frontmatter additions against the body set on the\n // (normalizedTarget, anchor) key — otherwise a member-list in frontmatter\n // that's also referenced in body would produce a \"phantom\" extra backlink.\n //\n // (Note: SQLite's UNIQUE(source, target, anchor) constraint does NOT dedup\n // here, because NULL anchors are never equal to each other under SQL\n // semantics. App-level dedup is the only reliable path.)\n //\n // Within body and within frontmatter we keep duplicates: body duplicates\n // are pre-existing behaviour (multiple mentions of the same target across\n // different lines were always inserted as separate rows), and frontmatter\n // duplicates are vanishingly rare in practice. Limiting the dedup scope\n // keeps this change minimal and behaviour-preserving for body wikilinks.\n const bodyLinks = extractWikilinks(content);\n const frontmatterLinks = extractFrontmatterWikilinks(frontmatter);\n const wikilinks =\n frontmatterLinks.length === 0\n ? bodyLinks\n : mergeFrontmatterIntoBody(bodyLinks, frontmatterLinks);\n const wordCount = countWords(content);\n const relativePath = toPosix(path.relative(path.resolve(vaultRoot), path.resolve(absolutePath)));\n\n // ADR-033: project a clean body for indexing — Datacore/Dataview dynamic-view\n // fence bodies become a neutral placeholder so query source doesn't pollute\n // the index. `content` (hash + wikilinks) is untouched. No-op when the note\n // has no dynamic-view blocks (returns `content` unchanged).\n const indexedContent = stripDynamicViewBlocks(content).content;\n\n return {\n relativePath,\n content,\n indexedContent,\n frontmatter,\n title,\n hash,\n bodyHash,\n mtime,\n wikilinks,\n wordCount,\n };\n}\n\n/**\n * Combine body and frontmatter wikilinks, dropping any frontmatter entry whose\n * `(normalizedTarget, anchor)` already appears in the body set. Both inputs\n * are preserved otherwise in their original order.\n */\nfunction mergeFrontmatterIntoBody(\n body: ReturnType,\n fm: ReturnType,\n): ReturnType {\n const seen = new Set();\n for (const w of body) {\n seen.add(`${w.normalizedTarget}\u0000${w.anchor ?? \"\"}`);\n }\n const result = body.slice();\n for (const w of fm) {\n const key = `${w.normalizedTarget}\u0000${w.anchor ?? \"\"}`;\n if (seen.has(key)) continue;\n seen.add(key);\n result.push(w);\n }\n return result;\n}\n\n/** Find the first H1 (`# Title`) at the start of a line. */\nfunction extractTitle(content: string): string | null {\n const lines = content.split(\"\\n\");\n for (const line of lines) {\n const m = /^#\\s+(.+?)\\s*$/.exec(line);\n if (m !== null && m[1] !== undefined) return m[1].trim();\n // Stop scanning into the body too far — but Obsidian title H1 can be\n // anywhere near the top. We keep scanning the whole content; cheap.\n }\n return null;\n}\n\nfunction countWords(content: string): number {\n if (content.length === 0) return 0;\n return content.split(/\\s+/).filter((s) => s.length > 0).length;\n}\n\nfunction toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n","/**\n * ObsidianFsSource — the v2 SourceConnector implementation for\n * filesystem-backed Obsidian vaults.\n *\n * Wraps the relocated scanner / parser / hash / wikilinks modules behind\n * the ADR-002 §SourceConnector contract. This file is the SOLE entry\n * point through which Layer-0 retrieval obtains content for an\n * obsidian-fs vault; the registry hands callers an `ObsidianFsSource`\n * keyed by the `obsidian-fs://` handle.\n *\n * # Invariant carve-outs (ADR-002)\n *\n * - I-2 (raw `node:fs` / `node:path`): ALLOWED inside this directory.\n * `readDocument`, `hash`, and `exists` use `fs.readFile` / `fs.stat`\n * directly; `formatDisplayUrl` uses `path.basename` style helpers.\n * - I-3 (raw file-path manipulation): ALLOWED — the adapter owns the\n * conversion between DocId and absolute filesystem path.\n * - I-4 (YAML-frontmatter parsing via `gray-matter`): ALLOWED — the\n * relocated `./parser.ts` already imports it, and that import is now\n * confined to this directory (modulo the existing write-side leaks in\n * `src/write/write.ts` and `src/frontmatter/update.ts`, which plan\n * 01-04 absorbs into the delivery adapter).\n *\n * # Capabilities (Invariant I-7 — honest publication)\n *\n * bodyShape: \"flat-text\" — single-paragraph fallback for v1 compat\n * properties: \"untyped\" — YAML frontmatter is untyped per ADR-003\n * linkTypes: [\"wikilink\"] — sole edge type emitted\n * identityStable: false — paths rename; DocIds are not durable\n * permissions: false — fs ACLs not modeled\n * contentHashStable: true — sha256(content + canonicalJson(fm))\n * refHashKind: \"content\" — DocumentRef.hash === Document.hash\n * watch: \"push\" — chokidar lands in plan 01-05\n *\n * # Phase-3 follow-ups\n *\n * - `blocks` is a single-paragraph stub; richer block decomposition is\n * Phase 3 work (ADR-003 BlockNode union).\n * - The v1 hash semantics (`computeNoteHash(body, frontmatter)`) are\n * preserved for Phase 1 backwards-compat. ADR-003 H-1..H-6 may revise\n * the canonical hash later.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Document, DocId, SourceHandle, VaultConfig, WikilinkRef } from \"../../../types.js\";\nimport type { DocumentRef, ListOptions, SourceCapabilities, SourceConnector } from \"../types.js\";\nimport { formatDocId, parseSourceHandle } from \"../../registry.js\";\nimport { scanVault, scanContractFiles } from \"./scanner.js\";\nimport { parseNote } from \"./parser.js\";\nimport { computeBodyHash } from \"./hash.js\";\nimport { errorMessage } from \"../../../errors/format.js\";\n\n/**\n * Phase 6 / Plan 06-04 — task-contract YAML path matcher. Mirrors\n * `CONTRACT_PATH_REGEX` in `src/contracts/types.ts` (Pitfall F3\n * non-recursion). YAML files under `_contracts/` are enumerated +\n * read through the SourceConnector seam so the contract loader's\n * boot scan + ChangeFeed paths see real on-disk YAML.\n */\nconst CONTRACT_PATH_RE = /^_contracts\\/[^/]+\\.yaml$/;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ObsidianFsSource\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst SCHEME = \"obsidian-fs\";\n\nexport class ObsidianFsSource implements SourceConnector {\n readonly handle: SourceHandle;\n\n readonly capabilities: SourceCapabilities = {\n bodyShape: \"flat-text\",\n properties: \"untyped\",\n linkTypes: [\"wikilink\"] as const,\n identityStable: false,\n permissions: false,\n contentHashStable: true,\n refHashKind: \"content\",\n watch: \"push\",\n };\n\n constructor(private readonly vault: VaultConfig) {\n this.handle = parseSourceHandle(`${SCHEME}://${vault.name}`);\n }\n\n // ── enumeration ────────────────────────────────────────────────────────────\n\n async *listDocuments(opts?: ListOptions): AsyncIterable {\n const excludeOverlay = opts?.excludeGlobs;\n const mdFiles = await scanVault(this.vault.path, {\n ...(excludeOverlay ? { excludeGlobs: excludeOverlay } : {}),\n });\n // Phase 6 / Plan 06-04 — yield task-contract YAML files alongside .md\n // notes. The contract loader's boot scan + ChangeFeed paths filter by\n // CONTRACT_PATH_REGEX inside `src/contracts/loader.ts`, so unrelated\n // consumers (indexer, watcher, search) that filter on `.md` extension\n // are unaffected. The indexer uses scanVault() directly (not this\n // method) so its .md-only contract is preserved.\n const yamlFiles = await scanContractFiles(this.vault.path);\n const files = mdFiles.concat(yamlFiles);\n files.sort();\n const since = opts?.since;\n const limit = opts?.limit;\n let yielded = 0;\n for (const abs of files) {\n if (limit !== undefined && yielded >= limit) break;\n const rel = this.toPosix(path.relative(path.resolve(this.vault.path), abs));\n const stat = await fs.stat(abs);\n const mtime = Math.floor(stat.mtimeMs);\n if (since !== undefined && mtime < since) continue;\n // Cheap content hash for the ref — matches refHashKind: \"content\"\n const body = await fs.readFile(abs, \"utf-8\");\n const hash = computeBodyHash(body);\n // A pathological filename (e.g. an embedded newline from a botched\n // Obsidian title) makes pathToDocId → formatDocId throw. Skipping the\n // one bad file keeps a single malformed note from aborting the whole\n // listDocuments() iteration — which previously took down bootScan /\n // the contract registry for the entire vault.\n let id: DocId;\n try {\n id = this.pathToDocId(rel);\n } catch (err) {\n console.error(\n `[obsidian-fs:${this.vault.name}] skipping un-addressable file ` +\n `${JSON.stringify(rel)}: ${errorMessage(err)}`,\n );\n continue;\n }\n yield { id, mtime, hash };\n yielded++;\n }\n }\n\n // ── single-doc reads ───────────────────────────────────────────────────────\n\n async readDocument(id: DocId): Promise {\n const rel = this.docIdToPath(id);\n const abs = this.absPath(rel);\n\n // Phase 6 / Plan 06-04 — task-contract YAML branch. parseNote()\n // assumes markdown + frontmatter; it would mis-parse a YAML\n // contract file. Return the raw text as a single paragraph block\n // with no properties; the contract loader (`src/contracts/loader.ts`)\n // is the only consumer and parses the body via `yaml@2.9`.\n if (CONTRACT_PATH_RE.test(rel)) {\n const body = await fs.readFile(abs, \"utf-8\");\n const stat = await fs.stat(abs);\n const hash = computeBodyHash(body);\n return {\n id,\n source: this.handle,\n title: rel,\n blocks: [{ kind: \"paragraph\", text: body }],\n properties: {},\n links: [],\n mtime: Math.floor(stat.mtimeMs),\n hash,\n display_url: this.formatDisplayUrl(id),\n };\n }\n\n const parsed = await parseNote(abs, this.vault.path);\n\n // D-05: surface wikilinks as Document.properties.wikilinks: WikilinkRef[]\n const wikilinks: WikilinkRef[] = parsed.wikilinks.map((w) => {\n const ref: WikilinkRef = { target: w.normalizedTarget };\n if (w.alias !== null) ref.alias = w.alias;\n if (w.anchor !== null) ref.section = w.anchor;\n return ref;\n });\n\n const properties: Record = {\n ...(parsed.frontmatter ?? {}),\n wikilinks,\n };\n\n return {\n id,\n source: this.handle,\n title: parsed.title,\n blocks: [{ kind: \"paragraph\", text: parsed.content }],\n properties,\n links: [],\n mtime: parsed.mtime,\n hash: parsed.hash,\n display_url: this.formatDisplayUrl(id),\n };\n }\n\n async hash(id: DocId): Promise {\n const rel = this.docIdToPath(id);\n const abs = this.absPath(rel);\n const body = await fs.readFile(abs, \"utf-8\");\n return computeBodyHash(body);\n }\n\n async exists(id: DocId): Promise {\n try {\n const rel = this.docIdToPath(id);\n const abs = this.absPath(rel);\n await fs.stat(abs);\n return true;\n } catch {\n return false;\n }\n }\n\n // ── display ────────────────────────────────────────────────────────────────\n\n formatDisplayUrl(id: DocId): string {\n const rel = this.docIdToPath(id);\n const vault = encodeURIComponent(this.vault.name);\n const file = encodeURIComponent(rel);\n return `obsidian://open?vault=${vault}&file=${file}`;\n }\n\n // ── helpers ────────────────────────────────────────────────────────────────\n\n /**\n * Parse the URI authority + resource off a DocId. Asserts the authority\n * matches `this.vault.name` — prevents one vault's adapter from reading\n * another vault's file via a forged DocId (T-01-03-02 in the plan's\n * threat model).\n */\n private docIdToPath(id: DocId): string {\n const prefix = `${SCHEME}://`;\n if (!id.startsWith(prefix)) {\n throw new Error(`DocId scheme mismatch: expected \"${SCHEME}://…\", got ${JSON.stringify(id)}`);\n }\n const rest = id.slice(prefix.length);\n const slash = rest.indexOf(\"/\");\n if (slash < 0) {\n throw new Error(`Invalid DocId shape: missing resource path in ${JSON.stringify(id)}`);\n }\n const authority = rest.slice(0, slash);\n const resource = rest.slice(slash + 1);\n if (authority !== this.vault.name) {\n throw new Error(\n `DocId vault mismatch: id authority \"${authority}\" does not match ` +\n `this adapter's configured vault \"${this.vault.name}\"`,\n );\n }\n if (resource.length === 0) {\n throw new Error(`Invalid DocId: empty resource path in ${JSON.stringify(id)}`);\n }\n return resource;\n }\n\n private pathToDocId(rel: string): DocId {\n const posix = this.toPosix(rel);\n return formatDocId(SCHEME, this.vault.name, posix);\n }\n\n private absPath(rel: string): string {\n return path.resolve(this.vault.path, rel);\n }\n\n private toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n }\n}\n","/**\n * Token-count approximation for chunk sizing.\n *\n * This is intentionally NOT a real BPE tokenizer. We use a simple length/4\n * heuristic that is \"good enough\" to keep chunks in a ~400-token band, which\n * is all the chunker actually needs.\n *\n * Why this is fine:\n * - Chunk sizing is a band, not an exact budget. Embedding models tell us at\n * call time if a chunk was too long, and we re-chunk then.\n * - A real tokenizer (tiktoken, transformers.js) would couple us to a model\n * family. We embed via Ollama with model-agnostic input, so any model-\n * specific count would be wrong for some models anyway.\n *\n * If we ever need accuracy, swap this for a real tokenizer here — the rest of\n * the chunker only depends on `countTokens`.\n */\nexport function countTokens(text: string): number {\n if (text.length === 0) return 0;\n return Math.ceil(text.length / 4);\n}\n","/**\n * Heading-aware Markdown chunker.\n *\n * Strategy (in order of preference for splits):\n * 1. Heading boundaries (level 1–3) — preferred.\n * 2. Paragraph boundaries (blank lines) — used when a heading section is\n * itself too long.\n * 3. Sentence boundaries (`.!?` followed by whitespace + uppercase) —\n * naive, no abbreviation handling in MVP.\n * 4. Hard cut at `maxTokens * 4` characters — last resort.\n *\n * Overlap is applied as a character window taken from the tail of the previous\n * chunk; if a sentence boundary is found within the overlap window, we start\n * at that boundary for cleaner reads.\n */\n\nimport type { Chunk, ChunkOptions } from \"../types.js\";\nimport { countTokens } from \"./tokens.js\";\nimport { extractHeadings, headingPathAtOffset } from \"./headings.js\";\nimport type { HeadingRef } from \"./headings.js\";\n\nconst DEFAULT_MAX_TOKENS = 400;\nconst DEFAULT_OVERLAP_TOKENS = 50;\n/**\n * Minimum non-whitespace characters required for a chunk to be kept.\n * Notes that begin with a blank line before the first heading produce a\n * leading whitespace-only span; those would otherwise become a chunk_idx=0\n * \"\\n\" chunk and pollute search top-k because their embedding is close to\n * the embedding of every other near-empty text (cosine ≈ 1.0). Trimming\n * here is the source of truth — search/rerank don't need a follow-up filter.\n */\nconst MIN_CHUNK_TRIM_CHARS = 3;\n\ninterface Span {\n start: number;\n end: number; // exclusive\n}\n\nexport function chunkNote(content: string, options?: ChunkOptions): Chunk[] {\n if (content.length === 0) return [];\n\n const maxTokens = options?.maxTokens ?? DEFAULT_MAX_TOKENS;\n const overlapTokens = options?.overlapTokens ?? DEFAULT_OVERLAP_TOKENS;\n const maxChars = maxTokens * 4;\n const overlapChars = overlapTokens * 4;\n\n const headings = extractHeadings(content);\n\n // Fast path: whole note fits.\n if (countTokens(content) <= maxTokens) {\n if (content.trim().length < MIN_CHUNK_TRIM_CHARS) return [];\n return [\n {\n idx: 0,\n text: content,\n headingPath: headingPathAtOffset(headings, 0),\n startOffset: 0,\n endOffset: content.length,\n tokenCount: countTokens(content),\n },\n ];\n }\n\n // 1. Build initial spans by splitting at level 1–3 headings.\n const headingSpans = splitAtHeadings(content, headings, maxChars);\n\n // 2. For each span still too large, recursively split: paragraphs → sentences → hard cut.\n const finalSpans: Span[] = [];\n for (const span of headingSpans) {\n if (span.end - span.start <= maxChars) {\n finalSpans.push(span);\n } else {\n finalSpans.push(...splitParagraphs(content, span, maxChars));\n }\n }\n\n // 3. Apply overlap and build Chunk objects.\n // headingPath is computed at the *primary* span start (pre-overlap) so the\n // heading describes the chunk's own content, not borrowed overlap text.\n const chunks: Chunk[] = [];\n for (let i = 0; i < finalSpans.length; i++) {\n const span = finalSpans[i];\n if (!span) continue;\n const primaryStart = span.start;\n let start = span.start;\n const end = span.end;\n\n if (i > 0 && overlapChars > 0) {\n const overlapStart = Math.max(0, start - overlapChars);\n // Try to align to a sentence boundary inside the overlap window.\n const window = content.slice(overlapStart, start);\n const sentenceIdx = findLastSentenceBoundary(window);\n start = sentenceIdx >= 0 ? overlapStart + sentenceIdx : overlapStart;\n }\n\n const text = content.slice(start, end);\n // Drop whitespace-only and tiny chunks: they produce near-identical\n // embeddings and pollute search top-k (see MIN_CHUNK_TRIM_CHARS doc).\n if (text.trim().length < MIN_CHUNK_TRIM_CHARS) continue;\n\n chunks.push({\n idx: chunks.length,\n text,\n headingPath: headingPathAtOffset(headings, primaryStart),\n startOffset: start,\n endOffset: end,\n tokenCount: countTokens(text),\n });\n }\n\n return chunks;\n}\n\n/**\n * Split content into spans bounded by level 1–3 ATX headings.\n * Each span starts at a heading (or the document start) and runs until just\n * before the next eligible heading.\n *\n * Headings deeper than level 3 do not break sections (they live inside).\n */\nfunction splitAtHeadings(content: string, headings: HeadingRef[], _maxChars: number): Span[] {\n const boundaries: number[] = [0];\n for (const h of headings) {\n if (h.level <= 3 && h.startOffset > 0) {\n boundaries.push(h.startOffset);\n }\n }\n boundaries.push(content.length);\n\n // Deduplicate / sort defensively.\n const uniq = [...new Set(boundaries)].sort((a, b) => a - b);\n\n const spans: Span[] = [];\n for (let i = 0; i < uniq.length - 1; i++) {\n const start = uniq[i];\n const end = uniq[i + 1];\n if (start === undefined || end === undefined) continue;\n if (end > start) spans.push({ start, end });\n }\n return spans;\n}\n\n/**\n * Split a single span by paragraph boundaries (blank lines / `\\n\\n+`), packing\n * paragraphs greedily up to `maxChars`. Falls back to sentence-splitting for\n * any paragraph that is itself too long.\n */\nfunction splitParagraphs(content: string, span: Span, maxChars: number): Span[] {\n const text = content.slice(span.start, span.end);\n const paragraphs: Span[] = [];\n const re = /\\n{2,}/g;\n let cursor = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n const paraEnd = m.index;\n if (paraEnd > cursor) {\n paragraphs.push({ start: span.start + cursor, end: span.start + paraEnd });\n }\n cursor = m.index + m[0].length;\n }\n if (cursor < text.length) {\n paragraphs.push({ start: span.start + cursor, end: span.end });\n }\n if (paragraphs.length === 0) {\n paragraphs.push({ start: span.start, end: span.end });\n }\n\n const out: Span[] = [];\n let current: Span | null = null;\n\n const flush = () => {\n if (!current) return;\n if (current.end - current.start <= maxChars) {\n out.push(current);\n } else {\n out.push(...splitSentences(content, current, maxChars));\n }\n current = null;\n };\n\n for (const p of paragraphs) {\n if (!current) {\n current = { start: p.start, end: p.end };\n continue;\n }\n if (p.end - current.start <= maxChars) {\n current = { start: current.start, end: p.end };\n } else {\n flush();\n current = { start: p.start, end: p.end };\n }\n }\n flush();\n\n return out;\n}\n\n/**\n * Sentence-aware split. Detects `.!?` followed by whitespace + an uppercase\n * letter as a sentence boundary. No abbreviation handling in MVP.\n *\n * Packs sentences greedily up to `maxChars`. Falls back to hard cut for any\n * sentence that is itself too long.\n */\nfunction splitSentences(content: string, span: Span, maxChars: number): Span[] {\n const text = content.slice(span.start, span.end);\n const boundaries: number[] = [];\n const re = /[.!?]\\s+(?=[A-ZÄÖÜ])/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n boundaries.push(m.index + m[0].length);\n }\n\n const sentences: Span[] = [];\n let cursor = 0;\n for (const b of boundaries) {\n if (b > cursor) {\n sentences.push({ start: span.start + cursor, end: span.start + b });\n cursor = b;\n }\n }\n if (cursor < text.length) {\n sentences.push({ start: span.start + cursor, end: span.end });\n }\n if (sentences.length === 0) {\n sentences.push({ start: span.start, end: span.end });\n }\n\n const out: Span[] = [];\n let current: Span | null = null;\n\n const flush = () => {\n if (!current) return;\n if (current.end - current.start <= maxChars) {\n out.push(current);\n } else {\n out.push(...hardCut(current, maxChars));\n }\n current = null;\n };\n\n for (const s of sentences) {\n if (!current) {\n current = { start: s.start, end: s.end };\n continue;\n }\n if (s.end - current.start <= maxChars) {\n current = { start: current.start, end: s.end };\n } else {\n flush();\n current = { start: s.start, end: s.end };\n }\n }\n flush();\n\n return out;\n}\n\n/**\n * Last-resort: cut into fixed-size character windows.\n */\nfunction hardCut(span: Span, maxChars: number): Span[] {\n const out: Span[] = [];\n for (let s = span.start; s < span.end; s += maxChars) {\n out.push({ start: s, end: Math.min(span.end, s + maxChars) });\n }\n return out;\n}\n\n/**\n * Find the offset within `window` just after the last sentence boundary,\n * or -1 if none found.\n */\nfunction findLastSentenceBoundary(window: string): number {\n const re = /[.!?]\\s+(?=[A-ZÄÖÜ])/g;\n let last = -1;\n let m: RegExpExecArray | null;\n while ((m = re.exec(window)) !== null) {\n last = m.index + m[0].length;\n }\n return last;\n}\n","/**\n * Chunker module — heading-aware Markdown chunking for embedding.\n *\n * Public surface:\n * - `chunkNote(content, options?)` — split a note body into Chunk[]\n * - `countTokens(text)` — approximate token counter (length/4 heuristic)\n * - `extractHeadings(content)` — ATX heading extraction (ignores code fences)\n */\n\nexport { chunkNote } from \"./chunker.js\";\nexport { countTokens } from \"./tokens.js\";\nexport { extractHeadings, headingPathAtOffset } from \"./headings.js\";\nexport type { HeadingRef } from \"./headings.js\";\n","/**\n * WikilinkResolver — per-index-run resolver with prepared-statement reuse\n * and a target-path → noteId cache.\n *\n * Why this exists:\n * resolveWikilinkTarget() is called once per wikilink during indexVault.\n * Each call did up to three SQL operations and prepared the filename-match\n * statement on the fly. On large vaults (5k notes / 20k links) that's\n * measurable. This class:\n * - prepares the filename-match statement once,\n * - memoises results by normalised target path inside a single run.\n *\n * Cache scope:\n * One instance per indexVault run. Notes inserted during the run can\n * change resolution results (transient broken links), which is why the\n * second pass uses a fresh resolver — see indexer.ts. Do NOT reuse an\n * instance across runs.\n *\n * Key choice:\n * Obsidian's heuristic in this codebase ignores the source note's folder,\n * so the cache key is just the normalised target path. If same-folder\n * priority is added later, switch to `${sourcePath}::${targetPath}`.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\nimport type { Vault } from \"../vault/index.js\";\n\nexport interface ResolveHit {\n id: number;\n path: string;\n}\n\nexport class WikilinkResolver {\n private readonly vault: Vault;\n private readonly filenameStmt: BetterSqlite3.Statement<\n [string, string],\n { id: number; path: string }\n >;\n private readonly cache = new Map();\n\n constructor(vault: Vault) {\n this.vault = vault;\n this.filenameStmt = vault.db.handle.prepare(\n `SELECT id, path FROM notes\n WHERE path = ?\n OR path LIKE ?\n ORDER BY length(path) ASC\n LIMIT 1`,\n );\n }\n\n /**\n * Resolve a wikilink target the way Obsidian does, in priority order:\n * 1) exact relative path match (with or without .md)\n * 2) filename-only match anywhere in the vault — shortest path wins\n * 3) alias match — looks up note_aliases (case-insensitive)\n *\n * Returns null if no candidate exists.\n */\n resolve(normalizedTarget: string): ResolveHit | null {\n const cached = this.cache.get(normalizedTarget);\n if (cached !== undefined) return cached;\n\n const hit = this.resolveUncached(normalizedTarget);\n this.cache.set(normalizedTarget, hit);\n return hit;\n }\n\n private resolveUncached(normalizedTarget: string): ResolveHit | null {\n // 1. Exact relative path (with .md, then without)\n const exact =\n this.vault.db.notes.getByPath(`${normalizedTarget}.md`) ??\n this.vault.db.notes.getByPath(normalizedTarget);\n if (exact) return { id: exact.id, path: exact.path };\n\n // 2 + 3 only apply to slash-less targets (filename-only references).\n if (!normalizedTarget.includes(\"/\")) {\n const filename = `${normalizedTarget}.md`;\n const suffix = `%/${filename}`;\n const hit = this.filenameStmt.get(filename, suffix);\n if (hit) return hit;\n\n const aliasHit = this.vault.db.aliases.resolve(normalizedTarget);\n if (aliasHit) {\n return { id: aliasHit.note_id, path: aliasHit.path };\n }\n }\n\n return null;\n }\n\n /** Test/diagnostics: cache size after a run. */\n get cacheSize(): number {\n return this.cache.size;\n }\n}\n","/**\n * Edge extractors — produce typed `EdgeInput[]` rows for the `edges`\n * table from a single `ParsedNote`.\n *\n * Phase 4 / 04-02 / GRA-04. Implements the contracts:\n * - D-02 — `extractAllEdges` unified entry: wikilink + mention +\n * frontmatter-ref + hyperlink in one parse pass.\n * - D-03 — mention: casefold + min-length 4 + word-boundary, scanned\n * only on paragraph blocks (headings + fenced code + inline\n * code + bracketed wikilink spans are pre-masked away).\n * Candidate set built once per indexer run from `note_aliases`.\n * - Pitfall 6 — frontmatter-ref two-rule heuristic:\n * (a) ANY property whose value is `[[...]]` syntax →\n * resolve via `WikilinkResolver`; `rel` = property name.\n * (b) Allowlisted property names (closed set of 8) whose\n * value is a bare string → resolve against\n * `note_aliases` only.\n *\n * Source-neutral by construction: zero imports of `fs`, `path`,\n * `chokidar`, or `gray-matter`. CI `scripts/lint-adapters.sh` verifies\n * this on every push (rule I-2). All inputs flow through the\n * already-parsed `ParsedNote` shape produced by the obsidian-fs\n * adapter (Phase 1 seam).\n *\n * RESEARCH.md §\"Code Examples\" lines 580–656 spell out the algorithms;\n * `` in `04-02-edge-extractors-PLAN.md` pins the exact\n * function signatures + the `FRONTMATTER_REF_ALLOWLIST` constant.\n *\n * Idempotency: re-extracting the same note yields the same `EdgeInput[]`\n * (order-stable; mention candidates sorted by `alias_norm` ASC inside\n * `db.aliases.listAll()`). The DB layer's `UNIQUE INDEX` on\n * `(source_doc, target_doc, type, anchor)` + `INSERT OR IGNORE` makes\n * the write side idempotent independently — see `src/db/queries/edges.ts`.\n */\n\nimport type { ParsedNote } from \"../types.js\";\nimport type { EdgeInput } from \"../db/queries/edges.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { WikilinkResolver } from \"./resolver.js\";\n\n// ───────────────────────────────────────────────────────────────────────────\n// constants (D-03 + Pitfall 6)\n// ───────────────────────────────────────────────────────────────────────────\n\n/**\n * Minimum casefolded alias length eligible for mention extraction.\n *\n * D-03 fixes this at 4 to block pronoun / acronym noise (\"the\", \"API\",\n * \"you\"). RESEARCH §Pitfall 2 + A1 carry the empirical reasoning; if\n * false positives exceed 3/note on the Atlas fixture, the plan\n * §verification step raises it to 5.\n */\nexport const MIN_MENTION_LEN = 4 as const;\n\n/**\n * Closed allowlist of frontmatter property names whose bare-string\n * values are resolved against `note_aliases` (Pitfall 6 rule (b)).\n *\n * Sealed at the **type level** via `ReadonlySet` — the TS\n * compiler rejects `.add()` at any call site without an explicit\n * cast. Runtime sealing via `Object.freeze` is intentionally avoided:\n * it is a no-op on the internal slot Set uses for its entries, so it\n * gives a false sense of immutability. The closed-set property is a\n * *compile-time* invariant; widening this set requires an ADR plus a\n * matching update to the threat-model mitigations T-04-02-01 +\n * T-04-02-02 (over-activation / private-term over-matching).\n */\nexport const FRONTMATTER_REF_ALLOWLIST: ReadonlySet = new Set([\n \"assignee\",\n \"owner\",\n \"project\",\n \"related\",\n \"parent\",\n \"child\",\n \"attendees\",\n \"superseded_by\",\n]);\n\n// ───────────────────────────────────────────────────────────────────────────\n// entry point — D-02\n// ───────────────────────────────────────────────────────────────────────────\n\n/**\n * Run all four extractors on a single parsed note. No cross-type\n * dedup — the UNIQUE index on `edges` handles row-level idempotency\n * (Pattern C from PATTERNS.md).\n *\n * Order:\n * 1. wikilink (delegates to `extractWikilinkEdges` — same shape as\n * the legacy `insertWikilinks` helper produces, just reshaped\n * to `EdgeInput`)\n * 2. mention\n * 3. frontmatter-ref\n * 4. hyperlink\n *\n * Stable order matters for snapshot tests downstream (Plan 04-04\n * cluster output) and for the per-note edge dump used in\n * `` empirical validation.\n */\nexport function extractAllEdges(\n vault: Vault,\n parsed: ParsedNote,\n resolver: WikilinkResolver,\n): EdgeInput[] {\n return [\n ...extractWikilinkEdges(parsed, resolver),\n ...extractMentionEdges(parsed, vault),\n ...extractFrontmatterRefEdges(parsed, vault, resolver),\n ...extractHyperlinkEdges(parsed),\n ];\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// wikilink extractor\n// ───────────────────────────────────────────────────────────────────────────\n\n/**\n * Reshape `parsed.wikilinks` (produced by the parser's\n * `extractWikilinks` + `extractFrontmatterWikilinks`) into typed\n * `EdgeInput` rows. Resolution uses the long-lived `WikilinkResolver`\n * to amortize prepared-statement cost across a full indexer run.\n *\n * Per D-01, the legacy `wikilinks` table also receives these rows\n * via the existing `insertWikilinks` helper in `single.ts` /\n * `indexer.ts`. This function only adds the `edges` side; the\n * indexer write path stays a dual-write until v3 retires `wikilinks`.\n */\nexport function extractWikilinkEdges(parsed: ParsedNote, resolver: WikilinkResolver): EdgeInput[] {\n const out: EdgeInput[] = [];\n for (const wl of parsed.wikilinks) {\n const hit = resolver.resolve(wl.normalizedTarget);\n out.push({\n targetNoteId: hit?.id ?? null,\n targetPath: wl.normalizedTarget,\n type: \"wikilink\",\n rel: null,\n anchor: wl.anchor,\n lineNumber: wl.line,\n linkText: wl.alias,\n });\n }\n return out;\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// mention extractor — D-03\n// ───────────────────────────────────────────────────────────────────────────\n\ninterface MentionCandidate {\n noteId: number;\n path: string;\n}\n\n/**\n * Per-note mention extraction.\n *\n * Algorithm (RESEARCH lines 581–611):\n * 1. Build candidate set from `note_aliases` — casefold each alias\n * and skip if length < MIN_MENTION_LEN. (T-04-02-04 mitigation:\n * `db.aliases.listAll()` returns rows sorted by `alias_norm`\n * ASC for deterministic regex alternation.)\n * 2. Mask the note body to keep only \"paragraph\" scope:\n * - strip fenced code blocks (replace contents with spaces,\n * preserving newlines so line numbers stay aligned),\n * - strip ATX heading lines,\n * - strip inline backtick code spans,\n * - strip wikilink `[[...]]` spans (those become wikilink\n * edges; the bare text after the span on the same line\n * can still match — see Test 3).\n * 3. Run `\\b(alt1|alt2|...)\\b` (casefold + Unicode-aware\n * word-boundary via lookbehind/lookahead on \\w) over the\n * masked body; for each hit, push an EdgeInput.\n * 4. Dedup by `${targetNoteId}:${lineNumber}` per RESEARCH line 609.\n */\nexport function extractMentionEdges(parsed: ParsedNote, vault: Vault): EdgeInput[] {\n const candidates = buildMentionCandidateSet(vault);\n if (candidates.size === 0) return [];\n\n const masked = maskForMentionScope(parsed.content);\n\n // Precompute line starts for O(log n) line lookup per match.\n const lineStarts = computeLineStarts(masked);\n\n // Build a single regex from the candidate set. Sorted descending by\n // length so longer aliases win greedy alternation (prevents \"alice\"\n // from masking \"alice-chen\" when both are registered).\n const alts = [...candidates.keys()]\n .sort((a, b) => b.length - a.length || a.localeCompare(b))\n .map(escapeRegex);\n // Word-boundary via character-class lookbehind/lookahead so it\n // works for aliases containing `-` and `_` (which \\w does match).\n // We use `(?();\n const out: EdgeInput[] = [];\n let match: RegExpExecArray | null;\n while ((match = re.exec(masked)) !== null) {\n const lower = match[0].toLowerCase();\n const cand = candidates.get(lower);\n if (!cand) continue;\n const line = lineOf(lineStarts, match.index);\n const key = `${cand.noteId}:${line}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push({\n targetNoteId: cand.noteId,\n targetPath: cand.path,\n type: \"mention\",\n rel: null,\n anchor: null,\n lineNumber: line,\n linkText: null,\n });\n }\n return out;\n}\n\nfunction buildMentionCandidateSet(vault: Vault): Map {\n const out = new Map();\n for (const row of vault.db.aliases.listAll()) {\n const norm = row.alias_norm;\n if (norm.length < MIN_MENTION_LEN) continue;\n // First-seen-wins so the deterministic `alias_norm ASC` order\n // from listAll() decides ties.\n if (!out.has(norm)) {\n out.set(norm, { noteId: row.note_id, path: row.path });\n }\n }\n return out;\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// frontmatter-ref extractor — Pitfall 6\n// ───────────────────────────────────────────────────────────────────────────\n\nconst WIKILINK_SHAPED = /^\\s*\\[\\[([^\\]]+)\\]\\]\\s*$/;\n\n/**\n * Recursive frontmatter walker — emits one edge per matched value.\n *\n * Rule (a): wikilink-shaped property value at ANY depth → resolver\n * lookup. `rel` carries the TOP-LEVEL property name (not the dotted\n * path — RESEARCH +interfaces both treat `attendees: [\"[[X]]\"]` as\n * `rel='attendees'` for every array element; this matches the plan's\n * Test 9 expectation and aligns with how Plan 04-03's `expand()`\n * filters by `rel`).\n *\n * Rule (b): top-level property name in the closed 8-key allowlist\n * with a bare-string value → resolve against `note_aliases` only.\n * Sub-arrays of bare strings on allowlisted keys are also resolved\n * (e.g. `attendees: [\"alice-chen\", \"bob-martinez\"]` — each element\n * goes through the alias resolver).\n *\n * Rule (a) takes precedence over (b) for a given value: a value\n * that's `[[...]]` shaped never falls through to alias-only\n * resolution.\n */\nexport function extractFrontmatterRefEdges(\n parsed: ParsedNote,\n vault: Vault,\n resolver: WikilinkResolver,\n): EdgeInput[] {\n const fm = parsed.frontmatter;\n if (!fm) return [];\n\n const out: EdgeInput[] = [];\n\n for (const [key, value] of Object.entries(fm)) {\n if (key === \"aliases\" || key === \"alias\") continue;\n collectFrontmatterRefsForKey(key, value, vault, resolver, out);\n }\n return out;\n}\n\nfunction collectFrontmatterRefsForKey(\n key: string,\n value: unknown,\n vault: Vault,\n resolver: WikilinkResolver,\n out: EdgeInput[],\n): void {\n // Array → recurse per element with same `key`.\n if (Array.isArray(value)) {\n for (const item of value) {\n collectFrontmatterRefsForKey(key, item, vault, resolver, out);\n }\n return;\n }\n // Plain string — try rule (a) first, then rule (b) if allowlisted.\n if (typeof value === \"string\") {\n // Rule (a) — wikilink syntax. Fires for ANY key.\n const wl = WIKILINK_SHAPED.exec(value);\n if (wl !== null) {\n const inner = wl[1];\n if (inner !== undefined) {\n // Strip alias / anchor parts mirroring the body wikilink parser.\n const normalized = normalizeWikilinkInner(inner);\n if (normalized.length > 0) {\n const hit = resolver.resolve(normalized);\n if (hit) {\n out.push({\n targetNoteId: hit.id,\n targetPath: normalized,\n type: \"frontmatter-ref\",\n rel: key,\n anchor: null,\n lineNumber: null,\n linkText: null,\n });\n }\n }\n }\n return;\n }\n // Rule (b) — closed allowlist; alias-only resolution.\n if (FRONTMATTER_REF_ALLOWLIST.has(key)) {\n const aliasHit = vault.db.aliases.resolve(value);\n if (aliasHit) {\n out.push({\n targetNoteId: aliasHit.note_id,\n targetPath: aliasHit.path,\n type: \"frontmatter-ref\",\n rel: key,\n anchor: null,\n lineNumber: null,\n linkText: null,\n });\n }\n }\n return;\n }\n // Nested object — recurse, but carry the TOP-LEVEL key forward\n // (consistent with the array case + the plan's Test 9 expectation).\n if (value !== null && typeof value === \"object\") {\n for (const v of Object.values(value as Record)) {\n collectFrontmatterRefsForKey(key, v, vault, resolver, out);\n }\n }\n}\n\nfunction normalizeWikilinkInner(inner: string): string {\n // Strip `|alias` and `#anchor` suffixes; trim; drop trailing `.md`.\n let s = inner;\n const pipe = s.indexOf(\"|\");\n if (pipe >= 0) s = s.slice(0, pipe);\n const hash = s.indexOf(\"#\");\n if (hash >= 0) s = s.slice(0, hash);\n s = s.trim().replace(/\\\\/g, \"/\").replace(/\\.md$/i, \"\");\n return s;\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// hyperlink extractor\n// ───────────────────────────────────────────────────────────────────────────\n\nconst MD_LINK_RE = /(!?)\\[(?:[^\\]]*?)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g;\nconst BARE_URL_RE = /(?();\n const out: EdgeInput[] = [];\n\n MD_LINK_RE.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = MD_LINK_RE.exec(masked)) !== null) {\n const url = m[2];\n if (url === undefined) continue;\n const line = lineOf(lineStarts, m.index);\n const cleaned = stripTrailingPunctuation(url);\n pushHyperlinkEdge(out, seen, cleaned, line);\n }\n\n BARE_URL_RE.lastIndex = 0;\n while ((m = BARE_URL_RE.exec(masked)) !== null) {\n const raw = m[0];\n const line = lineOf(lineStarts, m.index);\n const cleaned = stripTrailingPunctuation(raw);\n pushHyperlinkEdge(out, seen, cleaned, line);\n }\n\n return out;\n}\n\nfunction pushHyperlinkEdge(out: EdgeInput[], seen: Set, url: string, line: number): void {\n const key = `${url}:${line}`;\n if (seen.has(key)) return;\n seen.add(key);\n out.push({\n targetNoteId: null,\n targetPath: url,\n type: \"hyperlink\",\n rel: null,\n anchor: null,\n lineNumber: line,\n linkText: null,\n });\n}\n\nfunction stripTrailingPunctuation(url: string): string {\n // Trim common terminator punctuation that authors append immediately\n // after a URL (\"...see https://example.com.\"). Keeps trailing slashes\n // and intentional fragments / queries intact.\n return url.replace(/[.,;:!?]+$/, \"\");\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// shared masking — keep mention + hyperlink scope to \"paragraph-like\"\n// regions: no headings, no fenced code, no inline code, no [[wikilink]]\n// spans. Newlines and offsets are preserved so line lookups stay valid.\n// ───────────────────────────────────────────────────────────────────────────\n\nfunction maskForMentionScope(content: string): string {\n const lines = content.split(\"\\n\");\n const out: string[] = [];\n\n let inFence = false;\n let fenceMarker = \"\";\n for (const line of lines) {\n const trimmed = line.trimStart();\n if (!inFence) {\n const fenceOpen = /^(`{3,}|~{3,})/.exec(trimmed);\n if (fenceOpen !== null && fenceOpen[1] !== undefined) {\n inFence = true;\n fenceMarker = fenceOpen[1][0] ?? \"`\";\n out.push(blankLine(line));\n continue;\n }\n } else {\n const fenceClose = /^(`{3,}|~{3,})\\s*$/.exec(trimmed);\n if (fenceClose !== null && fenceClose[1] !== undefined && fenceClose[1][0] === fenceMarker) {\n inFence = false;\n out.push(blankLine(line));\n continue;\n }\n out.push(blankLine(line));\n continue;\n }\n // ATX heading lines: mask entirely. (Setext headings are rare in\n // Obsidian vaults and v1 wikilink extraction did not special-case\n // them either — leaving them in scope is consistent.)\n if (/^\\s{0,3}#{1,6}\\s/.test(line)) {\n out.push(blankLine(line));\n continue;\n }\n // Mask inline code spans + bracketed wikilinks within the line.\n let lineOut = line;\n lineOut = maskRanges(lineOut, /`[^`\\n]*`/g);\n lineOut = maskRanges(lineOut, /\\[\\[[^\\[\\]\\n]+\\]\\]/g);\n out.push(lineOut);\n }\n\n return out.join(\"\\n\");\n}\n\nfunction blankLine(line: string): string {\n // Preserve length so byte offsets / line numbers are stable.\n return \" \".repeat(line.length);\n}\n\nfunction maskRanges(line: string, re: RegExp): string {\n let result = \"\";\n let last = 0;\n re.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(line)) !== null) {\n result += line.slice(last, m.index);\n result += \" \".repeat(m[0].length);\n last = m.index + m[0].length;\n }\n result += line.slice(last);\n return result;\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// line-lookup helpers (mirrors the obsidian-fs parser idiom)\n// ───────────────────────────────────────────────────────────────────────────\n\nfunction computeLineStarts(content: string): number[] {\n const starts: number[] = [0];\n for (let i = 0; i < content.length; i++) {\n if (content[i] === \"\\n\") starts.push(i + 1);\n }\n return starts;\n}\n\nfunction lineOf(lineStarts: number[], offset: number): number {\n // Largest lineStart <= offset, 1-based.\n let lo = 0;\n let hi = lineStarts.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n const v = lineStarts[mid];\n if (v !== undefined && v <= offset) lo = mid;\n else hi = mid - 1;\n }\n return lo + 1;\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[\\\\^$.*+?()[\\]{}|]/g, \"\\\\$&\");\n}\n","/**\n * Phase 3 — `src/sections/` barrel.\n *\n * Re-exports the section-identity surface that the indexer, the\n * assembly layer (`src/assembly/` — landing in Phase 3 slices\n * 03-02..03-04), and downstream consumers depend on.\n *\n * Adapter-seam discipline (per 03-CONTEXT.md, enforced by\n * `scripts/lint-adapters.sh`): nothing under `src/sections/` imports\n * `fs`, `gray-matter`, `chokidar`, `path.join`, or `path.resolve`.\n */\n\nexport { computeAnchor, blockToPlainText } from \"./anchor.js\";\nexport { extractSections, markdownToSectionBlocks } from \"./extract.js\";\nexport { backfillSectionsFromChunks } from \"./backfill.js\";\nexport type { SectionInfo, SectionRow, InsertSectionRow } from \"../types.js\";\n","/**\n * Index Builder — orchestrates Reader → Chunker → Ollama → DB.\n *\n * Two modes:\n * - full: wipe chunks/embeddings/wikilinks, re-index everything\n * - incremental: only re-index notes whose hash changed (default)\n *\n * Returns run statistics.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { scanVault } from \"../adapters/source/obsidian-fs/scanner.js\";\nimport { parseNote } from \"../adapters/source/obsidian-fs/parser.js\";\nimport { chunkNote } from \"../chunker/index.js\";\nimport { computeChunkIdFragment } from \"../chunker/chunk-id.js\";\nimport { OllamaClient } from \"../ollama/index.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type {\n ChunkRow,\n InsertSectionRow,\n ParsedNote,\n ParsedWikilink,\n SectionInfo,\n} from \"../types.js\";\nimport { WikilinkResolver } from \"./resolver.js\";\nimport { extractAllEdges } from \"./extract-edges.js\";\nimport { extractSections, markdownToSectionBlocks } from \"../sections/index.js\";\nimport { extractHeadings } from \"../chunker/headings.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\nexport interface IndexerOptions {\n mode?: \"full\" | \"incremental\";\n embeddingModel: string;\n /** Phase 7c: optional secondary (shadow) embedding model. When set, every\n * chunk is embedded with BOTH the primary and the secondary model in\n * parallel. Stored in separate `embeddings_` tables so search and\n * the active model are unaffected. Used by `/setup-memory-system` and\n * the watcher to keep a shadow index live for a future model switch. */\n secondaryEmbeddingModel?: string;\n /** Ollama client. Required when `embeddings !== \"none\"`. ContextFit-backed\n * vaults pass `embeddings: \"none\"` and may omit this (no Ollama needed). */\n ollama?: OllamaClient;\n /** ADR-008: embedding strategy.\n * - \"ollama\" (default): build the full SQLite layer AND embed chunks into\n * sqlite-vec (the classic path).\n * - \"none\": build the full SQLite content layer (notes, chunks, sections,\n * wikilinks, edges, audit) but SKIP embedding + model registration. Used\n * by ContextFit vaults, whose search runs through the ContextFit engine —\n * the SQLite layer still powers graph/sections/frontmatter/stats tools. */\n embeddings?: \"ollama\" | \"none\";\n /** Called periodically with progress info. */\n onProgress?: (msg: string) => void;\n}\n\nexport interface IndexRunResult {\n runId: string;\n status: \"completed\" | \"failed\";\n notesIndexed: number;\n notesUpdated: number;\n notesDeleted: number;\n notesSkipped: number;\n chunksCreated: number;\n durationMs: number;\n error?: string;\n}\n\nexport async function indexVault(vault: Vault, options: IndexerOptions): Promise {\n const startedAt = Date.now();\n const runId = randomUUID();\n const mode = options.mode ?? \"incremental\";\n const log = options.onProgress ?? (() => {});\n // ADR-008: \"none\" builds the SQLite content layer but skips embedding +\n // model registration (ContextFit vaults). \"ollama\" is the classic path.\n const embedMode = options.embeddings ?? \"ollama\";\n const ollama = options.ollama;\n\n // 1. Resolve / upsert model in DB — ONLY for the Ollama embedding path.\n // ContextFit vaults register no model and need no Ollama at all.\n let dim = 0;\n let modelRow: { id: number } | null = null;\n let secondaryModelRow: { id: number; dim: number } | null = null;\n\n if (embedMode === \"ollama\") {\n if (!ollama) {\n throw new Error(\"indexVault: embeddings='ollama' requires an OllamaClient (options.ollama).\");\n }\n log(`Probing Ollama model: ${options.embeddingModel}`);\n const health = await ollama.healthCheck();\n if (!health.ok) {\n throw new Error(`Ollama unreachable: ${health.error ?? \"unknown error\"}`);\n }\n const modelExists = await ollama.modelExists(options.embeddingModel);\n if (!modelExists) {\n throw new Error(\n `Embedding model \"${options.embeddingModel}\" not found in Ollama. ` +\n `Available: ${health.models?.join(\", \") ?? \"(none)\"}. ` +\n `Run: ollama pull ${options.embeddingModel}`,\n );\n }\n\n // Probe dim with a 1-text embed (cheap)\n const probe = await ollama.embed({\n model: options.embeddingModel,\n texts: [\"probe\"],\n });\n dim = probe.dim;\n modelRow = vault.db.models.upsert({\n name: options.embeddingModel,\n provider: \"ollama\",\n dim,\n });\n\n // Phase 7c: secondary (shadow) model registration. We probe + upsert with\n // active=false so the primary stays active. Probing also fails fast if the\n // model isn't pulled — better than discovering that mid-run on note 5000.\n if (options.secondaryEmbeddingModel) {\n const secName = options.secondaryEmbeddingModel;\n log(`Probing secondary (shadow) model: ${secName}`);\n const secExists = await ollama.modelExists(secName);\n if (!secExists) {\n throw new Error(\n `Secondary embedding model \"${secName}\" not found in Ollama. ` +\n `Run: ollama pull ${secName}`,\n );\n }\n const secProbe = await ollama.embed({\n model: secName,\n texts: [\"probe\"],\n });\n const row = vault.db.models.upsert({\n name: secName,\n provider: \"ollama\",\n dim: secProbe.dim,\n active: false,\n });\n secondaryModelRow = { id: row.id, dim: row.dim };\n }\n }\n\n vault.db.audit.startRun({\n runId,\n vaultName: vault.config.name,\n modelId: modelRow?.id ?? null,\n trigger: mode === \"full\" ? \"manual-full\" : \"manual-incremental\",\n });\n\n let notesIndexed = 0;\n let notesUpdated = 0;\n let notesDeleted = 0;\n let notesSkipped = 0;\n let chunksCreated = 0;\n\n // Per-run resolver: prepared statements reused, results memoised.\n // First pass uses this. Second pass (after all notes are inserted) uses\n // a fresh instance so newly-visible notes aren't masked by stale \"null\"\n // cache entries from the first pass.\n const firstPassResolver = new WikilinkResolver(vault);\n\n try {\n // 2. Full mode: clear derived layer\n if (mode === \"full\") {\n log(\"Full mode: clearing existing chunks and embeddings\");\n // Cascade via FK: deleting notes wipes chunks/embeddings/wikilinks.\n // But we want to keep notes (and re-upsert) — so we clear chunks only.\n vault.db.transaction(() => {\n const allNotes = vault.db.notes.listAll();\n for (const n of allNotes) {\n vault.db.chunks.deleteByNote(n.id);\n vault.db.wikilinks.deleteByNote(n.id);\n // Phase 4 / 04-01 (D-01): dual-write mirror.\n vault.db.edges.deleteByNote(n.id);\n }\n });\n }\n\n // 3. Scan vault\n log(`Scanning ${vault.config.path}`);\n const files = await scanVault(vault.config.path, {\n excludeGlobs: vault.config.exclude_globs,\n });\n log(`Found ${files.length} markdown files`);\n\n // 4. Parse + decide per-note\n const parsedNotes: Array<{ parsed: ParsedNote; noteId: number; needsReindex: boolean }> = [];\n\n for (const file of files) {\n let parsed: ParsedNote;\n try {\n parsed = await parseNote(file, vault.config.path);\n } catch (err) {\n // Robustheit gegen invalides Frontmatter / kaputte Notes:\n // skip + log statt Vault-Abort. User-Notes sind nicht unser Vertrag.\n notesSkipped++;\n const msg = err instanceof Error ? err.message.split(\"\\n\")[0] : String(err);\n const rel = file.startsWith(vault.config.path)\n ? file.slice(vault.config.path.length + 1)\n : file;\n log(` skipped (parse error): ${rel} — ${msg}`);\n continue;\n }\n // Issue #14 / P1: read the PRE-upsert state so we can make a correct\n // re-index decision. `upsertByPath` mutates notes.hash/content in place,\n // so we must capture the previous hashes BEFORE it runs — otherwise a\n // changed body keeps stale chunks/embeddings/sections/edges (the bug).\n // Mirrors the 3-way decision in `src/indexer/single.ts`:\n // - hash unchanged → no re-embed (metadata maintenance only)\n // - body_hash unchanged → frontmatter-only edit: keep chunks\n // - body changed / NULL body_hash → full re-embed\n const previous = vault.db.notes.getByPath(parsed.relativePath);\n const hashUnchanged = previous != null && previous.hash === parsed.hash;\n const bodyUnchanged =\n previous != null &&\n previous.body_hash != null &&\n previous.body_hash === parsed.bodyHash;\n\n const upsert = vault.db.notes.upsertByPath({\n path: parsed.relativePath,\n content: parsed.content,\n frontmatter: parsed.frontmatter ? JSON.stringify(parsed.frontmatter) : null,\n title: parsed.title,\n hash: parsed.hash,\n bodyHash: parsed.bodyHash,\n mtime: parsed.mtime,\n wordCount: parsed.wordCount,\n });\n\n // Phase 3 / 03-01 (M4): maintain the denormalized notes.status\n // column in sync with the frontmatter on every write. The\n // migration-time backfill populates this for existing notes;\n // this call keeps it correct for new writes and re-indexes.\n // Done unconditionally (every run, not just on reindex) so an\n // alias-only frontmatter edit that flips status also propagates.\n vault.db.notes.setStatus(upsert.id, extractStatus(parsed.frontmatter));\n\n // Persist aliases from frontmatter. We do this every run (not just on\n // reindex) so alias-only frontmatter edits propagate even when the body\n // is unchanged. The set is idempotent: setForNote does delete+insert.\n vault.db.aliases.setForNote(upsert.id, extractAliases(parsed.frontmatter));\n\n // A note with zero chunks still needs (re-)indexing even if its hash\n // matched — e.g. a previous run inserted the row but crashed before\n // chunking, or a legacy row predates the chunk layer.\n const chunkCount = vault.db.chunks.getByNote(upsert.id).length;\n\n // Full re-embed when: full mode, brand-new note, no chunks yet, OR the\n // body actually changed (hash differs AND body_hash differs / is NULL).\n const bodyChanged = !hashUnchanged && !bodyUnchanged;\n const needsReindex =\n mode === \"full\" || upsert.isNew || chunkCount === 0 || bodyChanged;\n\n // Frontmatter-only edit (hash changed, body identical): the note row +\n // status + aliases are already updated above. We must ALSO refresh\n // wikilinks + typed edges (frontmatter can hold wikilink-shaped refs\n // like `owner: \"[[X]]\"`), but we KEEP chunks/embeddings/sections —\n // no Ollama roundtrip. Mirrors single.ts step 4b.\n const frontmatterOnly = !upsert.isNew && !needsReindex && !hashUnchanged;\n\n if (upsert.isNew) notesIndexed++;\n else if (needsReindex || frontmatterOnly) notesUpdated++;\n\n if (needsReindex) {\n parsedNotes.push({ parsed, noteId: upsert.id, needsReindex: true });\n } else if (frontmatterOnly) {\n vault.db.wikilinks.deleteByNote(upsert.id);\n vault.db.edges.deleteByNote(upsert.id);\n insertWikilinks(vault, upsert.id, parsed.wikilinks, firstPassResolver);\n writeAllEdges(vault, upsert.id, parsed, firstPassResolver);\n }\n }\n\n log(`${parsedNotes.length} notes need (re-)indexing`);\n\n // 5. Chunk + embed + persist\n for (const { parsed, noteId } of parsedNotes) {\n // Clear derived layer for this note. Sections FIRST — sections\n // reference chunks via chunk_id_first/last with no ON DELETE cascade,\n // so deleting chunks while sections still point at them trips a\n // FOREIGN KEY constraint. Ordering matches single.ts step 7. (Before\n // Issue #14's P1 fix this path only ran for brand-new notes, which\n // have no sections yet — so the wrong order never surfaced. It does\n // now that changed notes are correctly re-indexed.)\n vault.db.sections.deleteByNote(noteId);\n vault.db.chunks.deleteByNote(noteId);\n vault.db.wikilinks.deleteByNote(noteId);\n // Phase 4 / 04-01 (D-01): dual-write mirror.\n vault.db.edges.deleteByNote(noteId);\n\n const chunks = chunkNote(parsed.indexedContent);\n\n if (chunks.length === 0) {\n // empty note — record wikilinks anyway, but no chunks/embeddings\n insertWikilinks(vault, noteId, parsed.wikilinks, firstPassResolver);\n // Phase 4 / 04-02 / GRA-04 / D-02: also emit typed edges for\n // frontmatter-ref / hyperlink / mention. A note with only\n // frontmatter (no body) can still contribute owner / attendees\n // edges to the graph.\n writeAllEdges(vault, noteId, parsed, firstPassResolver);\n continue;\n }\n\n // Insert chunks first to get IDs.\n // Phase 5 / D-05: compute chunk_id_fragment via the canonical\n // helper (`src/chunker/chunk-id.ts`) at every insert path.\n // Scattered createHash calls are an anti-pattern (RESEARCH §Pitfall 14).\n const chunkInputs = chunks.map((c) => ({\n idx: c.idx,\n text: c.text,\n headingPath: c.headingPath,\n startOffset: c.startOffset,\n endOffset: c.endOffset,\n tokenCount: c.tokenCount,\n chunkIdFragment: computeChunkIdFragment(c.text),\n }));\n const chunkIds = vault.db.chunks.insertBatch(noteId, chunkInputs);\n\n // Phase 3 / 03-01: extract + persist sections. Runs AFTER chunks\n // are inserted so chunk IDs exist (sections.chunk_id_first/last\n // reference chunks.id). The chunk-to-section binning uses the\n // chunker's start_offset to find each chunk's owning heading\n // region. Sections of a heading with no body content get\n // chunk_id_first = chunk_id_last = NULL.\n // Defensive: section building must never abort the whole vault index\n // because of one pathological note. The duplicate-anchor crash is\n // handled at the insert layer (insertOneResolving); this catch covers\n // any other unexpected failure — log and continue with the rest of the\n // vault (see ISSUE-indexer-duplicate-anchor.md \"Notes for the agent\").\n try {\n buildSectionsForNote(vault, noteId, parsed.indexedContent, chunkIds);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `[indexer:${vault.config.name}] section build failed for ${parsed.relativePath}: ${message} — skipping sections for this note`,\n );\n }\n\n // Embed — ONLY in the Ollama path. ContextFit vaults (embedMode \"none\")\n // skip this entirely: chunks + sections + links + edges are persisted\n // above; search runs through the ContextFit engine, not sqlite-vec.\n if (embedMode === \"ollama\") {\n const embedResult = await ollama!.embed({\n model: options.embeddingModel,\n texts: chunks.map((c) => c.text),\n });\n if (embedResult.dim !== dim) {\n throw new Error(`Embedding dimension mismatch: expected ${dim}, got ${embedResult.dim}`);\n }\n\n const embeddingInputs = chunkIds.map((chunkId, i) => ({\n chunkId,\n modelId: modelRow!.id,\n vector: embedResult.vectors[i]!,\n }));\n vault.db.embeddings.insertBatch(embeddingInputs);\n\n // Phase 7c: shadow-index pass. Embed each chunk a second time with\n // the secondary model and persist into its dim-specific table.\n // Independent failure surface: if secondary embed throws, the primary\n // index for this run still completes — the secondary will be retried\n // on the next index run (idempotent: LEFT JOIN in start_shadow_index).\n if (secondaryModelRow) {\n const secEmbed = await ollama!.embed({\n model: options.secondaryEmbeddingModel!,\n texts: chunks.map((c) => c.text),\n });\n if (secEmbed.dim !== secondaryModelRow.dim) {\n throw new Error(\n `Secondary embedding dimension mismatch: expected ` +\n `${secondaryModelRow.dim}, got ${secEmbed.dim}`,\n );\n }\n vault.db.embeddings.insertBatch(\n chunkIds.map((chunkId, i) => ({\n chunkId,\n modelId: secondaryModelRow!.id,\n vector: secEmbed.vectors[i]!,\n })),\n );\n }\n }\n\n // Wikilinks (v1 invariant write path — D-01)\n insertWikilinks(vault, noteId, parsed.wikilinks, firstPassResolver);\n // Phase 4 / 04-02 / GRA-04 / D-02: typed-edge unified write.\n writeAllEdges(vault, noteId, parsed, firstPassResolver);\n\n chunksCreated += chunks.length;\n }\n\n // 6. Detect deleted notes (in DB but not on disk)\n const knownPaths = new Set(files.map((f) => relativize(f, vault.config.path)));\n const dbNotes = vault.db.notes.listAll();\n for (const n of dbNotes) {\n if (!knownPaths.has(n.path)) {\n vault.db.notes.deleteByPath(n.path);\n notesDeleted++;\n }\n }\n\n // 7. Second-pass wikilink resolution.\n //\n // The first pass resolved wikilinks while notes were being inserted in\n // arbitrary order — so any link to a note that hadn't been inserted yet,\n // or any link via an alias whose owner hadn't been processed yet, was\n // marked unresolved. Now that the full notes + aliases tables exist, we\n // re-resolve broken links once. This converts \"transient broken\" links\n // (resolution-order artifact) into proper edges without re-parsing files.\n log(\"Resolving deferred wikilinks (second pass)\");\n const broken = vault.db.wikilinks.resolveBrokenLinks();\n let resolved = 0;\n const updateStmt = vault.db.handle.prepare(\n `UPDATE wikilinks SET target_note = ?\n WHERE source_note = ? AND target_path = ? AND target_note IS NULL`,\n );\n // Fresh resolver for the second pass — the notes table is now complete,\n // so first-pass \"null\" cache entries would mask newly-resolvable links.\n const secondPassResolver = new WikilinkResolver(vault);\n for (const link of broken) {\n const hit = secondPassResolver.resolve(link.targetPath);\n if (hit) {\n updateStmt.run(hit.id, link.sourceNoteId, link.targetPath);\n resolved++;\n }\n }\n if (resolved > 0) log(`Second pass resolved ${resolved} wikilinks`);\n\n vault.db.audit.finishRun(runId, {\n notesIndexed,\n chunksCreated,\n notesUpdated,\n notesDeleted,\n });\n\n if (notesSkipped > 0) {\n log(`${notesSkipped} note(s) skipped due to parse errors`);\n }\n\n return {\n runId,\n status: \"completed\",\n notesIndexed,\n notesUpdated,\n notesDeleted,\n notesSkipped,\n chunksCreated,\n durationMs: Date.now() - startedAt,\n };\n } catch (err) {\n const message = errorMessage(err);\n vault.db.audit.finishRun(runId, {\n notesIndexed,\n chunksCreated,\n notesUpdated,\n notesDeleted,\n error: message,\n });\n return {\n runId,\n status: \"failed\",\n notesIndexed,\n notesUpdated,\n notesDeleted,\n notesSkipped,\n chunksCreated,\n durationMs: Date.now() - startedAt,\n error: message,\n };\n }\n}\n\nfunction insertWikilinks(\n vault: Vault,\n sourceNoteId: number,\n wikilinks: ParsedWikilink[],\n resolver?: WikilinkResolver,\n): void {\n if (wikilinks.length === 0) return;\n\n const r = resolver ?? new WikilinkResolver(vault);\n const inputs = wikilinks.map((wl) => {\n const target = r.resolve(wl.normalizedTarget);\n return {\n targetPath: wl.normalizedTarget,\n targetNoteId: target?.id ?? null,\n linkText: wl.alias,\n anchor: wl.anchor,\n lineNumber: wl.line,\n };\n });\n vault.db.wikilinks.insertBatch(sourceNoteId, inputs);\n // Phase 4 / 04-02 / GRA-04 / D-02 — the unified edge write is no\n // longer co-located here. `writeAllEdges` (called immediately\n // after this helper at every call site) produces the full typed\n // edge mix in a single pass, sharing this same `WikilinkResolver`\n // so cache lookups are not duplicated. The legacy `wikilinks`\n // table write above stays for v1 invariance per D-01.\n}\n\n/**\n * Phase 4 / 04-02 / GRA-04 / D-02 — emit all four typed edges into\n * `vault.db.edges`. Callers MUST have already issued\n * `vault.db.edges.deleteByNote(sourceNoteId)` for a clean replace;\n * the UNIQUE index on `(source_doc, target_doc, type, anchor)` +\n * `INSERT OR IGNORE` makes the write idempotent regardless.\n *\n * The full-index path passes its long-lived `firstPassResolver` so\n * the wikilink-edge resolution shares the same cache as the\n * frontmatter-ref rule-(a) lookups. Plan 04-01's second-pass broken-\n * link resolver (`secondPassResolver`) only mutates the `wikilinks`\n * table — Plan 04-03 will lift that into `edges` if needed.\n */\nfunction writeAllEdges(\n vault: Vault,\n sourceNoteId: number,\n parsed: ParsedNote,\n resolver: WikilinkResolver,\n): void {\n const edges = extractAllEdges(vault, parsed, resolver);\n if (edges.length > 0) vault.db.edges.insertBatch(sourceNoteId, edges);\n}\n\n/**\n * Resolve a wikilink target the way Obsidian does, in priority order:\n * 1) exact relative path match (with or without .md)\n * 2) filename-only match anywhere in the vault — shortest path wins\n * 3) alias match — looks up note_aliases (case-insensitive)\n *\n * Returns null if no candidate exists (true broken link).\n */\nexport function resolveWikilinkTarget(\n vault: Vault,\n normalizedTarget: string,\n): { id: number; path: string } | null {\n // API-compat wrapper. Single-call sites (e.g. single-note re-index) pay\n // the prepared-statement cost per call. The hot path (indexVault) goes\n // through a long-lived WikilinkResolver instance instead.\n return new WikilinkResolver(vault).resolve(normalizedTarget);\n}\n\n/**\n * Extract aliases from a parsed frontmatter object. Accepts the two common\n * shapes Obsidian writes:\n * aliases: [\"OWR\", \"Oliver\"]\n * alias: \"OWR\" (singular form, sometimes used)\n * aliases: \"OWR\" (string fallback)\n *\n * Anything else (numbers, objects) is ignored.\n */\nexport function extractAliases(frontmatter: Record | null): string[] {\n if (!frontmatter) return [];\n const raw = frontmatter[\"aliases\"] ?? frontmatter[\"alias\"];\n if (raw == null) return [];\n if (typeof raw === \"string\") return [raw];\n if (Array.isArray(raw)) {\n return raw.filter((v): v is string => typeof v === \"string\");\n }\n return [];\n}\n\n/**\n * Phase 3 / 03-01: extract the `status` value from a parsed\n * frontmatter object. Accepts any string value; returns null when\n * absent / non-string. The denormalized `notes.status` column is\n * read by 03-05's SQL-level superseded filter.\n */\nexport function extractStatus(frontmatter: Record | null): string | null {\n if (!frontmatter) return null;\n const raw = frontmatter[\"status\"];\n if (typeof raw === \"string\") return raw;\n return null;\n}\n\n/**\n * Phase 3 / 03-01: extract sections for a note and persist them.\n *\n * Sections are materialized from the SAME `notes.content` bytes the\n * chunker just consumed — `markdownToSectionBlocks` → `extractSections`\n * runs on the unmodified parsed body. The resulting `SectionInfo[]`\n * gets `chunk_id_first` / `chunk_id_last` filled in by walking the\n * inserted chunk IDs and binning each chunk into the section whose\n * source-offset window contains the chunk's `start_offset`.\n *\n * Sibling: `backfillSectionsFromChunks` does the same operation for\n * existing v1 notes at migration time. Both code paths run the same\n * pipeline against the same `content` bytes → identical anchors\n * (anchor-equivalence proven in\n * `src/sections/backfill.test.ts`).\n */\nexport function buildSectionsForNote(\n vault: Vault,\n noteId: number,\n content: string,\n insertedChunkIds: number[],\n): number {\n if (content.length === 0) return 0;\n const blocks = markdownToSectionBlocks(content);\n const sections = extractSections(blocks);\n if (sections.length === 0) return 0;\n\n // Hydrate just-inserted chunks so we can bin by start_offset. The\n // `getByNote` query returns chunks in `idx` order — same as\n // `insertedChunkIds`. We pass the chunk rows through to the helper\n // so the helper itself is pure (no DB dep).\n const chunkRows = vault.db.chunks.getByNote(noteId);\n // Defensive sanity: chunk count must match.\n if (chunkRows.length !== insertedChunkIds.length) {\n // This should never happen — chunkInputs went in via insertBatch\n // and we read them right back. If it does, the section ranges are\n // best-effort but the anchors are still correct.\n }\n\n const sectionRanges = computeSectionOffsetRanges(content, sections);\n const rangePairs = mapChunksToSections(chunkRows, sectionRanges);\n\n // Materialize rows: parent_id is filled in via the inserted-id map\n // (the in-memory `SectionInfo.parent_index` is an array index).\n // Duplicate-anchor sibling sections (two H2s GitHub-slugify to the same\n // anchor) would collide on UNIQUE(note_id, anchor). `insertOneResolving`\n // collapses later siblings into the first one's row and returns that\n // surviving id, so a single offending note can't abort the whole index\n // run (see ISSUE-indexer-duplicate-anchor.md). `null` is possible in\n // theory (insert ignored AND lookup miss) — mirror the backfill type.\n const insertedIds: Array = [];\n for (let i = 0; i < sections.length; i++) {\n const s = sections[i]!;\n const parentId = s.parent_index === null ? null : (insertedIds[s.parent_index] ?? null);\n const pair = rangePairs[i] ?? { first: null, last: null };\n const row: InsertSectionRow = {\n note_id: noteId,\n anchor: s.anchor,\n heading_path: JSON.stringify(s.heading_path),\n heading_text: s.heading_text,\n level: s.level,\n parent_id: parentId,\n ord: s.ord,\n chunk_id_first: pair.first,\n chunk_id_last: pair.last,\n };\n insertedIds.push(vault.db.sections.insertOneResolving(row));\n }\n return insertedIds.length;\n}\n\n/**\n * Phase 3 / 03-01: pure helper that bins each chunk into the section\n * whose source-offset range contains its `start_offset`. Returns the\n * `{first, last}` chunk-id pair per section index (in the order of\n * the `sectionRanges` array). Sections with no contained chunks get\n * `{first: null, last: null}`.\n *\n * Exported for unit testing.\n */\nexport function mapChunksToSections(\n chunks: ChunkRow[],\n sectionRanges: Array<{ start: number; end: number }>,\n): Array<{ first: number | null; last: number | null }> {\n const out: Array<{ first: number | null; last: number | null }> = sectionRanges.map(() => ({\n first: null,\n last: null,\n }));\n for (const chunk of chunks) {\n const offset = chunk.start_offset;\n let chosenIdx: number | null = null;\n // Walk in reverse so the innermost (deepest) section wins.\n for (let i = sectionRanges.length - 1; i >= 0; i--) {\n const r = sectionRanges[i];\n if (!r) continue;\n if (offset >= r.start && offset < r.end) {\n chosenIdx = i;\n break;\n }\n }\n if (chosenIdx === null) continue;\n const slot = out[chosenIdx]!;\n if (slot.first === null || chunk.id < slot.first) slot.first = chunk.id;\n if (slot.last === null || chunk.id > slot.last) slot.last = chunk.id;\n }\n return out;\n}\n\n/**\n * Compute the [start, end) byte range for each section in `content`.\n * Mirrors `src/sections/backfill.ts:computeSectionOffsetRanges`. Kept\n * here (not imported) so the indexer doesn't reach into the\n * backfill module's private surface — both implementations share\n * `src/chunker/headings.ts:extractHeadings` as the canonical heading\n * source, which is what guarantees they agree byte-for-byte.\n */\nfunction computeSectionOffsetRanges(\n content: string,\n sections: SectionInfo[],\n): Array<{ start: number; end: number }> {\n const headings = extractHeadings(content);\n const ranges: Array<{ start: number; end: number }> = [];\n const hasPreamble =\n sections.length > 0 && sections[0]!.level === 0 && sections[0]!.heading_text === \"\";\n const firstHeadingOffset = headings.length === 0 ? content.length : headings[0]!.startOffset;\n if (hasPreamble) {\n ranges.push({ start: 0, end: firstHeadingOffset });\n }\n for (let h = 0; h < headings.length; h++) {\n const h0 = headings[h]!;\n let endOffset = content.length;\n for (let j = h + 1; j < headings.length; j++) {\n if (headings[j]!.level <= h0.level) {\n endOffset = headings[j]!.startOffset;\n break;\n }\n }\n ranges.push({ start: h0.startOffset, end: endOffset });\n }\n while (ranges.length < sections.length) {\n ranges.push({ start: 0, end: content.length });\n }\n return ranges;\n}\n\nfunction relativize(absPath: string, vaultRoot: string): string {\n // Reader produces forward-slash relative paths. We must do the same here\n // so deletion detection works on all platforms.\n let p = absPath;\n if (p.startsWith(vaultRoot)) {\n p = p.slice(vaultRoot.length);\n }\n if (p.startsWith(\"/\") || p.startsWith(\"\\\\\")) {\n p = p.slice(1);\n }\n return p.split(\"\\\\\").join(\"/\");\n}\n","/**\n * Single-Note Indexer — re-index one note efficiently.\n *\n * Used by the file-watcher (Phase 4) to react to individual file events\n * without paying the full-vault setup overhead (model probe, audit run,\n * scan, second-pass wikilink resolution).\n *\n * Behavioral contract: see `indexNote` JSDoc below.\n */\n\nimport * as path from \"node:path\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { OllamaClient } from \"../ollama/index.js\";\nimport { parseNote } from \"../adapters/source/obsidian-fs/parser.js\";\nimport { chunkNote } from \"../chunker/index.js\";\nimport { computeChunkIdFragment } from \"../chunker/chunk-id.js\";\nimport { extractAliases, buildSectionsForNote } from \"./indexer.js\";\nimport { WikilinkResolver } from \"./resolver.js\";\nimport { extractAllEdges } from \"./extract-edges.js\";\nimport type { ParsedNote, ParsedWikilink } from \"../types.js\";\n\nexport interface IndexNoteOptions {\n vault: Vault;\n /** Absolute file path. Must be inside the vault. */\n absolutePath: string;\n embeddingModel: string;\n /** Phase 7c: optional secondary (shadow) model name. When set AND the\n * model is already registered in the vault DB, the watcher / single\n * indexer also writes shadow embeddings so the secondary index stays\n * current with the primary. Unregistered names are ignored silently —\n * registration only happens via a full `indexVault` run. */\n secondaryEmbeddingModel?: string;\n /** Required when `embeddings !== \"none\"`. ContextFit vaults omit it. */\n ollama?: OllamaClient;\n /** ADR-008: \"none\" builds the SQLite content layer for this note but skips\n * embedding (ContextFit vaults). Default \"ollama\". */\n embeddings?: \"ollama\" | \"none\";\n}\n\nexport interface IndexNoteResult {\n status: \"indexed\" | \"unchanged\" | \"outside_vault\" | \"missing\" | \"parse_error\";\n notePath: string | null;\n noteId: number | null;\n chunksCreated: number;\n /** True if this was a brand-new note (vs. updated existing). */\n isNew: boolean;\n}\n\n/**\n * Re-index a single note. Cheaper than a full vault scan: no model probe,\n * no audit run wrapping, no second-pass wikilink resolution (so transient\n * unresolved aliases will be flagged broken — call the full indexer if you\n * need them resolved).\n *\n * Behavior:\n * - Path outside vault → status: \"outside_vault\"\n * - File missing → status: \"missing\" (caller should call removeNote instead)\n * - File hash unchanged → status: \"unchanged\" (no-op fast path; aliases\n * are still re-applied idempotently)\n * - Otherwise → full re-index of this note: parse, chunk, embed, persist,\n * update aliases, persist wikilinks\n */\nexport async function indexNote(options: IndexNoteOptions): Promise {\n const { vault, absolutePath, embeddingModel, ollama } = options;\n const secondaryName = options.secondaryEmbeddingModel;\n\n // 1. Validate path is inside the vault.\n if (!isInsideVault(absolutePath, vault.config.path)) {\n return emptyResult(\"outside_vault\");\n }\n\n // 2. Parse — handle missing-file fast path and invalid-frontmatter skip.\n let parsed;\n try {\n parsed = await parseNote(absolutePath, vault.config.path);\n } catch (err) {\n if (isENOENT(err)) {\n return emptyResult(\"missing\");\n }\n // Invalid frontmatter or other parse failure: skip silently so a single\n // bad note doesn't kill the watcher or break the indexer mid-run. Caller\n // can inspect `status === \"parse_error\"` if it wants to log.\n return emptyResult(\"parse_error\");\n }\n\n // 3. Look up existing note for hash check.\n const existing = vault.db.notes.getByPath(parsed.relativePath);\n\n // 4. Fast path: hash unchanged → still re-apply aliases idempotently.\n if (existing && existing.hash === parsed.hash) {\n vault.db.aliases.setForNote(existing.id, extractAliases(parsed.frontmatter));\n return {\n status: \"unchanged\",\n notePath: parsed.relativePath,\n noteId: existing.id,\n chunksCreated: 0,\n isNew: false,\n };\n }\n\n // 4b. Body-hash fast path (v0.9.1): combined hash differs but body is\n // unchanged → frontmatter-only edit. Update note row + aliases, but\n // KEEP chunks/embeddings as-is. Saves an Ollama roundtrip per chunk\n // (typically 5-15 per note) on every update_frontmatter call.\n //\n // Wikilinks: extracted from BOTH body and frontmatter. Frontmatter\n // wikilinks (e.g. participation: [\"[[X]]\"]) can change with a\n // frontmatter-only edit, so we still rewrite the wikilinks index.\n //\n // NULL guard: legacy rows pre-migration-006 have body_hash=NULL.\n // `null === parsed.bodyHash` is always false, so we fall through to\n // the full re-embed path. Self-heals on next touch.\n if (existing && existing.body_hash && existing.body_hash === parsed.bodyHash) {\n const upsert = vault.db.notes.upsertByPath({\n path: parsed.relativePath,\n content: parsed.content,\n frontmatter: parsed.frontmatter ? JSON.stringify(parsed.frontmatter) : null,\n title: parsed.title,\n hash: parsed.hash,\n bodyHash: parsed.bodyHash,\n mtime: parsed.mtime,\n wordCount: parsed.wordCount,\n });\n vault.db.aliases.setForNote(upsert.id, extractAliases(parsed.frontmatter));\n vault.db.wikilinks.deleteByNote(upsert.id);\n // ── Phase 4 / 04-02 / GRA-04 / D-02 ──\n // Clear all typed edges and re-extract via the unified extractor.\n // The body-hash fast path is NOT a shortcut around edge\n // re-extraction — frontmatter-only edits (e.g. a new `owner:`\n // wikilink-shape) flip the frontmatter-ref edge mix, so we MUST\n // re-run the extractor here. The legacy wikilinks-table write\n // stays in place per D-01 (v1 invariance).\n vault.db.edges.deleteByNote(upsert.id);\n insertWikilinks(vault, upsert.id, parsed.wikilinks);\n writeAllEdges(vault, upsert.id, parsed);\n return {\n status: \"indexed\",\n notePath: parsed.relativePath,\n noteId: upsert.id,\n chunksCreated: 0,\n isNew: false,\n };\n }\n\n // ADR-008: ContextFit vaults skip embedding (embedMode \"none\") — no model,\n // no Ollama. The classic path requires a registered active model.\n const embedMode = options.embeddings ?? \"ollama\";\n let activeModel: { id: number; name: string; dim: number } | null = null;\n if (embedMode === \"ollama\") {\n if (!ollama) {\n throw new Error(\"single-indexer: embeddings='ollama' requires an OllamaClient.\");\n }\n // 5. Active model lookup + dimension contract check. The full indexer\n // upserts the model row; here we require it to already exist (caller\n // should run a full index first if not).\n const am = vault.db.models.getActive();\n if (!am) {\n throw new Error(\n `single-indexer: no active embedding model in DB. ` +\n `Run a full index first to register \"${embeddingModel}\".`,\n );\n }\n if (am.name !== embeddingModel) {\n throw new Error(\n `single-indexer: active model \"${am.name}\" does not match ` +\n `requested \"${embeddingModel}\". Run a full re-index to switch models.`,\n );\n }\n activeModel = am;\n }\n\n // 6. Upsert note row + aliases.\n const upsert = vault.db.notes.upsertByPath({\n path: parsed.relativePath,\n content: parsed.content,\n frontmatter: parsed.frontmatter ? JSON.stringify(parsed.frontmatter) : null,\n title: parsed.title,\n hash: parsed.hash,\n bodyHash: parsed.bodyHash,\n mtime: parsed.mtime,\n wordCount: parsed.wordCount,\n });\n vault.db.aliases.setForNote(upsert.id, extractAliases(parsed.frontmatter));\n\n // 7. Wipe derived layer for this note. Sections FIRST — sections reference\n // chunks via chunk_id_first/last (no ON DELETE cascade), so deleting chunks\n // while sections still point at them trips a FOREIGN KEY constraint. (This\n // ordering matches the full indexer; single-indexer historically skipped\n // section maintenance — now fixed so live re-index keeps sections correct.)\n vault.db.sections.deleteByNote(upsert.id);\n vault.db.chunks.deleteByNote(upsert.id);\n vault.db.wikilinks.deleteByNote(upsert.id);\n // ── Phase 4 / 04-02 / GRA-04 / D-02 ──\n // Wipe typed edges; writeAllEdges below repopulates them via the\n // unified extractor (wikilink + mention + frontmatter-ref +\n // hyperlink) in one parse pass. The legacy wikilinks write keeps\n // running too per D-01 — single-indexer's `insertWikilinks` helper\n // still hits the v1 table for byte-stable backward compatibility.\n vault.db.edges.deleteByNote(upsert.id);\n\n // 8. Chunk + embed + persist.\n const chunks = chunkNote(parsed.indexedContent);\n\n if (chunks.length === 0) {\n insertWikilinks(vault, upsert.id, parsed.wikilinks);\n // ── Phase 4 / 04-02 / GRA-04 / D-02 ──\n // Empty-body branch still gets the full extractor pass: a note\n // with only frontmatter (e.g. a person stub with `owner:` /\n // `attendees:` arrays) can still emit frontmatter-ref edges.\n writeAllEdges(vault, upsert.id, parsed);\n return {\n status: \"indexed\",\n notePath: parsed.relativePath,\n noteId: upsert.id,\n chunksCreated: 0,\n isNew: upsert.isNew,\n };\n }\n\n const chunkIds = vault.db.chunks.insertBatch(\n upsert.id,\n chunks.map((c) => ({\n idx: c.idx,\n text: c.text,\n headingPath: c.headingPath,\n startOffset: c.startOffset,\n endOffset: c.endOffset,\n tokenCount: c.tokenCount,\n // Phase 5 / D-05: canonical chunk-fragment via the chunker helper\n // (single source of truth — see src/chunker/chunk-id.ts).\n chunkIdFragment: computeChunkIdFragment(c.text),\n })),\n );\n\n // Rebuild this note's sections (was previously skipped by the single-indexer,\n // so live-reindexed notes silently lost their section rows). Runs for BOTH\n // backends — sections power outline/search_sections/bundle and need no\n // embeddings. Defensive try/catch: one pathological note must not break the\n // watcher (mirrors the full indexer).\n try {\n buildSectionsForNote(vault, upsert.id, parsed.indexedContent, chunkIds);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `[single-indexer:${vault.config.name}] section build failed for ${parsed.relativePath}: ${message}\\n`,\n );\n }\n\n // Embed — Ollama path only. ContextFit vaults skip; the chunks + links +\n // edges persisted here power the SQLite-backed tools, and search runs via\n // the ContextFit engine (KB re-ingested by the watcher/write path).\n if (embedMode === \"ollama\") {\n const embedResult = await ollama!.embed({\n model: embeddingModel,\n texts: chunks.map((c) => c.text),\n });\n if (embedResult.dim !== activeModel!.dim) {\n throw new Error(\n `single-indexer: embedding dim ${embedResult.dim} does not match ` +\n `registered dim ${activeModel!.dim} for model \"${embeddingModel}\".`,\n );\n }\n\n vault.db.embeddings.insertBatch(\n chunkIds.map((chunkId, i) => ({\n chunkId,\n modelId: activeModel!.id,\n vector: embedResult.vectors[i]!,\n })),\n );\n\n // Phase 7c: keep the shadow index live. Only embed if the secondary model\n // is already registered (i.e. a full indexVault run has set it up). We\n // never register a new model from a single-note path — the dim probe is\n // a full-indexer responsibility.\n if (secondaryName) {\n const secondaryModel = vault.db.models.getByName(secondaryName);\n if (secondaryModel && secondaryModel.id !== activeModel!.id) {\n const secEmbed = await ollama!.embed({\n model: secondaryName,\n texts: chunks.map((c) => c.text),\n });\n if (secEmbed.dim !== secondaryModel.dim) {\n throw new Error(\n `single-indexer: shadow embedding dim ${secEmbed.dim} ` +\n `does not match registered dim ${secondaryModel.dim} for ` +\n `\"${secondaryName}\".`,\n );\n }\n vault.db.embeddings.insertBatch(\n chunkIds.map((chunkId, i) => ({\n chunkId,\n modelId: secondaryModel.id,\n vector: secEmbed.vectors[i]!,\n })),\n );\n }\n }\n }\n\n insertWikilinks(vault, upsert.id, parsed.wikilinks);\n // ── Phase 4 / 04-02 / GRA-04 / D-02 ──\n // Full re-embed branch — emit the typed-edge mix into `edges`.\n writeAllEdges(vault, upsert.id, parsed);\n\n return {\n status: \"indexed\",\n notePath: parsed.relativePath,\n noteId: upsert.id,\n chunksCreated: chunks.length,\n isNew: upsert.isNew,\n };\n}\n\n/**\n * Remove a note from the index (note row + cascade: chunks, embeddings,\n * wikilinks, aliases). Does NOT touch the file on disk.\n */\nexport function removeNote(\n vault: Vault,\n absolutePath: string,\n): { removed: boolean; notePath: string | null } {\n if (!isInsideVault(absolutePath, vault.config.path)) {\n return { removed: false, notePath: null };\n }\n const relativePath = toRelativePosix(absolutePath, vault.config.path);\n\n const existing = vault.db.notes.getByPath(relativePath);\n if (!existing) {\n return { removed: false, notePath: null };\n }\n vault.db.notes.deleteByPath(relativePath);\n return { removed: true, notePath: relativePath };\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// helpers\n// ───────────────────────────────────────────────────────────────────────────\n\nfunction emptyResult(status: \"outside_vault\" | \"missing\" | \"parse_error\"): IndexNoteResult {\n return {\n status,\n notePath: null,\n noteId: null,\n chunksCreated: 0,\n isNew: false,\n };\n}\n\nfunction isInsideVault(absolutePath: string, vaultRoot: string): boolean {\n const absResolved = path.resolve(absolutePath);\n const rootResolved = path.resolve(vaultRoot);\n const absPosix = absResolved.split(path.sep).join(\"/\");\n const rootPosix = rootResolved.split(path.sep).join(\"/\");\n const rootWithSep = rootPosix.endsWith(\"/\") ? rootPosix : `${rootPosix}/`;\n return absPosix === rootPosix || absPosix.startsWith(rootWithSep);\n}\n\nfunction toRelativePosix(absolutePath: string, vaultRoot: string): string {\n return path\n .relative(path.resolve(vaultRoot), path.resolve(absolutePath))\n .split(path.sep)\n .join(\"/\");\n}\n\nfunction isENOENT(err: unknown): boolean {\n return (\n typeof err === \"object\" &&\n err !== null &&\n \"code\" in err &&\n (err as { code: unknown }).code === \"ENOENT\"\n );\n}\n\n/**\n * Mirror of indexer.ts `insertWikilinks` — kept private to avoid widening\n * that module's public API. Single-indexer skips the second-pass resolution,\n * so unresolved targets remain broken until a full index runs.\n *\n * Phase 4 / 04-02: the dual-write into `edges` has moved into\n * `writeAllEdges` below so the wikilinks-edge resolution shares a\n * single `WikilinkResolver` instance with the mention + frontmatter-ref\n * extractors. This helper now writes ONLY the legacy `wikilinks`\n * table (D-01 byte-stability).\n */\nfunction insertWikilinks(vault: Vault, sourceNoteId: number, wikilinks: ParsedWikilink[]): void {\n if (wikilinks.length === 0) return;\n\n const resolver = new WikilinkResolver(vault);\n const inputs = wikilinks.map((wl) => {\n const target = resolver.resolve(wl.normalizedTarget);\n return {\n targetPath: wl.normalizedTarget,\n targetNoteId: target?.id ?? null,\n linkText: wl.alias,\n anchor: wl.anchor,\n lineNumber: wl.line,\n };\n });\n vault.db.wikilinks.insertBatch(sourceNoteId, inputs);\n}\n\n/**\n * Phase 4 / 04-02 / GRA-04 / D-02 — write all four edge types into\n * `vault.db.edges` via the unified extractor. Callers MUST have\n * already issued `vault.db.edges.deleteByNote(sourceNoteId)` so the\n * write is a clean replace; `INSERT OR IGNORE` + the UNIQUE index on\n * `(source_doc, target_doc, type, anchor)` makes re-extraction\n * idempotent in any case (Pattern C from PATTERNS.md).\n *\n * Constructs a single `WikilinkResolver` per call — the single-indexer\n * path indexes one note at a time, so cache amortization across notes\n * is not relevant. The full-index path (`indexer.ts`) uses a long-lived\n * resolver instead (see `firstPassResolver` there).\n */\nfunction writeAllEdges(vault: Vault, sourceNoteId: number, parsed: ParsedNote): void {\n const resolver = new WikilinkResolver(vault);\n const edges = extractAllEdges(vault, parsed, resolver);\n if (edges.length > 0) vault.db.edges.insertBatch(sourceNoteId, edges);\n}\n","/**\n * Catch-up scan: reconcile DB state with the vault's filesystem on demand.\n *\n * Used at server start before activating the file watcher. The watcher only\n * sees events from its `start()` onward — so anything edited while the server\n * was offline would silently drift. Catch-up does a cheap hash-based scan:\n *\n * - For every .md on disk: parse + hash → compare to DB.\n * - Hash unchanged → skip (no embeddings work).\n * - Hash changed or note absent → indexNote (full re-embed for this note).\n * - For every DB note whose path is no longer on disk → removeNote.\n *\n * Embeddings are only generated for the notes that actually changed.\n */\n\nimport { scanVault } from \"../adapters/source/obsidian-fs/scanner.js\";\nimport { parseNote } from \"../adapters/source/obsidian-fs/parser.js\";\nimport { indexNote, removeNote } from \"./single.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { OllamaClient } from \"../ollama/index.js\";\n\nexport interface CatchupOptions {\n vault: Vault;\n embeddingModel: string;\n /** Required for Ollama vaults; omitted for ContextFit vaults (ADR-008). */\n ollama?: OllamaClient;\n log?: (msg: string) => void;\n}\n\nexport interface CatchupResult {\n scanned: number;\n reindexed: number;\n removed: number;\n durationMs: number;\n}\n\nexport async function catchupVault(options: CatchupOptions): Promise {\n const started = Date.now();\n const log = options.log ?? (() => {});\n const { vault } = options;\n\n const files = await scanVault(vault.config.path, {\n excludeGlobs: vault.config.exclude_globs,\n });\n\n let reindexed = 0;\n const knownPaths = new Set();\n // ADR-008: ContextFit vaults reconcile the SQLite layer without embeddings.\n const isContextFit = vault.config.backend === \"contextfit\";\n\n for (const file of files) {\n // Cheap path-relative computation — duplicates the reader's logic but\n // avoids a second filesystem hit.\n const parsed = await parseNote(file, vault.config.path).catch(() => null);\n if (!parsed) continue;\n knownPaths.add(parsed.relativePath);\n\n const dbRow = vault.db.notes.getByPath(parsed.relativePath);\n if (dbRow && dbRow.hash === parsed.hash) {\n continue;\n }\n\n const result = await indexNote({\n vault,\n absolutePath: file,\n embeddingModel: options.embeddingModel,\n ...(isContextFit ? { embeddings: \"none\" as const } : { ollama: options.ollama }),\n });\n if (result.status === \"indexed\") {\n reindexed++;\n log(`catch-up indexed ${parsed.relativePath} (${result.isNew ? \"new\" : \"updated\"})`);\n }\n }\n\n let removed = 0;\n for (const row of vault.db.notes.listAll()) {\n if (!knownPaths.has(row.path)) {\n const result = removeNote(vault, joinAbs(vault.config.path, row.path));\n if (result.removed) {\n removed++;\n log(`catch-up removed ${row.path}`);\n }\n }\n }\n\n // ADR-008: if a ContextFit vault changed during catch-up, rebuild its search\n // KB once so retrieval matches the reconciled SQLite layer.\n if (isContextFit && (reindexed > 0 || removed > 0)) {\n const { indexVaultWithContextFit } = await import(\"../adapters/retrieval/contextfit/index.js\");\n const r = await indexVaultWithContextFit(vault.config, { onProgress: log });\n log(\n r.status === \"completed\"\n ? `catch-up: ContextFit KB rebuilt (${r.durationMs}ms)`\n : `catch-up: ContextFit KB rebuild failed: ${r.error}`,\n );\n }\n\n return {\n scanned: files.length,\n reindexed,\n removed,\n durationMs: Date.now() - started,\n };\n}\n\nfunction joinAbs(root: string, relative: string): string {\n // removeNote expects absolute. The simple join here mirrors scanVault's\n // output convention (POSIX slashes) and works on macOS/Linux; on Windows\n // single.ts's safeJoinInsideVault normalizes either way.\n if (root.endsWith(\"/\")) return `${root}${relative}`;\n return `${root}/${relative}`;\n}\n","/**\n * Shadow indexer (Phase 7c) — backfills embeddings for a secondary model\n * over chunks that already exist in the vault DB.\n *\n * Use case: a user runs vault-memory v0.6.x with model A. They want to test\n * model B's retrieval quality. Instead of destructively re-indexing (which\n * would break search while it runs), they kick off a shadow index:\n *\n * 1. Every chunk in the `chunks` table is embedded with model B.\n * 2. Vectors land in `embeddings_` next to the existing `embeddings_`.\n * 3. While running, model A stays active — search is uninterrupted.\n * 4. Once complete, `switch_active_model` flips the active flag atomically.\n *\n * Idempotent: a LEFT JOIN against the secondary dim's embeddings table\n * skips chunks that are already embedded. Safe to interrupt and resume.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { OllamaClient } from \"../ollama/index.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\nexport interface ShadowIndexOptions {\n vault: Vault;\n /** Secondary model name. Registered on demand if not yet in the DB. */\n model: string;\n ollama: OllamaClient;\n /** Embed batch size — capped at Ollama batch size in practice. Default 16. */\n batchSize?: number;\n log?: (msg: string) => void;\n}\n\nexport interface ShadowIndexResult {\n runId: string;\n modelId: number;\n modelName: string;\n dim: number;\n chunksTotal: number;\n chunksEmbedded: number;\n chunksSkipped: number;\n durationMs: number;\n}\n\ninterface PendingChunkRow {\n id: number;\n text: string;\n}\n\n/**\n * Backfill secondary embeddings for every chunk currently in the vault.\n * Skips chunks already embedded with this model (idempotent resume).\n *\n * Does NOT switch the active model. Use `switch_active_model` once the\n * caller has independently verified the shadow index is complete.\n */\nexport async function startShadowIndex(options: ShadowIndexOptions): Promise {\n const { vault, model, ollama } = options;\n const log = options.log ?? (() => {});\n const batchSize = options.batchSize ?? 16;\n const runId = randomUUID();\n const started = Date.now();\n\n // 1. Probe Ollama for dim + existence. Fail fast if the model isn't pulled.\n if (!(await ollama.modelExists(model))) {\n throw new Error(`Shadow model \"${model}\" not found in Ollama. ` + `Run: ollama pull ${model}`);\n }\n const probe = await ollama.embed({ model, texts: [\"probe\"] });\n const dim = probe.dim;\n\n // 2. Register the model (active=false — primary stays active).\n const modelRow = vault.db.models.upsert({\n name: model,\n provider: \"ollama\",\n dim,\n active: false,\n });\n\n // The vec0 table for this model is created lazily by ensureTableForModel.\n vault.db.embeddings.ensureTableForModel(modelRow.id, dim);\n\n // 3. Audit run.\n vault.db.audit.startRun({\n runId,\n vaultName: vault.config.name,\n modelId: modelRow.id,\n trigger: \"shadow\",\n });\n\n // 4. Find chunks missing the shadow embedding.\n //\n // Phase 7e: each model owns its own vec0 table `embeddings_m_d`.\n // The table name is interpolated from validated integers — safe.\n const embTable = `embeddings_m${modelRow.id}_d${dim}`;\n const pendingSql = `\n SELECT c.id AS id, c.text AS text\n FROM chunks c\n LEFT JOIN ${embTable} e ON e.chunk_id = c.id\n WHERE e.chunk_id IS NULL\n ORDER BY c.id\n `;\n const totalSql = `SELECT COUNT(*) AS c FROM chunks`;\n\n const pending = vault.db.handle.prepare<[], PendingChunkRow>(pendingSql).all();\n const totalRow = vault.db.handle.prepare<[], { c: number }>(totalSql).get();\n const chunksTotal = totalRow?.c ?? 0;\n const chunksSkipped = chunksTotal - pending.length;\n\n log(\n `shadow-index \"${model}\" (dim=${dim}): ${pending.length} pending, ` +\n `${chunksSkipped} already embedded`,\n );\n\n let chunksEmbedded = 0;\n try {\n for (let i = 0; i < pending.length; i += batchSize) {\n const batch = pending.slice(i, i + batchSize);\n const embedResp = await ollama.embed({\n model,\n texts: batch.map((c) => c.text),\n });\n if (embedResp.dim !== dim) {\n throw new Error(\n `Shadow embedding dim mismatch mid-run: expected ${dim}, ` +\n `got ${embedResp.dim} on batch starting chunk_id ${batch[0]?.id}`,\n );\n }\n vault.db.embeddings.insertBatch(\n batch.map((row, j) => ({\n chunkId: row.id,\n modelId: modelRow.id,\n vector: embedResp.vectors[j]!,\n })),\n );\n chunksEmbedded += batch.length;\n if (i % (batchSize * 8) === 0) {\n log(` ${chunksEmbedded}/${pending.length}…`);\n }\n }\n\n vault.db.audit.finishRun(runId, {\n notesIndexed: 0,\n chunksCreated: chunksEmbedded,\n notesUpdated: 0,\n notesDeleted: 0,\n });\n } catch (err) {\n const message = errorMessage(err);\n vault.db.audit.finishRun(runId, {\n notesIndexed: 0,\n chunksCreated: chunksEmbedded,\n notesUpdated: 0,\n notesDeleted: 0,\n error: message,\n });\n throw err;\n }\n\n return {\n runId,\n modelId: modelRow.id,\n modelName: model,\n dim,\n chunksTotal,\n chunksEmbedded,\n chunksSkipped,\n durationMs: Date.now() - started,\n };\n}\n\n/**\n * Inventory of all registered models in a vault with per-model\n * shadow-completeness data.\n */\nexport interface ModelInventoryEntry {\n id: number;\n name: string;\n provider: string;\n dim: number;\n active: boolean;\n embedded_chunk_count: number;\n}\n\nexport function listModels(vault: Vault): ModelInventoryEntry[] {\n const rows = vault.db.models.listAll();\n return rows.map((m) => {\n // Phase 7e: each model owns its own vec0 table — chunk count is COUNT(*).\n let count = 0;\n try {\n vault.db.embeddings.ensureTableForModel(m.id, m.dim);\n const row = vault.db.handle\n .prepare<[], { c: number }>(`SELECT COUNT(*) AS c FROM embeddings_m${m.id}_d${m.dim}`)\n .get();\n count = row?.c ?? 0;\n } catch {\n // Defensive: if the table somehow can't be queried (e.g. corrupt\n // schema), surface 0 rather than crashing the listing call.\n count = 0;\n }\n return {\n id: m.id,\n name: m.name,\n provider: m.provider,\n dim: m.dim,\n active: m.active === 1,\n embedded_chunk_count: count,\n };\n });\n}\n\nexport interface SwitchResult {\n ok: boolean;\n reason?: \"unknown_model\" | \"incomplete\" | \"already_active\";\n missing_chunks?: number;\n switched_from?: string;\n switched_to?: string;\n}\n\n/**\n * Atomically switch the active embedding model for a vault. Refuses to\n * switch if any chunk in the vault is missing an embedding for the target\n * model — partial switches would leave the new active model unable to\n * answer queries for those chunks.\n */\nexport function switchActiveModel(vault: Vault, targetModelName: string): SwitchResult {\n const target = vault.db.models.getByName(targetModelName);\n if (!target) {\n return { ok: false, reason: \"unknown_model\" };\n }\n\n const current = vault.db.models.getActive();\n if (current && current.id === target.id) {\n return {\n ok: false,\n reason: \"already_active\",\n switched_from: current.name,\n switched_to: target.name,\n };\n }\n\n // Completeness check: every chunk must have an embedding for the target\n // model's vec0 table. Phase 7e: per-model table eliminates the model_id\n // join condition — presence in the table is sufficient.\n vault.db.embeddings.ensureTableForModel(target.id, target.dim);\n const embTable = `embeddings_m${target.id}_d${target.dim}`;\n const missingRow = vault.db.handle\n .prepare<[], { c: number }>(\n `SELECT COUNT(*) AS c\n FROM chunks c\n LEFT JOIN ${embTable} e ON e.chunk_id = c.id\n WHERE e.chunk_id IS NULL`,\n )\n .get();\n const missing = missingRow?.c ?? 0;\n\n if (missing > 0) {\n return {\n ok: false,\n reason: \"incomplete\",\n missing_chunks: missing,\n switched_from: current?.name,\n switched_to: target.name,\n };\n }\n\n vault.db.models.setActive(target.id);\n return {\n ok: true,\n switched_from: current?.name,\n switched_to: target.name,\n };\n}\n","/**\n * vacuum_embeddings — drop orphaned embedding rows.\n *\n * Over time, a vault DB can accumulate embedding rows whose `chunk_id` no\n * longer exists in the `chunks` table. Sources of orphans:\n * - Pre-v0.7.0 schemas where note-deletion did not always cascade through\n * the derived layer (since fixed by Migration 003 + 7c plumbing).\n * - Manual SQL repair, partial migrations, interrupted shadow runs.\n * - The v0.6.x → v0.7.x migration kept legacy `embeddings` rows in\n * `embeddings_` even when the chunks had been deleted upstream.\n *\n * The vault we used for the v0.7.2 eval still carried 1541 such orphans\n * (qwen3 had 3088 embeddings, only 1547 live chunks → ~50% orphaned).\n *\n * Behavior:\n * - Walks every per-model embeddings table (`embeddings_m_d`).\n * - Deletes rows whose `chunk_id` is not present in `chunks`.\n * - Returns a per-model count of (kept, removed, table) for the audit log.\n * - Never deletes rows in `chunks` itself; the raw layer is untouched.\n */\n\nimport type { Vault } from \"../vault/index.js\";\n\nexport interface VacuumPerModel {\n model_id: number;\n model_name: string;\n dim: number;\n table: string;\n removed: number;\n kept: number;\n}\n\nexport interface VacuumResult {\n total_removed: number;\n per_model: VacuumPerModel[];\n duration_ms: number;\n}\n\nexport function vacuumEmbeddings(vault: Vault): VacuumResult {\n const startedAt = Date.now();\n const models = vault.db.models.listAll();\n const per_model: VacuumPerModel[] = [];\n let total_removed = 0;\n\n // One transaction across all per-model tables so the result is all-or-nothing.\n vault.db.transaction(() => {\n for (const m of models) {\n // Materialise the table if it does not exist yet — `models` can list a\n // model that has not yet been embedded (e.g. a freshly registered\n // shadow model). In that case we'd skip cleanly with kept=0/removed=0.\n vault.db.embeddings.ensureTableForModel(m.id, m.dim);\n const table = `embeddings_m${m.id}_d${m.dim}`;\n\n const beforeRow = vault.db.handle\n .prepare<[], { c: number }>(`SELECT COUNT(*) AS c FROM ${table}`)\n .get();\n const before = beforeRow?.c ?? 0;\n\n // Two-step delete because sqlite-vec virtual tables do not support\n // `DELETE ... WHERE chunk_id NOT IN (subquery)` cleanly across all\n // builds. Collect the orphan IDs first, then delete by primary key.\n const orphans = vault.db.handle\n .prepare<[], { chunk_id: number }>(\n `SELECT chunk_id FROM ${table}\n WHERE chunk_id NOT IN (SELECT id FROM chunks)`,\n )\n .all();\n\n if (orphans.length > 0) {\n const stmt = vault.db.handle.prepare(`DELETE FROM ${table} WHERE chunk_id = ?`);\n for (const o of orphans) {\n stmt.run(BigInt(o.chunk_id));\n }\n }\n\n const removed = orphans.length;\n const kept = before - removed;\n total_removed += removed;\n per_model.push({\n model_id: m.id,\n model_name: m.name,\n dim: m.dim,\n table,\n removed,\n kept,\n });\n }\n });\n\n return {\n total_removed,\n per_model,\n duration_ms: Date.now() - startedAt,\n };\n}\n","export { indexVault, extractAliases, resolveWikilinkTarget } from \"./indexer.js\";\nexport type { IndexerOptions, IndexRunResult } from \"./indexer.js\";\nexport { indexNote, removeNote } from \"./single.js\";\nexport type { IndexNoteOptions, IndexNoteResult } from \"./single.js\";\nexport { catchupVault } from \"./catchup.js\";\nexport type { CatchupOptions, CatchupResult } from \"./catchup.js\";\nexport { startShadowIndex, listModels, switchActiveModel } from \"./shadow.js\";\nexport type {\n ShadowIndexOptions,\n ShadowIndexResult,\n ModelInventoryEntry,\n SwitchResult,\n} from \"./shadow.js\";\nexport { vacuumEmbeddings } from \"./vacuum.js\";\nexport type { VacuumResult, VacuumPerModel } from \"./vacuum.js\";\n","/**\n * Atomic filesystem helpers for the write module.\n *\n * Atomicity strategy: write to a sibling tmp file in the same directory as\n * the target, then `rename` it on top. On POSIX file systems, rename within\n * the same directory is atomic — this prevents readers (including Obsidian)\n * from ever observing a partially-written file.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport { dirname, isAbsolute, resolve, sep } from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\n\nexport class OutsideVaultError extends Error {\n constructor(relativePath: string, vaultRoot: string) {\n super(\n `Refused to operate on path outside vault: \"${relativePath}\" (vault root: \"${vaultRoot}\")`,\n );\n this.name = \"OutsideVaultError\";\n }\n}\n\n/**\n * Write `content` to `absPath` atomically. Creates parent directories if needed.\n *\n * The tmp file lives in the SAME directory as the target so the final rename\n * stays on the same filesystem (and therefore atomic).\n */\nexport async function atomicWriteFile(absPath: string, content: string): Promise {\n if (!isAbsolute(absPath)) {\n throw new Error(`atomicWriteFile requires an absolute path: ${absPath}`);\n }\n const parent = dirname(absPath);\n await fs.mkdir(parent, { recursive: true });\n\n const suffix = randomBytes(8).toString(\"hex\");\n const tmpPath = `${absPath}.tmp.${suffix}`;\n try {\n await fs.writeFile(tmpPath, content, \"utf-8\");\n await fs.rename(tmpPath, absPath);\n } catch (err) {\n // Best-effort cleanup of the tmp file. Ignore failure of cleanup itself.\n try {\n await fs.unlink(tmpPath);\n } catch {\n /* swallow */\n }\n throw err;\n }\n}\n\n/**\n * Resolve `relativePath` against `vaultRoot` and verify the result stays\n * within the vault. Throws `OutsideVaultError` on any escape attempt\n * (e.g. `../../etc/passwd`, absolute paths, string-level traversal).\n *\n * This function ALSO follows symlinks via `fs.realpath` to defeat\n * symlink-escape attacks: if a directory inside the vault is a symlink\n * pointing outside (e.g. `Netzwerk/escape -> /etc`), any path beneath\n * it is rejected even though the joined string looks vault-internal.\n *\n * Realpath is applied to:\n * - the vault root, and\n * - the deepest existing ancestor of the target (since the target\n * itself may not exist yet for a create/write).\n *\n * Async because it touches the filesystem.\n */\nexport async function safeJoinInsideVault(\n vaultRoot: string,\n relativePath: string,\n): Promise {\n if (typeof relativePath !== \"string\" || relativePath.length === 0) {\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n // Disallow absolute inputs outright — caller must pass vault-relative.\n if (isAbsolute(relativePath)) {\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n const root = resolve(vaultRoot);\n const target = resolve(root, relativePath);\n\n // String-level prefix check first — catches `../` traversal cheaply.\n const rootWithSep = root.endsWith(sep) ? root : root + sep;\n if (target !== root && !target.startsWith(rootWithSep)) {\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n if (target === root) {\n // The vault root itself is not a writable note path.\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n\n // Realpath both sides to defeat symlink-escape. The target may not exist\n // yet (creating a new note), so walk up to the deepest existing ancestor\n // and realpath that. Anything not yet on disk is by definition a fresh\n // path that cannot itself be a symlink.\n let realRoot: string;\n try {\n realRoot = await fs.realpath(root);\n } catch {\n // If the vault root itself cannot be resolved, refuse — we cannot\n // guarantee any boundary check is meaningful.\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n\n const realTarget = await resolveExistingAncestor(target);\n const realRootWithSep = realRoot.endsWith(sep) ? realRoot : realRoot + sep;\n if (realTarget !== realRoot && !realTarget.startsWith(realRootWithSep)) {\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n\n return target;\n}\n\n/**\n * Resolve the deepest existing ancestor of `absPath` via realpath, then\n * re-attach any non-existent trailing segments. This handles the common\n * case of writing a brand-new file whose parent (or grandparent) exists.\n */\nasync function resolveExistingAncestor(absPath: string): Promise {\n let current = absPath;\n const trailing: string[] = [];\n // Walk up until realpath succeeds or we hit the filesystem root.\n // eslint-disable-next-line no-constant-condition\n while (true) {\n try {\n const real = await fs.realpath(current);\n return trailing.length === 0 ? real : resolve(real, ...trailing.reverse());\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code !== \"ENOENT\" && code !== \"ENOTDIR\") {\n throw err;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Reached filesystem root without ever resolving — fall back to\n // the original string. The caller's prefix check has already\n // verified string-level containment.\n return absPath;\n }\n // Track the non-existent leaf to re-attach after realpath.\n trailing.push(current.slice(parent.length + 1));\n current = parent;\n }\n }\n}\n","/**\n * write/write.ts — atomic vault writes with hash-based concurrency control.\n *\n * Both `writeNote` and `deleteNote` keep the file system and the vault DB\n * in sync: the file is written/removed first, then the DB is updated and\n * an audit row is inserted. If the on-disk hash does not match the\n * caller-provided `expectedHash`, the operation aborts BEFORE touching\n * either FS or DB and returns a structured conflict.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport matter from \"gray-matter\";\nimport type { Vault } from \"../../../vault/index.js\";\nimport { computeNoteHash, computeBodyHash } from \"../../source/obsidian-fs/hash.js\";\nimport { extractAliases } from \"../../../indexer/index.js\";\nimport { atomicWriteFile, safeJoinInsideVault } from \"./fs.js\";\nimport { formatDocId } from \"../../registry.js\";\nimport type { MemorySinkRegistry } from \"../../../memory/registry.js\";\n\nexport interface WriteSuccess {\n ok: true;\n newHash: string;\n noteId: number;\n /** True if a brand-new file/note was created. */\n created: boolean;\n}\n\nexport interface WriteConflict {\n ok: false;\n reason: \"hash_mismatch\" | \"permission_denied\" | \"sink_write_blocked\";\n currentHash?: string;\n currentContent?: string;\n message: string;\n /** Phase 2 envelope (sink_write_blocked). */\n sinkName?: string;\n /** Phase 2 envelope — actionable next-step hint. */\n suggestion?: string;\n}\n\nexport type WriteResult = WriteSuccess | WriteConflict;\n\nexport interface WriteNoteInput {\n vault: Vault;\n /** Vault-relative path with forward slashes, ending in .md */\n relativePath: string;\n /** Markdown body WITHOUT frontmatter delimiters. */\n content: string;\n /** Optional frontmatter object — will be serialized to YAML by the function. */\n frontmatter?: Record | null;\n /**\n * Concurrency token. If the file's current hash on disk differs from\n * this, return a conflict instead of writing. If omitted: write\n * unconditionally only when the file does NOT exist yet; otherwise\n * return a conflict.\n */\n expectedHash?: string;\n /**\n * Audit-log attribution. Per D-02, this is captured by the\n * ObsidianFsDelivery facade from the MCP InitializeRequest.params.clientInfo\n * at server bootstrap (falling back to \"unknown\"). Per-call overrides via\n * the opts.clientId path beat the constructor default.\n *\n * Note: the v1 hardcoded `DEFAULT_CLIENT_ID` (a fixed client name) was\n * removed in plan 01-04 (the C-1 leak). Internal writeNote/deleteNote\n * now require the caller to supply the value explicitly via the facade.\n */\n clientId?: string;\n /**\n * Called exactly once, immediately before the filesystem write. Used by\n * the MCP server to mark the path on the watcher's SuppressionSet so the\n * watcher ignores the fs event triggered by our own atomic rename.\n *\n * If the operation aborts (hash conflict, permission denied) this hook\n * is NOT called — so a failed write cannot accidentally suppress a real\n * external edit that happens shortly after.\n */\n onBeforeFsWrite?: () => void;\n /**\n * Plan 02-03b: optional defense-in-depth entry-point Guard.\n *\n * When supplied AND the resolved target lands inside a registered\n * MemorySink (per `registry.findSinkContaining(docId)`), the write\n * is refused with `{ok:false, reason:\"sink_write_blocked\"}` BEFORE\n * any filesystem read. The authoritative chokepoint still lives at\n * the DeliveryAdapter (`ObsidianFsDelivery.preflight()` per ADR-002\n * §DeliveryAdapter); this v1 entry-point Guard is defense-in-depth\n * so that callers bypassing the facade hit a structured refusal\n * rather than silently dumping into a memory folder.\n *\n * When omitted (Phase 1 unit-test fixtures + any caller that has not\n * yet been threaded with the registry), the guard is silently\n * skipped — Phase 1 behavior is byte-for-byte preserved. The MCP\n * server bootstrap in Plan 02-03b always passes the registry, so\n * production callers are always guarded.\n */\n registry?: MemorySinkRegistry;\n /**\n * Plan 02-06 (MEM-08): whether this write is routed under a configured\n * `MemorySink`. The DeliveryAdapter facade derives the flag from\n * `opts.sink !== undefined` and forwards it; v1 `writeNote` callers\n * (e.g. `update_frontmatter`, raw `write_note`) leave it `false`. Stored\n * on the resulting `write_audit` row so `audit_log` can distinguish\n * agent-written memory documents from regular user writes.\n */\n isMemorySinkWrite?: boolean;\n}\n\nexport interface DeleteNoteInput {\n vault: Vault;\n relativePath: string;\n /** Required for delete — caller must prove they read the current state. */\n expectedHash: string;\n clientId?: string;\n /** See WriteNoteInput.onBeforeFsWrite. Called just before fs.unlink. */\n onBeforeFsWrite?: () => void;\n /** See WriteNoteInput.registry — same defense-in-depth Guard semantics\n * apply to deleteNote. The suggestion text references `supersede` per\n * Plan 02-03 truth: hard deletion of memory documents is forbidden in\n * v2.0.0; agents retire memory documents via supersede. */\n registry?: MemorySinkRegistry;\n /**\n * Plan 02-06 (MEM-08): whether this delete is routed under a configured\n * `MemorySink`. v2.0.0 forbids hard-deletion inside a sink (the\n * DeliveryAdapter facade and the entry-point Guard reject sink-resolved\n * paths) — this flag exists for symmetry with `WriteNoteInput` and for\n * audit-row stamping at any future delete path that does land inside a\n * sink (e.g. an admin-tier delete that bypasses the Guard). Defaults to\n * `false`; pre-Plan-02-06 call sites need no change.\n */\n isMemorySinkWrite?: boolean;\n}\n\n/**\n * Neutral fallback when no client_id is supplied at any level. Per D-02\n * + RESEARCH Pitfall 4: MCP InitializeRequest.params.clientInfo is\n * OPTIONAL in the spec, so older or non-conformant clients may not send\n * a name. This fallback is observably truthful (the previous hardcoded\n * default lied for any client that wasn't the assumed one).\n */\nconst UNKNOWN_CLIENT_ID = \"unknown\";\n\nfunction permissionDenied(vaultName: string): WriteConflict {\n return {\n ok: false,\n reason: \"permission_denied\",\n message: `Vault \"${vaultName}\" is read-only (write_enabled=false in config.toml)`,\n };\n}\n\n/**\n * Compute the canonical content-hash the way the reader does. Delegates to\n * `computeNoteHash` from reader/hash.ts (canonical, key-sorted JSON).\n */\nfunction computeHash(content: string, frontmatter: Record | null): string {\n return computeNoteHash(content, frontmatter);\n}\n\nfunction extractTitle(content: string, relativePath: string): string {\n for (const line of content.split(\"\\n\")) {\n const m = /^#\\s+(.+?)\\s*$/.exec(line);\n if (m !== null && m[1] !== undefined) return m[1].trim();\n }\n return basename(relativePath, \".md\");\n}\n\nfunction countWords(content: string): number {\n if (content.length === 0) return 0;\n return content.split(/\\s+/).filter((s) => s.length > 0).length;\n}\n\nasync function readExistingFile(absPath: string): Promise<{\n raw: string;\n content: string;\n frontmatter: Record | null;\n hash: string;\n} | null> {\n let raw: string;\n try {\n raw = await fs.readFile(absPath, \"utf-8\");\n } catch (err) {\n if (\n typeof err === \"object\" &&\n err !== null &&\n (err as NodeJS.ErrnoException).code === \"ENOENT\"\n ) {\n return null;\n }\n throw err;\n }\n const parsed = matter(raw);\n const fmData = parsed.data as Record | undefined;\n const frontmatter: Record | null =\n fmData !== undefined && Object.keys(fmData).length > 0 ? fmData : null;\n const hash = computeHash(parsed.content, frontmatter);\n return { raw, content: parsed.content, frontmatter, hash };\n}\n\nexport async function writeNote(input: WriteNoteInput): Promise {\n const { vault, relativePath, content, registry } = input;\n const frontmatter = input.frontmatter ?? null;\n const clientId = input.clientId ?? UNKNOWN_CLIENT_ID;\n\n // Plan 02-03b — defense-in-depth entry-point Guard. Runs BEFORE the\n // write_enabled check and BEFORE any FS read. When the optional registry\n // is supplied (production path) AND the target lands inside a registered\n // sink, refuse with the structured `sink_write_blocked` envelope.\n if (registry) {\n const docId = formatDocId(\"obsidian-fs\", vault.config.name, relativePath);\n const sink = registry.findSinkContaining(docId);\n if (sink !== null) {\n return {\n ok: false,\n reason: \"sink_write_blocked\",\n sinkName: sink.name,\n message:\n `Target ${relativePath} resolves into MemorySink \"${sink.name}\". ` +\n `v1 write_note is refused for memory-sink targets.`,\n suggestion: `Use record_observation for sink '${sink.name}'.`,\n };\n }\n }\n\n if (vault.config.write_enabled !== true) {\n return permissionDenied(vault.config.name);\n }\n\n // Throws OutsideVaultError on traversal — intentional: callers should not\n // be able to construct invalid paths and silently get a \"conflict\".\n const absPath = await safeJoinInsideVault(vault.config.path, relativePath);\n\n const existing = await readExistingFile(absPath);\n const created = existing === null;\n\n if (existing !== null) {\n if (input.expectedHash === undefined) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n currentHash: existing.hash,\n currentContent: existing.raw,\n message:\n `File \"${relativePath}\" already exists. ` +\n `Pass expectedHash=\"${existing.hash}\" to overwrite intentionally.`,\n };\n }\n if (input.expectedHash !== existing.hash) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n currentHash: existing.hash,\n currentContent: existing.raw,\n message:\n `Hash mismatch for \"${relativePath}\": ` +\n `expected ${input.expectedHash}, got ${existing.hash}. ` +\n `The file was modified externally — re-read and retry.`,\n };\n }\n }\n\n // Serialize new content. gray-matter.stringify writes a `---` block only\n // when the data object is non-empty; we mirror that behavior explicitly.\n // Issue #14: pass lineWidth: -1 so js-yaml does NOT fold long string values\n // into a `>-` block scalar (its default is lineWidth: 80). Obsidian's\n // Properties editor mishandles block scalars; single-line values round-trip\n // cleanly. gray-matter forwards this option verbatim to js-yaml's dump()\n // (see gray-matter/lib/stringify.js), but @types/gray-matter's option type\n // doesn't list the js-yaml keys — hence the narrow cast.\n const yamlDumpOptions = { lineWidth: -1 } as Parameters[2];\n const fileText =\n frontmatter !== null && Object.keys(frontmatter).length > 0\n ? matter.stringify(content, frontmatter, yamlDumpOptions)\n : content;\n\n input.onBeforeFsWrite?.();\n await atomicWriteFile(absPath, fileText);\n\n // Re-parse from disk to compute the canonical post-write hash. This also\n // protects us against any normalization gray-matter may apply on stringify.\n const written = await readExistingFile(absPath);\n if (written === null) {\n // Should never happen — we just wrote it.\n throw new Error(`Internal error: file disappeared after write: ${relativePath}`);\n }\n const stat = await fs.stat(absPath);\n\n const previousNote = vault.db.notes.getByPath(relativePath);\n const previousHash = previousNote?.hash ?? null;\n const title = extractTitle(written.content, relativePath);\n\n // Codex MEDIUM-1: wrap the three DB writes in a single transaction so they\n // either all land or none do. If the transaction throws, roll back the FS\n // write to the pre-write state — either by unlinking a freshly created\n // file, or restoring the previous on-disk content.\n let upsertId: number;\n try {\n upsertId = vault.db.transaction(() => {\n const up = vault.db.notes.upsertByPath({\n path: relativePath,\n content: written.content,\n frontmatter: written.frontmatter ? JSON.stringify(written.frontmatter) : null,\n title,\n hash: written.hash,\n bodyHash: computeBodyHash(written.content),\n mtime: Math.floor(stat.mtimeMs),\n wordCount: countWords(written.content),\n });\n vault.db.aliases.setForNote(up.id, extractAliases(written.frontmatter));\n vault.db.audit.recordWrite({\n noteId: up.id,\n op: created ? \"create\" : \"update\",\n previousHash,\n newHash: written.hash,\n expectedHash: input.expectedHash ?? null,\n clientId,\n diffSummary: null,\n // Plan 02-06 (MEM-08): stamp the audit row with the sink-routing\n // flag the facade derived from `opts.sink !== undefined`. v1 call\n // sites that haven't been threaded leave the field undefined →\n // recordWrite defaults to 0 (non-memory).\n isMemorySinkWrite: input.isMemorySinkWrite ?? false,\n });\n return up.id;\n });\n } catch (dbErr) {\n // Suppress the next watcher event from our rollback write/unlink too —\n // the watcher would otherwise re-index the rolled-back state and undo\n // the rollback's intent.\n input.onBeforeFsWrite?.();\n try {\n if (created) {\n await fs.unlink(absPath);\n } else if (existing !== null) {\n await atomicWriteFile(absPath, existing.raw);\n }\n } catch {\n // Rollback failed — leave the divergence visible by re-throwing the\n // original DB error. Catch-up reconciliation will eventually heal it.\n }\n throw dbErr;\n }\n\n return {\n ok: true,\n newHash: written.hash,\n noteId: upsertId,\n created,\n };\n}\n\nexport async function deleteNote(input: DeleteNoteInput): Promise {\n const { vault, relativePath, expectedHash, registry } = input;\n const clientId = input.clientId ?? UNKNOWN_CLIENT_ID;\n\n // Plan 02-03b — defense-in-depth entry-point Guard. Same shape as\n // writeNote, but the suggestion text directs the caller to `supersede`\n // (hard deletion of memory documents is forbidden in v2.0.0 per Plan\n // 02-03 truth).\n if (registry) {\n const docId = formatDocId(\"obsidian-fs\", vault.config.name, relativePath);\n const sink = registry.findSinkContaining(docId);\n if (sink !== null) {\n return {\n ok: false,\n reason: \"sink_write_blocked\",\n sinkName: sink.name,\n message:\n `Target ${relativePath} resolves into MemorySink \"${sink.name}\". ` +\n `Hard deletion of memory documents is not permitted in v2.0.0.`,\n suggestion:\n \"Use supersede to retire memory documents. Hard deletion is not yet supported in v2.0.0.\",\n };\n }\n }\n\n if (vault.config.write_enabled !== true) {\n return permissionDenied(vault.config.name);\n }\n\n const absPath = await safeJoinInsideVault(vault.config.path, relativePath);\n\n const existing = await readExistingFile(absPath);\n if (existing === null) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n message: `File \"${relativePath}\" does not exist — nothing to delete.`,\n };\n }\n if (existing.hash !== expectedHash) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n currentHash: existing.hash,\n currentContent: existing.raw,\n message:\n `Hash mismatch for \"${relativePath}\": ` +\n `expected ${expectedHash}, got ${existing.hash}. ` +\n `The file was modified externally — re-read and retry.`,\n };\n }\n\n const previousNote = vault.db.notes.getByPath(relativePath);\n const previousHash = previousNote?.hash ?? existing.hash;\n\n input.onBeforeFsWrite?.();\n await fs.unlink(absPath);\n\n // Remove from DB. If the note was never indexed (e.g. file appeared and\n // was deleted between indexer runs) we still record a synthetic audit\n // entry — but only when we have a noteId. Without one, the audit row\n // can't be tied to a (now-gone) note.\n if (previousNote !== null) {\n // Since migration 003 the FKs do the right thing:\n // - chunks.note_id, note_aliases.note_id → ON DELETE CASCADE (auto-clear)\n // - wikilinks.source_note → ON DELETE CASCADE (outgoing links gone)\n // - wikilinks.target_note → ON DELETE SET NULL (incoming links become\n // broken; find_broken_links surfaces them correctly)\n // - write_audit.note_id → ON DELETE SET NULL (the audit row survives;\n // getAuditLog already resolves notePath=null for a vanished note)\n //\n // Wrap delete + audit insert in one transaction so a crash leaves\n // either both or neither.\n vault.db.transaction(() => {\n vault.db.audit.recordWrite({\n noteId: previousNote.id,\n op: \"delete\",\n previousHash,\n newHash: null,\n expectedHash,\n clientId,\n diffSummary: null,\n // Plan 02-06 (MEM-08): symmetric stamp on delete. Production\n // deletes targeting a sink are refused by the entry-point Guard\n // and the facade — so this flag is normally `false` on delete\n // rows. Pass-through retained for symmetry / future admin paths.\n isMemorySinkWrite: input.isMemorySinkWrite ?? false,\n });\n vault.db.notes.deleteByPath(relativePath);\n });\n return {\n ok: true,\n newHash: existing.hash,\n noteId: previousNote.id,\n created: false,\n };\n }\n\n return {\n ok: true,\n newHash: existing.hash,\n noteId: 0,\n created: false,\n };\n}\n","/**\n * `validateAgentWrite` — the SINGLE Phase 2 chokepoint per\n * ADR-002 §DeliveryAdapter and ADR-004 §Resolution.\n *\n * Adapters (`ObsidianFsDelivery`, `StubDelivery`, and any future\n * delivery adapter) call this pure function at the top of `write()`,\n * `update()`, and `delete()` BEFORE touching the backing store. The\n * function returns `null` on pass and a structured `GuardFailure` on\n * refusal — the adapter then returns the failure as a `WriteConflict`.\n *\n * Guard ordering (per the TSDoc on `WriteConflict`):\n *\n * 1. Guard B (cheap): inspect `properties.source` against sink\n * membership.\n * - `source === \"agent\"` AND `sink === null`\n * ⇒ `agent_write_outside_sink`.\n * - `source` set AND `source !== \"agent\"` AND `sink !== null`\n * ⇒ `non_agent_write_inside_sink`.\n * Pass-through cases:\n * - `source === undefined` and `sink === null` ⇒ ordinary\n * (non-memory) v1 write — pass.\n * - `source === \"user\"` and `sink === null` ⇒ user writing\n * outside any sink — pass.\n * - `source === \"agent\"` and `sink !== null` ⇒ proceed to\n * Guard A.\n *\n * 2. Guard A: when the target lands in a sink AND a contract is\n * bound, run `contract.propertiesSchema.safeParse(doc.properties)`.\n * Map the FIRST issue to one of `missing_provenance`,\n * `invalid_provenance`, or `supersede_mismatch` (cross-field).\n *\n * The sentinel check (`sentinel_missing`) and the delete-into-sink\n * refusal (`sink_write_blocked`) are adapter-level concerns — they\n * live inside the adapter's `write` / `delete` and do NOT round-trip\n * through this validator. The validator covers exactly the five\n * `GuardFailure` codes.\n *\n * Zod 4 issue-shape notes (verified against zod@4.4.3 at probe time):\n * - `code === \"invalid_type\"` is emitted for both genuine type\n * errors AND for missing-required keys (because Zod sees\n * `undefined` at that path). We disambiguate \"missing\" from\n * \"wrong type\" by inspecting the actual value at the path: if it\n * is `undefined`, it's `missing_provenance`; otherwise\n * `invalid_provenance`.\n * - `code === \"invalid_value\"` is emitted for enum mismatch.\n * - `code === \"invalid_format\"` is emitted for `.datetime()` etc.\n * - `code === \"custom\"` is emitted by `.superRefine` cross-field\n * rules in `DEFAULT_MEMORY_V1` (status=superseded invariants).\n *\n * No filesystem, no path joining, no gray-matter, no node:* — pure\n * data-in, data-out. Re-usable by both delivery adapters and the v1\n * entry-point Guards landing in Plan 02-03b.\n */\n\nimport type { DocId, Document, MemorySink } from \"../types.js\";\nimport type { WriteConflict } from \"../adapters/delivery/types.js\";\nimport type { MemoryContract } from \"./contract/index.js\";\n\n/**\n * The subset of `WriteConflict` codes this validator can emit.\n * Adapter-only codes (`sentinel_missing`, `sink_write_blocked`) are\n * deliberately excluded — they are filesystem/registry-level concerns.\n *\n * Implemented as `WriteConflict & { reason: }` rather than\n * `Extract<...>` because `WriteConflict` is a single interface (not a\n * union), so `Extract` would distribute incorrectly and produce\n * `never`. The intersection narrows the `reason` field to the subset\n * we actually emit.\n */\nexport type GuardFailure = WriteConflict & {\n reason:\n | \"missing_provenance\"\n | \"invalid_provenance\"\n | \"supersede_mismatch\"\n | \"agent_write_outside_sink\"\n | \"non_agent_write_inside_sink\";\n};\n\n/**\n * Safe key read on `Document.properties`. Returns `undefined` if\n * `props` is missing, the key is missing, or the property bag itself\n * is non-object.\n */\nfunction getAt(props: Record | undefined, key: string): unknown {\n if (!props || typeof props !== \"object\") return undefined;\n return props[key];\n}\n\n/**\n * Run Guards B and A against a write target.\n *\n * @param id Document identity (carried in diagnostics).\n * @param doc Partial document being written / updated. The validator\n * inspects `doc.properties` only; blocks/title/etc. are ignored.\n * @param sink Resolved sink the target lands in, or `null` if the\n * target is outside every registered sink.\n * @param contract Contract bound to `sink.contractName`, or `null` if\n * `sink` is `null` (in which case Guard A is skipped).\n * @returns `null` on pass; a `GuardFailure` describing the first\n * detected violation otherwise.\n */\nexport function validateAgentWrite(\n id: DocId,\n doc: Partial,\n sink: MemorySink | null,\n contract: MemoryContract | null,\n): GuardFailure | null {\n const props = doc.properties as Record | undefined;\n const source = getAt(props, \"source\");\n\n // ── Guard B (cheap; runs first) ──────────────────────────────────────────\n if (source === \"agent\" && sink === null) {\n return {\n ok: false,\n reason: \"agent_write_outside_sink\",\n message:\n `source:\"agent\" writes are only permitted under a configured ` +\n `MemorySink. Target ${id} does not resolve into any sink.`,\n suggestion:\n \"Use record_observation for memory writes; or change source to 'user' / 'imported'.\",\n };\n }\n if (source !== undefined && source !== \"agent\" && sink !== null) {\n return {\n ok: false,\n reason: \"non_agent_write_inside_sink\",\n sinkName: sink.name,\n message:\n `source:\"${String(source)}\" writes are not permitted into ` + `MemorySink \"${sink.name}\".`,\n suggestion:\n \"Memory sinks accept source:'agent' writes only. User notes belong in the surrounding vault.\",\n };\n }\n\n // ── Guard A (only when target lands in a sink AND a contract is bound) ──\n if (sink !== null && contract !== null) {\n const result = contract.propertiesSchema.safeParse(props ?? {});\n if (!result.success) {\n const issue = result.error.issues[0];\n if (!issue) return null;\n const pathHead = issue.path[0];\n const key = typeof pathHead === \"string\" ? pathHead : undefined;\n\n // Cross-field rules in DEFAULT_MEMORY_V1 emit `code === \"custom\"`\n // with the path pointing at `superseded_by` or `superseded_reason`.\n // Map either path to `supersede_mismatch`.\n if (key === \"superseded_reason\" || key === \"superseded_by\") {\n return {\n ok: false,\n reason: \"supersede_mismatch\",\n sinkName: sink.name,\n ...(key !== undefined ? { key } : {}),\n message: `Cross-field rule failed at \"${key}\": ${issue.message}`,\n suggestion:\n \"When status is 'superseded', set both superseded_by (DocId) and superseded_reason (non-empty string).\",\n };\n }\n\n // \"Missing required\" disambiguation: in Zod 4 a missing required\n // key surfaces with `code === \"invalid_type\"` for plain-string\n // schemas (received undefined) OR with `code === \"invalid_value\"`\n // for enum schemas (no enum option matches undefined). Either\n // way, the canonical signal that the key is MISSING (rather than\n // present-but-wrong-shape) is that the actual value at the path\n // is `undefined`.\n const observed = key !== undefined ? getAt(props, key) : undefined;\n if (observed === undefined) {\n return {\n ok: false,\n reason: \"missing_provenance\",\n sinkName: sink.name,\n ...(key !== undefined ? { key } : {}),\n message:\n `Required property \"${key ?? \"(unknown)\"}\" is missing for writes ` +\n `into MemorySink \"${sink.name}\".`,\n suggestion:\n `Set properties.${key ?? \"\"} before retrying. ` +\n `See contract \"${contract.name}\" required keys: ${contract.requiredKeys.join(\", \")}.`,\n };\n }\n\n return {\n ok: false,\n reason: \"invalid_provenance\",\n sinkName: sink.name,\n ...(key !== undefined ? { key } : {}),\n observedValue: observed,\n message: `Property \"${key ?? \"(unknown)\"}\" failed validation: ${issue.message}`,\n suggestion: `See contract \"${contract.name}\" for valid values.`,\n };\n }\n }\n\n return null;\n}\n","/**\n * Hardcoded baseline `MemoryContract` for `default-memory-v1`.\n *\n * Mirrors the normative spec in `docs/v2/MEMORY_CONTRACT.md` and the\n * (post-amendment) `default-memory-v1` YAML example in\n * `docs/v2/adr/004-memory-sink-handles.md`. The seven required keys\n * (`source`, `confidence`, `evidence`, `status`, `observed_at`,\n * `superseded_by`, `type`) plus the optional `superseded_reason` and\n * the cross-field invariant (`status === \"superseded\"` ⇒\n * `superseded_reason` non-empty AND `superseded_by` non-null) are\n * baked in as a single Zod `.superRefine`-wrapped object schema.\n *\n * `passthrough()` keeps contract-extras (`expires_at`, `tags`, etc.)\n * from being silently dropped — D-02 (CONTEXT.md) escape hatch for\n * future contract-allowed fields.\n *\n * Phase 2 ships this hardcoded baseline so the validator works without\n * a disk read; the YAML loader (`./loader.ts`) handles named contracts\n * that ship in `_contracts/memory/.yaml`.\n */\n\nimport { z } from \"zod\";\nimport type { MemoryContract } from \"./types.js\";\n\nconst requiredKeys = [\n \"source\",\n \"confidence\",\n \"evidence\",\n \"status\",\n \"observed_at\",\n \"superseded_by\",\n \"type\",\n] as const;\n\nconst baseShape = z\n .object({\n source: z.enum([\"agent\", \"user\", \"imported\"]),\n confidence: z.enum([\"direct\", \"inferred\", \"uncertain\"]),\n evidence: z.array(z.string()),\n status: z.enum([\"active\", \"superseded\", \"archived\"]).default(\"active\"),\n observed_at: z.string().datetime({ offset: true }),\n superseded_by: z.string().nullable().default(null),\n type: z.string().min(1),\n superseded_reason: z.string().optional(),\n })\n // D-02: unknown contract-extra keys pass through.\n .passthrough()\n // Cross-field invariant: when status is \"superseded\", BOTH\n // `superseded_by` (non-null DocId) AND `superseded_reason` (non-empty\n // string) are required. Other statuses leave both fields unconstrained\n // beyond their base types.\n .superRefine((data, ctx) => {\n if (data.status === \"superseded\") {\n if (data.superseded_by === null || data.superseded_by === undefined) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"superseded_by\"],\n message: \"Required (non-null DocId) when status is 'superseded'\",\n });\n }\n if (typeof data.superseded_reason !== \"string\" || data.superseded_reason.length === 0) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"superseded_reason\"],\n message: \"Required (non-empty string) when status is 'superseded'\",\n });\n }\n }\n });\n\nexport const DEFAULT_MEMORY_V1: MemoryContract = {\n name: \"default-memory-v1\",\n version: \"1.0\",\n propertiesSchema: baseShape,\n requiredKeys,\n naming: {\n strategy: \"date-slug\",\n pattern: \"{observed_at:YYYY-MM-DD}-{slug}.md\",\n },\n};\n","/**\n * Hardcoded baseline `MemoryContract` for `default-brief-v1`.\n *\n * Phase 5 (ADR-005 §\"New default-brief-v1 contract\"): briefs have a\n * distinct lifecycle from observations — they can be `\"stale\"` (a state\n * `default-memory-v1` does not allow). Rather than widen the Phase 2\n * contract's status enum (scope creep + mis-types non-brief documents),\n * we register a separate contract bound to the `_memory/_briefs/` sink.\n *\n * Mirrors the shape of `default-memory-v1` (`./default-v1.ts`) and\n * extends:\n * - Status enum: `active | stale | superseded | archived` (adds\n * `\"stale\"`).\n * - Required keys: the base seven plus `target, purpose,\n * compiled_from, compiled_at, source_hashes`.\n * - Cross-field invariant: when `status === \"stale\"`,\n * `source_hashes` MUST be present (the daemon needs hashes to\n * drive recompute). Inherits the `status === \"superseded\"`\n * invariant from `default-v1`.\n *\n * `passthrough()` keeps contract-extras (changed_sources, max_tokens,\n * etc.) from being silently dropped — D-02 escape hatch.\n *\n * Naming strategy is `caller-provided` because `compile_brief`\n * computes the timestamped slug itself per D-12 (the slug-timestamp\n * algorithm is not a `MemoryContract.naming.strategy` enum member;\n * see ADR-005 §\"Decision: Recompile chain auto-supersede\").\n *\n * Pure module. No fs / gray-matter / chokidar / path imports.\n */\n\nimport { z } from \"zod\";\nimport type { MemoryContract } from \"./types.js\";\n\nconst requiredKeys = [\n // Base seven (mirrors default-v1).\n \"source\",\n \"confidence\",\n \"evidence\",\n \"status\",\n \"observed_at\",\n \"superseded_by\",\n \"type\",\n // Brief-specific keys per ADR-005 / MEMORY_CONTRACT.md brief shape.\n \"target\",\n \"purpose\",\n \"compiled_from\",\n \"compiled_at\",\n \"source_hashes\",\n] as const;\n\nconst baseShape = z\n .object({\n // ── Base shape inherited from default-v1 ────────────────────────\n source: z.enum([\"agent\", \"user\", \"imported\"]),\n confidence: z.enum([\"direct\", \"inferred\", \"uncertain\"]),\n evidence: z.array(z.string()),\n // ── Status enum WIDENED for briefs: + \"stale\" ──────────────────\n status: z.enum([\"active\", \"stale\", \"superseded\", \"archived\"]).default(\"active\"),\n observed_at: z.string().datetime({ offset: true }),\n superseded_by: z.string().nullable().default(null),\n type: z.string().min(1),\n superseded_reason: z.string().optional(),\n\n // ── Brief-specific properties (D-11 brief shape) ───────────────\n target: z.string().min(1),\n /**\n * Brief purpose — free text but bounded at 500 chars so\n * `list_briefs` stays scannable. Lower bound `min(1)` matches\n * BRF-03 \"no empty purpose\".\n */\n purpose: z.string().min(1).max(500),\n /** DocId list of all sources the brief was compiled from. */\n compiled_from: z.array(z.string()).min(1),\n /** ISO-8601 datetime with offset (mirrors observed_at). */\n compiled_at: z.string().datetime({ offset: true }),\n /**\n * Record — staleness contract. The map\n * key is the public ChunkId (`#chunk-<7-hex>`); the value\n * is `\"sha256:\"`. Marked optional at the type level because\n * the cross-field invariant below only REQUIRES it on stale; the\n * validator still rejects `status: \"stale\"` writes that omit it.\n */\n source_hashes: z.record(z.string(), z.string()).optional(),\n /**\n * Daemon-computed list of source DocIds whose hashes have\n * diverged. Populated when `status` flips to `\"stale\"`.\n */\n changed_sources: z.array(z.string()).optional(),\n })\n // D-02: unknown contract-extra keys pass through.\n .passthrough()\n // Cross-field invariants — inherits the `superseded` requirements\n // from default-v1 AND adds the brief-specific `stale` requirement.\n .superRefine((data, ctx) => {\n // Inherited from default-v1: when status is \"superseded\",\n // superseded_by MUST be non-null AND superseded_reason MUST be a\n // non-empty string. The recompile path (D-12) sets\n // `reason: \"recompiled\"` automatically; manual supersedes carry\n // a caller-supplied reason.\n if (data.status === \"superseded\") {\n if (data.superseded_by === null || data.superseded_by === undefined) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"superseded_by\"],\n message: \"Required (non-null DocId) when status is 'superseded'\",\n });\n }\n if (typeof data.superseded_reason !== \"string\" || data.superseded_reason.length === 0) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"superseded_reason\"],\n message: \"Required (non-empty string) when status is 'superseded'\",\n });\n }\n }\n // Brief-specific: when status is \"stale\", source_hashes MUST be\n // present (the daemon needs the recorded hashes to know which\n // sources diverged — without them recompile cannot be targeted).\n if (data.status === \"stale\") {\n if (!data.source_hashes) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"source_hashes\"],\n message: \"Required when status is 'stale' (daemon needs hashes to recompute)\",\n });\n }\n }\n });\n\nexport const DEFAULT_BRIEF_V1: MemoryContract = {\n name: \"default-brief-v1\",\n version: \"1.0\",\n propertiesSchema: baseShape,\n requiredKeys,\n // D-12 timestamped slug (`{target}--{compiled_at:YYYYMMDDTHHmm}.md`)\n // is computed by compile_brief itself — the caller (the brief layer)\n // hands the DeliveryAdapter a fully-formed DocId. The MemoryContract\n // naming strategy enum (`caller-provided | date-slug |\n // adapter-assigned`) does not include `slug-timestamp` as a value;\n // `caller-provided` is the closest match and signals \"the\n // implementation mints the DocId before write\".\n naming: {\n strategy: \"caller-provided\",\n },\n};\n","/**\n * Sink-aware path helpers for obsidian-fs.\n *\n * Per ADR-002 I-3 the `node:path` module is licensed only inside\n * `src/adapters/delivery/obsidian-fs/` (plus `src/cli.ts` and\n * `src/server.ts` for legacy bootstrap reasons). This file is the SOLE\n * licensed `path.join` site for sink/vault path resolution in Phase 2.\n *\n * Helpers split by consumer category (CR-03 / Plan 02-11):\n *\n * FS-bound (OS-native absolute) — for values that flow into `fs.*` calls:\n * - `joinVaultPath(vaultRoot, relPath)` — thin wrapper over `path.join`.\n * - `pathInSink(vaultAbsolutePath, sink, relativeSubpath?)` — absolute\n * path inside a memory sink.\n *\n * Comparison-bound (forward-slash, vault-relative) — for values that flow\n * into DocId-resource comparisons, SQL `LIKE '%'` lookups against\n * `notes.path`, or `MemorySinkRegistry.findSinkContaining` matching:\n * - `joinVaultPathPosix(...segments)` — `path.posix.join` with defensive\n * backslash normalization.\n * - `vaultRelativeInSink(sink, relativeSubpath?)` — forward-slash form of\n * ``.\n *\n * Why split? `pathInSink`'s return value goes into `fs.access` / `fs.readFile`\n * / `fs.writeFile`, where OS-native separators are the convention. The\n * comparison-bound helpers must emit forward-slash on every OS, because the\n * DocId resource (governed by `DOC_ID_PATTERN` in `src/adapters/registry.ts`)\n * and `notes.path` storage (forward-slash by indexer convention) are both\n * forward-slash regardless of `process.platform`. On Windows, `path.join`\n * emits backslashes — silently breaking Guard B / `findSinkContaining` /\n * `lastMemoryWriteAtForPathPrefix` SQL lookups. The split is the seam-level\n * fix; FS-bound helpers retain OS-native semantics, comparison-bound helpers\n * lock forward-slash.\n *\n * All helpers are pure synchronous string ops — no `fs` calls, no I/O.\n */\n\nimport path from \"node:path\";\n\n/**\n * Join a vault-absolute path with a vault-relative subpath. Thin wrapper\n * over `path.join` — OS-native separators. Use for paths that flow into\n * `fs.*` calls (read / write / stat / access). Use this instead of\n * importing `node:path` so the seam-preservation CI grep stays happy.\n *\n * FS-bound — DO NOT use the return value for comparison against a DocId\n * resource or for SQL `LIKE` prefix lookups; use `joinVaultPathPosix` or\n * `vaultRelativeInSink` for those callers.\n */\nexport function joinVaultPath(vaultRoot: string, relPath: string): string {\n return path.join(vaultRoot, relPath);\n}\n\n/**\n * Structural shape required from a sink — only the `resolveToRelativePath`\n * field is consulted. Declared as a local interface (not a `Pick`)\n * so Task 0 can land independently of the broader `MemorySink` widening\n * in Task 1; once both are in place the broader `MemorySink` interface\n * matches this shape structurally and callers pass the full sink record.\n */\ninterface SinkLike {\n resolveToRelativePath: string;\n}\n\n/**\n * Compute an absolute path inside a memory sink. The caller supplies the\n * vault-absolute path (resolved through `VaultManager`); the sink record\n * contributes its vault-relative folder; the optional `relativeSubpath`\n * is appended inside.\n *\n * Example:\n * pathInSink(\"/v/atlas\", { resolveToRelativePath: \"_memory/\" }, \"obs/foo.md\")\n * → \"/v/atlas/_memory/obs/foo.md\"\n *\n * FS-bound — OS-native separators. Use the return value for `fs.*` calls\n * only. For DocId-resource comparisons or SQL `LIKE` prefix lookups, see\n * `vaultRelativeInSink`.\n */\nexport function pathInSink(\n vaultAbsolutePath: string,\n sink: SinkLike,\n relativeSubpath = \"\",\n): string {\n return path.join(vaultAbsolutePath, sink.resolveToRelativePath, relativeSubpath);\n}\n\n/**\n * Defensive: convert any backslash to forward-slash. Inputs are normally\n * byte-clean (the sink-handle parser at `src/memory/sink.ts` refuses\n * backslashes inside segments), but `relativeSubpath` and other callsites\n * may originate from caller-controlled inputs — normalize so the\n * forward-slash invariant always holds on output.\n */\nfunction normalizeToForwardSlash(s: string): string {\n return s.includes(\"\\\\\") ? s.replace(/\\\\/g, \"/\") : s;\n}\n\n/**\n * Vault-relative POSIX join — emits forward-slash regardless of\n * `process.platform`. Use for ANY value that will be compared against a\n * DocId resource, used as a SQL `LIKE` prefix against `notes.path`, or\n * threaded into `MemorySinkRegistry.findSinkContaining` lookups.\n *\n * On POSIX this is functionally equivalent to `path.join` minus the\n * leading vault root; on Windows it differs because `path.join` would\n * have emitted backslashes that downstream forward-slash comparisons\n * miss (CR-03 — silent Guard B no-op on Windows).\n *\n * Caller-supplied backslashes in any segment are normalized to forward-\n * slash defensively, so the output invariant holds even if a future\n * caller smuggles a backslash in.\n *\n * Comparison-bound — DO NOT pass the return value to `fs.*` calls; use\n * `joinVaultPath` or `pathInSink` for FS callers.\n */\nexport function joinVaultPathPosix(...segments: string[]): string {\n return path.posix.join(...segments.map(normalizeToForwardSlash));\n}\n\n/**\n * Forward-slash form of a path INSIDE a sink, relative to the vault root.\n * Used for any caller that compares against a DocId resource (always\n * forward-slash by `DOC_ID_PATTERN` invariant in\n * `src/adapters/registry.ts`) or feeds a SQL `LIKE '%'` lookup\n * against `notes.path` (forward-slash by indexer convention).\n *\n * Round-trip property: for any `(vault, sink, subpath)` triple,\n * `vaultRelativeInSink(sink, subpath)` is byte-equal with the `resource`\n * portion of `decomposeDocId(formatDocId(\"obsidian-fs\", vault, rel))`.\n *\n * Edge cases:\n * - `relativeSubpath = \"\"` (default) returns the sink folder with its\n * trailing slash preserved (e.g. `\"_memory/\"`). This matches the\n * `findSinkContaining` policy where `sink.resolveToRelativePath`\n * includes its trailing slash so prefix matches respect folder\n * boundaries (`_memory/` matches `_memory/foo.md` but NOT\n * `_memory-staging/foo.md`).\n * - Caller-supplied backslashes in `relativeSubpath` are normalized to\n * forward-slash before joining.\n *\n * Comparison-bound — DO NOT pass the return value to `fs.*` calls; use\n * `pathInSink` for FS callers.\n */\nexport function vaultRelativeInSink(sink: SinkLike, relativeSubpath = \"\"): string {\n if (relativeSubpath === \"\") return normalizeToForwardSlash(sink.resolveToRelativePath);\n return joinVaultPathPosix(sink.resolveToRelativePath, relativeSubpath);\n}\n","/**\n * Read a memory-contract YAML file from disk.\n *\n * The disk read lives here (under `src/adapters/delivery/obsidian-fs/`)\n * because ADR-002 I-2 confines `node:fs` to the licensed adapter\n * directories. The pure contract logic (`src/memory/contract/`) calls\n * this helper through the `loader.ts` indirection so it remains\n * filesystem-ignorant.\n *\n * Path resolution uses `joinVaultPath` from this same directory's\n * `path.ts` so the seam-preservation CI grep stays happy.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { joinVaultPath } from \"./path.js\";\n\n/** Marker error: contract YAML file does not exist at the resolved path. */\nexport class ContractYamlNotFoundError extends Error {\n override readonly name = \"ContractYamlNotFoundError\";\n constructor(\n public readonly path: string,\n message?: string,\n ) {\n super(message ?? `Contract YAML not found at ${path}`);\n }\n}\n\n/**\n * Read `/_contracts/memory/.yaml` as a UTF-8\n * string. Throws `ContractYamlNotFoundError` on ENOENT (so the caller\n * can distinguish \"no file\" from \"file present but malformed\").\n *\n * Returns both the resolved absolute path (for diagnostics) and the\n * raw text contents.\n */\nexport async function readContractYaml(\n vaultPath: string,\n contractName: string,\n): Promise<{ path: string; text: string }> {\n const yamlPath = joinVaultPath(vaultPath, `_contracts/memory/${contractName}.yaml`);\n try {\n const text = await readFile(yamlPath, \"utf-8\");\n return { path: yamlPath, text };\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n throw new ContractYamlNotFoundError(yamlPath);\n }\n throw err;\n }\n}\n","/**\n * Zod schema for the `_contracts/memory/.yaml` file format.\n *\n * The YAML on disk declares which property keys a `MemoryContract`\n * requires (with their allowed enum values, types, and defaults). The\n * loader (`./loader.ts`) reads the file, parses it via `yaml@^2.9.x`,\n * validates the parsed object against `MemoryContractYamlSchema`, then\n * walks the validated tree to BUILD a Zod `z.object(...)` schema for\n * validating `Document.properties` payloads at write time.\n *\n * The two-phase pipeline (validate-the-contract-shape, then\n * build-the-property-validator) keeps the contract grammar\n * declaratively validated by Zod itself — no hand-rolled walker.\n */\n\nimport { z } from \"zod\";\n\n/**\n * A single property rule. `type` is the field's Zod-mapped value type;\n * `allowed` is an optional enum constraint; `default` is a literal\n * default; `items` is the per-element rule for arrays; `min_length`\n * applies to strings or arrays.\n */\nexport const PropertyRuleSchema = z.object({\n type: z.enum([\"string\", \"datetime\", \"array\", \"doc_id\", \"number\", \"boolean\", \"reference\", \"date\"]),\n allowed: z.array(z.string()).optional(),\n default: z.unknown().optional(),\n items: z.object({ type: z.string() }).optional(),\n min_length: z.number().optional(),\n /** When true, the property accepts `null` as a sentinel value (in\n * addition to whatever `type` says). Used for required-but-null-by-\n * default properties like `superseded_by` on active observations. */\n nullable: z.boolean().optional(),\n});\n\nexport type PropertyRule = z.infer;\n\n/**\n * A cross-field rule. `when` is a simple boolean expression on\n * properties (e.g. `status == 'superseded'`); `require` is a\n * comma-separated or `&&`-joined list of keys that MUST be present and\n * non-empty when `when` evaluates true.\n *\n * Phase 2 ships a hardcoded interpretation for the only currently\n * required rule (status=superseded → superseded_by + superseded_reason\n * both non-empty); the schema accepts the declarative form so future\n * contracts (Phase 5+) can add their own without code changes.\n */\nexport const CrossFieldRuleSchema = z.object({\n when: z.string(),\n require: z.string(),\n});\n\nexport type CrossFieldRule = z.infer;\n\n/**\n * Top-level contract shape. Mirrors the YAML in\n * `_contracts/memory/default-memory-v1.yaml`.\n */\nexport const MemoryContractYamlSchema = z.object({\n name: z.string().min(1),\n version: z.string().default(\"1.0\"),\n required_properties: z.record(z.string(), PropertyRuleSchema),\n optional_properties: z.record(z.string(), PropertyRuleSchema).default({}),\n cross_field_rules: z.array(CrossFieldRuleSchema).default([]),\n naming: z.object({\n strategy: z.enum([\"caller-provided\", \"date-slug\", \"adapter-assigned\"]),\n pattern: z.string().optional(),\n }),\n});\n\nexport type MemoryContractYaml = z.infer;\n","/**\n * YAML → Zod-validated → `MemoryContract` pipeline.\n *\n * The disk read is delegated to\n * `src/adapters/delivery/obsidian-fs/contract-yaml-read.ts` so this\n * module remains free of `node:fs` / `node:path` imports (ADR-002 I-2\n * confines those to the licensed adapter directory).\n *\n * Cache: contracts are cached by name on first successful load. The\n * cache key is the contract `name` (not the file path), so a contract\n * with the same name in different vaults would conflict — in Phase 2\n * this is fine because contracts are server-process global; Phase 5/6\n * may need to introduce per-vault scoping.\n *\n * Public symbols are re-exported from `./index.ts`.\n */\n\nimport { parse as parseYaml } from \"yaml\";\nimport { z, type ZodType } from \"zod\";\nimport {\n readContractYaml,\n ContractYamlNotFoundError,\n} from \"../../adapters/delivery/obsidian-fs/contract-yaml-read.js\";\nimport { MemoryContractYamlSchema, type MemoryContractYaml, type PropertyRule } from \"./schema.js\";\nimport type { MemoryContract } from \"./types.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public errors\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class MemoryContractNotFoundError extends Error {\n override readonly name = \"MemoryContractNotFoundError\";\n}\n\nexport class MemoryContractInvalidError extends Error {\n override readonly name = \"MemoryContractInvalidError\";\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Module-level cache (process lifetime)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst contractCache = new Map();\n\n/** Test-only: drop the cache so a `beforeEach` can re-seed contracts. */\nexport function __clearContractCache(): void {\n contractCache.clear();\n}\n\n/** Internal: insert a contract into the cache by name. Used by `index.ts`. */\nexport function __cacheContract(name: string, contract: MemoryContract): void {\n contractCache.set(name, contract);\n}\n\n/** Internal: read a contract from the cache by name. Returns `undefined`. */\nexport function __getCachedContract(name: string): MemoryContract | undefined {\n return contractCache.get(name);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Schema builder — converts a validated MemoryContractYaml into a Zod\n// schema for validating `Document.properties` payloads at write time.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Map a YAML `PropertyRule` to a Zod schema for a single property\n * value. The mapping is intentionally narrow — Phase 2 supports the\n * types listed in `PropertyRuleSchema` and nothing more. Future\n * contracts that need new types must extend `schema.ts` first.\n *\n * `key` is threaded in so fail-closed diagnostics (WR-01, WR-02) can\n * name the offending property in their error messages.\n */\nfunction ruleToZod(rule: PropertyRule, key: string): ZodType {\n let schema: ZodType;\n switch (rule.type) {\n case \"string\":\n schema = rule.min_length !== undefined ? z.string().min(rule.min_length) : z.string();\n break;\n case \"datetime\":\n case \"date\":\n schema = z.string().datetime({ offset: true });\n break;\n case \"array\": {\n // WR-01: Honor `items.type`. The shipped default-memory-v1\n // contract uses `items: { type: reference }` for the `evidence`\n // array, so `reference` (and its alias `doc_id`) is accepted and\n // mapped to `z.string()` at the element level — references are\n // structurally strings at the Zod layer; DocId parsing happens\n // separately when callers need branded values. `string` and\n // `number` are also supported. Any other element type (including\n // `date`, `datetime`, `boolean`, nested `array`) is rejected at\n // load time so contract authors get a fail-loud signal.\n //\n // When `items` is omitted entirely the default is `string` to\n // preserve the pre-WR-01 behavior on legacy contracts.\n const itemType = rule.items?.type ?? \"string\";\n switch (itemType) {\n case \"string\":\n schema = z.array(z.string());\n break;\n case \"number\":\n schema = z.array(z.number());\n break;\n case \"reference\":\n case \"doc_id\":\n schema = z.array(z.string());\n break;\n default:\n throw new MemoryContractInvalidError(\n `Property \"${key}\" has unsupported items.type \"${itemType}\". ` +\n `Phase 2 supports array items of type 'string', 'number', or 'reference'.`,\n );\n }\n break;\n }\n case \"reference\":\n case \"doc_id\":\n // Reference / doc_id is structurally a string at the Zod level;\n // the validator performs DocId parsing separately when callers\n // need branded values.\n schema = z.string();\n break;\n case \"number\":\n schema = z.number();\n break;\n case \"boolean\":\n schema = z.boolean();\n break;\n default:\n // The schema validator already constrained `rule.type` to the\n // enum above, so this branch is unreachable at runtime. The\n // exhaustive check helps the TypeScript compiler.\n schema = z.unknown();\n break;\n }\n if (rule.allowed && rule.allowed.length > 0) {\n // WR-02: `allowed` is declared as `z.array(z.string())` in\n // schema.ts — it is string-only by design. Silently overriding a\n // non-string declared type with a string enum produces semantic\n // type drift, so reject the combination at load time with a\n // diagnostic naming the offending key and declared type.\n if (rule.type !== \"string\") {\n throw new MemoryContractInvalidError(\n `Property \"${key}\" declares type \"${rule.type}\" with allowed=[...]. ` +\n `'allowed' is string-only — either declare type:'string' or remove 'allowed'.`,\n );\n }\n // `z.enum` requires a non-empty tuple, which the YAML schema does\n // not enforce at parse time, so we guard with a length check above.\n schema = z.enum(rule.allowed as [string, ...string[]]);\n }\n if (rule.nullable) {\n schema = schema.nullable();\n }\n if (rule.default !== undefined) {\n schema = schema.default(rule.default);\n }\n return schema;\n}\n\n/**\n * Build the `propertiesSchema` Zod schema from a validated YAML\n * contract. Required keys are added as required object members;\n * optional keys are wrapped in `.optional()`; cross-field rules are\n * encoded via `.superRefine()`.\n */\nfunction buildPropertiesSchema(yaml: MemoryContractYaml): ZodType {\n const shape: Record = {};\n for (const [key, rule] of Object.entries(yaml.required_properties)) {\n shape[key] = ruleToZod(rule, key);\n }\n for (const [key, rule] of Object.entries(yaml.optional_properties)) {\n shape[key] = ruleToZod(rule, key).optional();\n }\n let obj: ZodType = z.object(shape).passthrough();\n\n if (yaml.cross_field_rules.length > 0) {\n // WR-03: Validate every `when` expression eagerly at load time so\n // unsupported shapes (typos, `!=`, `=`, double-quoted values,\n // multi-clause) surface as a `MemoryContractInvalidError` instead\n // of being silently dropped at runtime. The Phase 2 DSL supports a\n // single declarative form: ` == ''` (single-quoted\n // value, `==` operator).\n const WHEN_RE = /^([A-Za-z_][A-Za-z0-9_]*)\\s*==\\s*'([^']+)'$/;\n for (const rule of yaml.cross_field_rules) {\n if (!WHEN_RE.test(rule.when)) {\n throw new MemoryContractInvalidError(\n `Cross-field rule has unsupported 'when' expression: ${JSON.stringify(rule.when)}. ` +\n `Phase 2 supports a single form: \\` == ''\\` (single-quoted value, '==' operator). ` +\n `Rule: ${JSON.stringify(rule)}`,\n );\n }\n }\n\n obj = (obj as z.ZodObject>).superRefine((data, ctx) => {\n for (const rule of yaml.cross_field_rules) {\n // `require` is ` && ` or single key. The\n // load-time check above guarantees `when` matches the regex,\n // so the exec below cannot fail — but we keep the defensive\n // `continue` to satisfy `noUncheckedIndexedAccess`.\n const whenMatch = WHEN_RE.exec(rule.when);\n if (!whenMatch) continue;\n const [, whenKey, whenValue] = whenMatch;\n if (whenKey === undefined || whenValue === undefined) continue;\n if ((data as Record)[whenKey] !== whenValue) continue;\n const requiredKeys = rule.require\n .split(/&&|,/)\n .map((k) => k.trim())\n .filter(Boolean);\n for (const key of requiredKeys) {\n const value = (data as Record)[key];\n if (value === undefined || value === null || value === \"\") {\n ctx.addIssue({\n code: \"custom\",\n path: [key],\n message: `Required when ${whenKey} == '${whenValue}'`,\n });\n }\n }\n }\n });\n }\n return obj;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public loader\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Load a `MemoryContract` from `/_contracts/memory/.yaml`.\n * Cached on success; re-loading the same name returns the cached\n * instance (referential equality holds).\n *\n * Errors:\n * - `MemoryContractNotFoundError` — file does not exist.\n * - `MemoryContractInvalidError` — file exists but cannot be parsed\n * or fails Zod validation. The error message includes the file\n * path for diagnostics.\n */\nexport async function loadContractFromDisk(\n name: string,\n vaultPath: string,\n): Promise {\n const cached = contractCache.get(name);\n if (cached) return cached;\n\n let yamlPath: string;\n let text: string;\n try {\n const read = await readContractYaml(vaultPath, name);\n yamlPath = read.path;\n text = read.text;\n } catch (err) {\n if (err instanceof ContractYamlNotFoundError) {\n throw new MemoryContractNotFoundError(`Memory contract \"${name}\" not found at ${err.path}`);\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = parseYaml(text);\n } catch (err) {\n throw new MemoryContractInvalidError(\n `Failed to parse YAML at ${yamlPath}: ${(err as Error).message}`,\n );\n }\n\n let validated: MemoryContractYaml;\n try {\n validated = MemoryContractYamlSchema.parse(parsed);\n } catch (err) {\n throw new MemoryContractInvalidError(\n `Contract at ${yamlPath} failed validation: ${(err as Error).message}`,\n );\n }\n\n const propertiesSchema = buildPropertiesSchema(validated);\n const contract: MemoryContract = {\n name: validated.name,\n version: validated.version,\n propertiesSchema,\n requiredKeys: Object.keys(validated.required_properties),\n naming: validated.naming,\n };\n contractCache.set(name, contract);\n // ALSO cache under the contract's declared `name` field (which may\n // differ from the file-stem `name` parameter — e.g. the shipped\n // `default-memory-v1` YAML always self-declares as\n // `default-memory-v1` regardless of the file name used to load it).\n if (validated.name !== name) {\n contractCache.set(validated.name, contract);\n }\n return contract;\n}\n","/**\n * Public surface for the `MemoryContract` subsystem.\n *\n * Phase 2 ships:\n * - `DEFAULT_MEMORY_V1` — hardcoded baseline matching MEMORY_CONTRACT.md.\n * - `getContract(name)` — synchronous lookup from the in-process\n * cache; returns the baseline for `\"default-memory-v1\"`, or any\n * previously-`loadContractFromDisk`-ed contract; throws otherwise.\n * - `loadContractFromDisk(name, vaultPath)` — async YAML loader.\n * - `MemoryContract` type.\n * - `MemoryContractNotFoundError` / `MemoryContractInvalidError`\n * classes for `instanceof` checks.\n *\n * Phase 5+ may add per-vault scoping, mtime-based cache invalidation,\n * or a higher-level \"contracts directory\" loader; this surface stays\n * stable until then.\n */\n\nimport { DEFAULT_MEMORY_V1 } from \"./default-v1.js\";\nimport { DEFAULT_BRIEF_V1 } from \"./default-brief-v1.js\";\nimport {\n __cacheContract,\n __getCachedContract,\n loadContractFromDisk,\n MemoryContractInvalidError,\n MemoryContractNotFoundError,\n} from \"./loader.js\";\nimport type { MemoryContract } from \"./types.js\";\n\n// Pre-seed the cache with the hardcoded baselines so `getContract` can\n// look them up uniformly without a `if (name === \"default-memory-v1\")`\n// special case at every call site.\n__cacheContract(\"default-memory-v1\", DEFAULT_MEMORY_V1);\n// Phase 5 / Pitfall 1 resolution: register a separate contract for\n// briefs with widened status enum and brief-specific required keys.\n// See ADR-005 §\"New default-brief-v1 contract\".\n__cacheContract(\"default-brief-v1\", DEFAULT_BRIEF_V1);\n\n/**\n * Synchronous lookup. Returns the named contract from the in-process\n * cache:\n * - `\"default-memory-v1\"` — always available (pre-seeded).\n * - Any name previously loaded via `loadContractFromDisk(name, ...)`.\n *\n * Throws a helpful diagnostic when the name is unknown.\n */\nexport function getContract(name: string): MemoryContract {\n const cached = __getCachedContract(name);\n if (cached) return cached;\n throw new Error(\n `Unknown memory contract: \"${name}\". ` +\n `Known contracts: default-memory-v1${otherCachedNames(name)}. ` +\n `Call loadContractFromDisk(name, vaultPath) first to register a contract.`,\n );\n}\n\nfunction otherCachedNames(excluding: string): string {\n // For diagnostics only — list any names cached besides\n // default-memory-v1 / default-brief-v1 and the excluded name;\n // helps users notice typos when they have many contracts loaded.\n const names: string[] = [];\n // Pull from the cache through the loader's internal accessor — the\n // cache map itself is intentionally not exported.\n for (const candidate of [\"default-memory-v1\", \"default-brief-v1\"]) {\n if (candidate === excluding) continue;\n if (__getCachedContract(candidate)) names.push(candidate);\n }\n return names.length > 0 ? `, ${names.join(\", \")}` : \"\";\n}\n\n// IN-02 closure: `__clearContractCache` is intentionally NOT exported from\n// this public barrel. The test-only symbol lives at\n// `./__testing__.ts` and is imported via the deep path from test files only.\n// Production callers that import from this barrel cannot accidentally clear\n// the cache at runtime — the import path itself is the access marker.\n\nexport {\n DEFAULT_MEMORY_V1,\n DEFAULT_BRIEF_V1,\n loadContractFromDisk,\n MemoryContractInvalidError,\n MemoryContractNotFoundError,\n};\nexport type { MemoryContract };\n","/**\n * `MemorySinkHandle` parser + sentinel filename constant.\n *\n * Per ADR-004 §\"MemorySink handle shape\", a `MemorySinkHandle` is a\n * fully-formed URI of the shape `obsidian-fs:////` —\n * lowercase scheme, non-empty authority, non-empty resource, **trailing\n * slash required** (per ADR-001 §I-6 canonical-serialization). The\n * trailing slash distinguishes a sink handle (a folder address) from a\n * `DocId` (a file address); a folder handle that did not require a\n * trailing slash could be confused with a parent-directory `DocId`.\n *\n * The brand-cast escape hatch lives ONLY inside the IIFE below; this\n * file is the SOLE module that performs it for `MemorySinkHandle`.\n * Only the validating `parseMemorySinkHandle` is exported. The IIFE\n * pattern is identical to `parseDocId` in `src/adapters/registry.ts`.\n *\n * `SENTINEL_FILENAME` is the single canonical name for the sink\n * sentinel file (`.memory-sink`). The sentinel mechanics live in\n * `src/adapters/delivery/obsidian-fs/sentinel.ts` (the only place\n * `node:fs` is licensed for sentinel work, per ADR-002 I-2); this\n * module just declares the filename so other modules don't have to\n * hard-code the string.\n *\n * Phase 2 scope: only `obsidian-fs://` handles are accepted. Future\n * adapters (notion-api, etc.) may add their own schemes; until then,\n * a non-obsidian-fs handle is a config error and the parser rejects.\n */\n\nimport type { MemorySinkHandle } from \"../types.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle pattern + IIFE-closed mint\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Canonical MemorySinkHandle shape: `obsidian-fs:////`.\n *\n * - scheme: `obsidian-fs` (Phase 2 scope; future adapters add their own).\n * - authority: lowercase ASCII alphanumeric + dashes; starts alphanumeric.\n * - resource: at least one non-whitespace character segment;\n * - MUST end with a `/`.\n *\n * Examples that PASS: `obsidian-fs://atlas/_memory/`,\n * `obsidian-fs://atlas/_memory/inbox/`.\n * Examples that FAIL: `obsidian-fs://atlas/_memory` (no trailing slash),\n * `OBSIDIAN-FS://x/y/` (uppercase scheme),\n * `obsidian-fs:/atlas/_memory/` (single slash),\n * `notion-api://...` (non-obsidian-fs scheme, Phase 2).\n */\nexport const MEMORY_SINK_HANDLE_PATTERN = /^obsidian-fs:\\/\\/[a-z0-9][a-z0-9-]*\\/[^\\s]+\\/$/;\n\n/**\n * Allowed characters inside a single path segment of the resource portion\n * of a `MemorySinkHandle`. ASCII alphanumeric plus the three filename-safe\n * punctuation characters (`.`, `_`, `-`). Critically, the literal `.` is\n * permitted INSIDE a segment (so file extensions and dotfiles are fine),\n * but the per-segment whitelist used in `parseMemorySinkHandle` rejects\n * the bare-dot (`.`) and bare-dot-dot (`..`) segments that `path.normalize`\n * / `path.join` would otherwise collapse and let a sink escape its vault.\n *\n * The character class is intentionally narrower than the top-level\n * `MEMORY_SINK_HANDLE_PATTERN` (which only refuses whitespace) so that\n * the parser refuses anything `path.join` could reshape: backslashes,\n * leading-slash empties, control characters, and Unicode lookalikes.\n *\n * Per CR-01 (Plan 02-09): this is the substrate the memory-namespace\n * safety invariant rests on. Downstream `pathInSink` is safe-by-construction\n * precisely because the parser refuses any traversal-shaped input here.\n */\nconst SEGMENT_PATTERN = /^[A-Za-z0-9._\\-]+$/;\n\nconst { parseMemorySinkHandle } = (() => {\n // `mint` is the unsafe brand cast; closed inside this IIFE so it\n // cannot escape. We export only the validating `parse`.\n const mint = (s: string): MemorySinkHandle => s as MemorySinkHandle;\n const parse = (rawInput: string): MemorySinkHandle => {\n // Normalize to NFC BEFORE the regex test. This forecloses Unicode\n // tricks where decomposed-vs-precomposed equivalents differ\n // byte-for-byte: an attacker cannot smuggle a `..` past the parser\n // by spelling it with a combining sequence that re-composes inside\n // the per-segment check. For ASCII inputs NFC is a fixed point, so\n // this is a no-op on the positive controls.\n const s = typeof rawInput === \"string\" ? rawInput.normalize(\"NFC\") : rawInput;\n if (!MEMORY_SINK_HANDLE_PATTERN.test(s)) {\n throw new Error(\n `Invalid MemorySinkHandle: ${JSON.stringify(s)}. ` +\n `Expected obsidian-fs://// (trailing slash required).`,\n );\n }\n // Extract the resource portion: everything after the authority's\n // trailing slash and before the handle's trailing slash. The regex\n // above guarantees the shape, so the slice math is safe.\n //\n // obsidian-fs:////\n // ^ ^ ^\n // authStart authEnd trailing\n //\n // `authStart` is fixed at the length of \"obsidian-fs://\". `authEnd`\n // is the first `/` AT OR AFTER `authStart` (the regex guarantees\n // one exists). The resource is `[authEnd+1, length-1)` so the\n // trailing slash is excluded.\n const authStart = \"obsidian-fs://\".length;\n const authEnd = s.indexOf(\"/\", authStart);\n const resource = s.slice(authEnd + 1, s.length - 1);\n for (const segment of resource.split(\"/\")) {\n if (\n segment.length === 0 ||\n segment === \".\" ||\n segment === \"..\" ||\n !SEGMENT_PATTERN.test(segment)\n ) {\n throw new Error(\n `Invalid MemorySinkHandle: ${JSON.stringify(s)}. ` +\n `Resource path segment ${JSON.stringify(segment)} is not allowed: ` +\n `only [A-Za-z0-9._-]+ segments are permitted ` +\n `(no \"..\", no \".\", no empty segments, no backslashes, no control characters).`,\n );\n }\n }\n return mint(s);\n };\n return { parseMemorySinkHandle: parse };\n})();\n\nexport { parseMemorySinkHandle };\n\n/**\n * Construct a `MemorySinkHandle` from its parts and validate via\n * `parseMemorySinkHandle`. Convenience helper so callers do not\n * concatenate by hand. The caller is responsible for ensuring\n * `resource` ends with a trailing slash; the parser rejects otherwise.\n */\nexport function formatMemorySinkHandle(\n scheme: string,\n authority: string,\n resource: string,\n): MemorySinkHandle {\n return parseMemorySinkHandle(`${scheme}://${authority}/${resource}`);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sentinel filename — canonical declaration\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The single canonical filename for the memory-sink sentinel. Per\n * ADR-004 §\"Sentinel file — `.memory-sink`\", every folder serving as a\n * memory sink MUST contain a file with this name; the registry refuses\n * to resolve a sink against a folder that lacks the sentinel.\n *\n * The sentinel's contents are informational only (timestamp + sink\n * name); the *presence* is the gate. The actual file write/read\n * mechanics live in `src/adapters/delivery/obsidian-fs/sentinel.ts`\n * (the only file licensed to call `node:fs` for sentinel work per\n * ADR-002 I-2).\n */\nexport const SENTINEL_FILENAME = \".memory-sink\";\n","/**\n * Memory-sink sentinel mechanics.\n *\n * Per ADR-004 §\"Sentinel file — `.memory-sink`\", every folder serving\n * as a memory sink MUST contain a `.memory-sink` file at its root.\n * The handle parser refuses to resolve a sink against a folder that\n * lacks the sentinel. This module is the SOLE file licensed to call\n * `node:fs` for sentinel-write / sentinel-check work (ADR-002 I-2 +\n * I-3 confine `node:fs` and `node:path` to\n * `src/adapters/delivery/obsidian-fs/`).\n *\n * Provisioning policy (ADR-004 §\"Provisioning\"; tightened by Plan 02-10\n * to close CR-02):\n * - Empty folder OR folder with only sink-expected content\n * (observations/, _briefs/, status-updates/, .memory-sink): write\n * the sentinel. Plain `.md` files at the sink root are NOT in the\n * allow-list — they almost certainly are user notes and the sink\n * must refuse to absorb them.\n * - Folder with unrelated content (any plain `.md`, `.txt`, etc.):\n * throw `SinkProvisioningError`. The user must either move the\n * foreign content out or change the configured sink handle.\n * - Sentinel already exists: no-op (idempotent).\n * - Folder does not exist: create with `recursive: true`, then\n * write the sentinel.\n *\n * Path joins go through `pathInSink` / `joinVaultPath` from this\n * directory's `path.ts` — the SOLE licensed `path.join` site for\n * sink/vault path resolution in Phase 2.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport type { MemorySink } from \"../../../types.js\";\nimport { SENTINEL_FILENAME as SINK_SENTINEL_FILENAME } from \"../../../memory/sink.js\";\nimport { pathInSink } from \"./path.js\";\n\n/** Re-export so `src/server.ts` / tests can import from this barrel. */\nexport const SENTINEL_FILENAME = SINK_SENTINEL_FILENAME;\n\n/**\n * Provisioning error — thrown when a folder cannot be safely labeled as\n * a memory sink because it already contains unrelated user content.\n */\nexport class SinkProvisioningError extends Error {\n override readonly name = \"SinkProvisioningError\";\n readonly code = \"SINK_PROVISION_UNSAFE\";\n constructor(\n public readonly sinkName: string,\n public readonly absoluteFolderPath: string,\n public readonly offendingEntries: readonly string[],\n ) {\n super(\n `Memory sink \"${sinkName}\" target folder ${absoluteFolderPath} ` +\n `contains unrelated user content (${offendingEntries.join(\", \")}). ` +\n `Refusing to label as a sink. Move user content out, or change the ` +\n `[[memory_sinks]] handle.`,\n );\n }\n}\n\n/**\n * Heuristic: returns true if an entry name \"looks like\" expected\n * memory-sink content. The allowed list is intentionally narrow:\n * - the `.memory-sink` sentinel itself,\n * - the three known sink subfolders (`observations`, `_briefs`,\n * `status-updates`).\n *\n * Plain `.md` files at the sink root are NOT expected — they are\n * almost certainly user notes. Forcing a SinkProvisioningError here\n * surfaces the misconfiguration loudly instead of silently absorbing\n * the folder (CR-02 — gap-closure Plan 02-10).\n */\nfunction isExpectedSinkContent(entry: string): boolean {\n if (entry === SENTINEL_FILENAME) return true;\n if (entry === \"observations\" || entry === \"_briefs\" || entry === \"status-updates\") {\n return true;\n }\n return false;\n}\n\n/**\n * Build the three-line sentinel content per RESEARCH §Q10.\n * Format is informational only — the parser does not validate\n * contents; the *presence* of the file is the gate.\n */\nfunction formatSentinelContent(args: { sinkName: string; version: string }): string {\n const ts = new Date().toISOString();\n return [\n `created_at: ${ts}`,\n `sink_name: ${args.sinkName}`,\n `vault_memory_version: ${args.version}`,\n \"\",\n ].join(\"\\n\");\n}\n\n/**\n * Provision (or no-op) a memory sink at `/`.\n *\n * - If the sentinel already exists, return immediately.\n * - If the folder does not exist, create it recursively and write the sentinel.\n * - If the folder exists and is empty (or contains only expected content),\n * write the sentinel.\n * - If the folder exists and contains foreign content, throw `SinkProvisioningError`.\n */\nexport async function provisionSink(\n sink: MemorySink,\n vaultAbsolutePath: string,\n opts: { version: string },\n): Promise {\n const folder = pathInSink(vaultAbsolutePath, sink);\n const sentinelPath = pathInSink(vaultAbsolutePath, sink, SENTINEL_FILENAME);\n\n // Fast path: sentinel already in place.\n try {\n await fs.access(sentinelPath);\n return;\n } catch {\n // Sentinel missing — fall through to creation logic.\n }\n\n let folderExists = true;\n let entries: string[] = [];\n try {\n entries = await fs.readdir(folder);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n folderExists = false;\n } else {\n throw err;\n }\n }\n\n if (!folderExists) {\n await fs.mkdir(folder, { recursive: true });\n await fs.writeFile(\n sentinelPath,\n formatSentinelContent({ sinkName: sink.name, version: opts.version }),\n \"utf-8\",\n );\n return;\n }\n\n // Folder exists. Check it contains only expected sink content.\n const foreign = entries.filter((e) => !isExpectedSinkContent(e));\n if (foreign.length > 0) {\n throw new SinkProvisioningError(sink.name, folder, foreign);\n }\n await fs.writeFile(\n sentinelPath,\n formatSentinelContent({ sinkName: sink.name, version: opts.version }),\n \"utf-8\",\n );\n}\n\n/**\n * Sentinel-check failure for non-ENOENT errno codes. Distinct from the\n * \"sentinel missing\" case so the caller (preflight in\n * `ObsidianFsDelivery`) can surface the underlying errno (EACCES, EIO,\n * ENAMETOOLONG, EPERM, …) instead of the misleading \"restart the\n * server\" suggestion attached to `sentinel_missing` (WR-06 — gap-closure\n * Plan 02-10). Consumed via `WriteConflict.reason = \"sentinel_check_failed\"`\n * (literal declared by Plan 02-13 in wave 9 in `../types.ts`).\n */\nexport class SinkSentinelCheckError extends Error {\n override readonly name = \"SinkSentinelCheckError\";\n readonly code = \"SINK_SENTINEL_CHECK_FAILED\";\n constructor(\n public readonly sinkName: string,\n public readonly underlyingCode: string,\n message: string,\n ) {\n super(message);\n }\n}\n\n/**\n * Return true iff the sentinel exists under the resolved sink folder.\n * Cheap (one `fs.access`) — safe to call on every write per ADR-004\n * §\"Runtime check on every write\".\n *\n * Errno discipline (WR-06 closure):\n * - ENOENT → return `false` (sentinel literally absent).\n * - Anything else (EACCES, EIO, ENAMETOOLONG, EPERM, …) → throw a\n * `SinkSentinelCheckError` carrying the original errno code, so\n * the caller can report it accurately rather than collapsing to\n * \"sentinel missing — restart the server\".\n */\nexport async function assertSentinelExists(\n sink: MemorySink,\n vaultAbsolutePath: string,\n): Promise {\n const sentinelPath = pathInSink(vaultAbsolutePath, sink, SENTINEL_FILENAME);\n try {\n await fs.access(sentinelPath);\n return true;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") return false;\n throw new SinkSentinelCheckError(\n sink.name,\n code ?? \"UNKNOWN\",\n `Sentinel check for MemorySink \"${sink.name}\" at ${sentinelPath} failed: ${(err as Error).message}`,\n );\n }\n}\n\n/**\n * Lower-level discovery probe used by server bootstrap auto-discovery\n * (Plan 02-03b). Returns true iff `//.memory-sink`\n * exists. Confined to this adapter directory because it touches `node:fs`\n * (ADR-002 I-2). Server bootstrap calls this through `joinVaultPath` so\n * the path-join stays inside the licensed adapter dir too.\n */\nexport async function sentinelExistsAt(vaultRoot: string, relPath: string): Promise {\n // We intentionally do NOT use pathInSink here — auto-discovery probes a\n // candidate folder BEFORE any sink record exists, so the join must\n // operate on a plain relative path.\n const probe = `${vaultRoot.endsWith(\"/\") ? vaultRoot.slice(0, -1) : vaultRoot}/${relPath.replace(/^\\//, \"\")}/${SENTINEL_FILENAME}`;\n try {\n await fs.access(probe);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * ObsidianFsDelivery — v2 DeliveryAdapter implementation. Wraps the relocated\n * write/atomic-write modules (writeNote, deleteNote) and exposes them under\n * the ADR-002 §DeliveryAdapter contract.\n *\n * I-2/I-3/I-4/I-6 (raw fs.*, raw path.*, gray-matter, fs.writeFile/unlink/rename)\n * are ALLOWED here — this directory is the only legitimate home for write-side\n * filesystem operations on obsidian-fs vaults.\n *\n * Phase 2 (MEM-01..12) will inject MemorySink guards A (provenance required)\n * and B (source:agent outside configured sink rejected) at the entry of\n * `write()` WITHOUT changing the public method shape. The TSDoc note on\n * `DeliveryAdapter.write()` (see ../types.ts) signals that seam; this Phase 1\n * implementation has ONLY the existing `write_enabled` flag + safeJoinInsideVault\n * path safety.\n *\n * Backwards-compat: the legacy `writeNote` / `deleteNote` / `atomicWriteFile` /\n * `safeJoinInsideVault` / `OutsideVaultError` symbols are still re-exported\n * for v1 handlers that haven't been refactored to call the facade directly.\n * The DeliveryAdapter facade is the preferred entry point for v2 consumers.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport matter from \"gray-matter\";\nimport type {\n DeliveryAdapter,\n DeliveryCapabilities,\n WriteOptions,\n WriteResult as V2WriteResult,\n UpdateResult as V2UpdateResult,\n DeleteResult as V2DeleteResult,\n} from \"../types.js\";\nimport type { Document, DocId, MemorySink, SourceHandle } from \"../../../types.js\";\nimport type { Vault } from \"../../../vault/index.js\";\nimport { parseSourceHandle } from \"../../registry.js\";\nimport {\n writeNote as writeNoteInternal,\n deleteNote as deleteNoteInternal,\n type WriteResult as V1WriteResult,\n} from \"./write.js\";\nimport { safeJoinInsideVault } from \"./fs.js\";\nimport { validateAgentWrite } from \"../../../memory/validator.js\";\nimport { getContract } from \"../../../memory/contract/index.js\";\nimport type { MemorySinkRegistry } from \"../../../memory/registry.js\";\nimport { assertSentinelExists, SinkSentinelCheckError } from \"./sentinel.js\";\n\n// ─── Legacy re-exports (v1 callers + tests) ─────────────────────────────────\n//\n// Existing handlers in src/server.ts and tests import these directly. They\n// continue to work; new code should construct an ObsidianFsDelivery and call\n// the DeliveryAdapter methods instead.\n\nexport { writeNote, deleteNote } from \"./write.js\";\nexport type {\n WriteResult,\n WriteSuccess,\n WriteConflict,\n WriteNoteInput,\n DeleteNoteInput,\n} from \"./write.js\";\nexport { atomicWriteFile, safeJoinInsideVault, OutsideVaultError } from \"./fs.js\";\n\n// ─── DeliveryAdapter facade ─────────────────────────────────────────────────\n\nconst SCHEME = \"obsidian-fs\";\n\n/**\n * Map a v1 internal `WriteResult` (with `noteId: number`) to the v2\n * `WriteResult` shape (with `doc_id: DocId`). The facade owns this\n * boundary mapping so the internal write.ts can keep its v1 shape and\n * caller-facing handlers can keep deriving v1 `noteId` from the DB.\n */\nfunction v1ToV2WriteResult(id: DocId, v1: V1WriteResult): V2WriteResult {\n if (!v1.ok) {\n return v1.currentHash !== undefined\n ? { ok: false, reason: v1.reason, currentHash: v1.currentHash, message: v1.message }\n : { ok: false, reason: v1.reason, message: v1.message };\n }\n return { ok: true, doc_id: id, newHash: v1.newHash, created: v1.created };\n}\n\nfunction v1ToV2UpdateResult(id: DocId, v1: V1WriteResult): V2UpdateResult {\n if (!v1.ok) {\n return v1.currentHash !== undefined\n ? { ok: false, reason: v1.reason, currentHash: v1.currentHash, message: v1.message }\n : { ok: false, reason: v1.reason, message: v1.message };\n }\n return { ok: true, doc_id: id, newHash: v1.newHash };\n}\n\nexport class ObsidianFsDelivery implements DeliveryAdapter {\n readonly handle: SourceHandle;\n\n readonly capabilities: DeliveryCapabilities = {\n atomic: true,\n hashProtected: \"strong\",\n enforcedSchema: false,\n naming: \"caller-provided\",\n };\n\n /**\n * @param vault The Vault unit-of-access (config + db handle).\n * @param clientId Default audit-log attribution. Per D-02, captured from\n * MCP InitializeRequest.params.clientInfo (via the SDK's\n * `Server.getClientVersion()?.name`) at server bootstrap. May be a static\n * string OR a lazy getter — the getter form lets the server construct\n * deliveries BEFORE the initialize handshake completes and have the\n * handshake value flow through automatically on the first write.\n * Falls back to \"unknown\" at the call site if no value is supplied at\n * any level (per RESEARCH Pitfall 4: clientInfo is OPTIONAL in the MCP\n * spec, so older or non-conformant clients may not send it).\n * @param memorySinkRegistry Optional Phase 2 sink registry. When supplied,\n * the adapter runs Guards A/B + sentinel check at the entry of\n * `write` / `update` / `delete` per ADR-002 §DeliveryAdapter. When\n * omitted (Phase 1 fixture tests + back-compat), the validator is\n * silently skipped — production paths in Plan 02-03b's server\n * bootstrap always pass the registry, so production is always\n * guarded.\n */\n constructor(\n private readonly vault: Vault,\n private readonly clientIdSource: string | (() => string),\n private readonly memorySinkRegistry?: MemorySinkRegistry,\n ) {\n this.handle = parseSourceHandle(`${SCHEME}://${vault.config.name}`);\n }\n\n private get clientId(): string {\n return typeof this.clientIdSource === \"function\" ? this.clientIdSource() : this.clientIdSource;\n }\n\n /**\n * Resolve the sink that \"owns\" a write target.\n *\n * Resolution order (per ADR-004 §Resolution + Plan 02-03 ):\n * 1. If `opts.sink` is supplied AND the registry knows it, use it.\n * The caller explicitly routed the write under that sink.\n * 2. Else, ask the registry `findSinkContaining(id)` — for DocIds\n * whose vault-relative path lies inside a registered sink, this\n * returns the enclosing sink. Used for guarding writes that\n * target memory paths WITHOUT an explicit `opts.sink` (e.g. v1\n * `writeNote` against `_memory/...`).\n * 3. Else, the target is outside every sink — return `null`.\n *\n * Returns `null` when no registry is configured (Phase 1 fixture\n * tests + back-compat). The validator then silently passes.\n */\n private resolveTargetSink(id: DocId, opts?: WriteOptions): MemorySink | null {\n const registry = this.memorySinkRegistry;\n if (!registry) return null;\n if (opts?.sink !== undefined) {\n try {\n return registry.resolveMemorySink(opts.sink);\n } catch {\n // Fall through to path-based lookup; surfaces as\n // `agent_write_outside_sink` if the caller declared the wrong\n // sink and the path also doesn't land in any registered sink.\n }\n }\n return registry.findSinkContaining(id);\n }\n\n /**\n * Derive the `is_memory_sink_write` flag for the audit row.\n *\n * WR-08 (Plan 02-14): this MUST use the resolved truth\n * (`registry.findSinkContaining(id)`), NOT the caller-intent signal\n * (`opts.sink !== undefined`). The two signals diverge when a write\n * lands inside a sink WITHOUT the caller having routed through the\n * sink-aware path (legacy `writeNote` bypass, future code paths). The\n * audit must reflect what the disk says, not what the caller said.\n *\n * When no registry is configured (Phase 1 fixture constructors), the\n * flag falls back to `false` — preserves back-compat fixture tests.\n */\n private isMemorySinkWriteFor(id: DocId): boolean {\n const sink = this.memorySinkRegistry?.findSinkContaining(id);\n return sink !== null && sink !== undefined;\n }\n\n /**\n * Run Guards A/B + sentinel for a write or update. Returns the\n * conflict to short-circuit on, or `null` to proceed.\n *\n * Order: Guard B (cheap) → sentinel (fail-closed) → Guard A.\n * The sentinel check is filesystem-specific and intentionally lives\n * here, not in the validator.\n */\n private async preflight(\n id: DocId,\n doc: Partial,\n opts?: WriteOptions,\n ): Promise {\n if (!this.memorySinkRegistry) return null;\n const sink = this.resolveTargetSink(id, opts);\n const contract = sink ? getContract(sink.contractName) : null;\n\n // Guard B (and partial Guard A for source mismatch) — runs first.\n // Guard A short-circuits if source-check fails.\n const sourceCheck = validateAgentWrite(id, doc, sink, null);\n if (sourceCheck) return sourceCheck;\n\n // Sentinel check (filesystem-specific) — only when target lands in a sink.\n // WR-06 (gap-closure Plan 02-10): ENOENT distinguishes from other errno\n // codes. The literal `\"sentinel_check_failed\"` was added to the\n // WriteConflict.reason union by Plan 02-13 Task 1 in wave 9; this plan\n // CONSUMES that literal here.\n if (sink !== null) {\n let ok: boolean;\n try {\n ok = await assertSentinelExists(sink, this.vault.config.path);\n } catch (err) {\n if (err instanceof SinkSentinelCheckError) {\n return {\n ok: false,\n reason: \"sentinel_check_failed\",\n sinkName: sink.name,\n message: err.message,\n suggestion:\n `Check filesystem permissions / disk health for ` +\n `${this.vault.config.name}/${sink.resolveToRelativePath}. ` +\n `Underlying errno: ${err.underlyingCode}.`,\n };\n }\n throw err;\n }\n if (!ok) {\n return {\n ok: false,\n reason: \"sentinel_missing\",\n sinkName: sink.name,\n message:\n `MemorySink \"${sink.name}\" refuses to resolve: ` +\n `'.memory-sink' sentinel file is missing under ${this.vault.config.name}/${sink.resolveToRelativePath}.`,\n suggestion:\n \"Restart the server (it re-provisions automatically) or restore .memory-sink manually.\",\n };\n }\n }\n\n // Guard A (full Zod schema validation) — only when target lands in a sink\n // and a contract is bound.\n if (sink !== null && contract !== null) {\n const guardA = validateAgentWrite(id, doc, sink, contract);\n if (guardA) return guardA;\n }\n return null;\n }\n\n async write(id: DocId, doc: Partial, opts?: WriteOptions): Promise {\n const guard = await this.preflight(id, doc, opts);\n if (guard) return guard;\n const path = this.docIdToPath(id);\n const { body, frontmatter } = extractBodyAndFrontmatter(doc);\n const effectiveClientId = opts?.clientId ?? this.clientId;\n // Plan 02-14 (MEM-08 follow-up, WR-08): the audit row's\n // `is_memory_sink_write` flag is derived from\n // `registry.findSinkContaining(id)` — the resolved-target truth, not\n // caller intent. A write that lands inside a sink without `opts.sink`\n // (legacy `writeNote` bypass, future code paths) is still correctly\n // flagged. When no registry is configured (Phase 1 fixture tests), the\n // flag falls back to `false`.\n const v1 = await writeNoteInternal({\n vault: this.vault,\n relativePath: path,\n content: body,\n frontmatter,\n ...(opts?.expectedHash !== undefined ? { expectedHash: opts.expectedHash } : {}),\n clientId: effectiveClientId,\n isMemorySinkWrite: this.isMemorySinkWriteFor(id),\n });\n return v1ToV2WriteResult(id, v1);\n }\n\n /**\n * Replace-or-merge update. Reads current document via the filesystem,\n * applies `patch.properties` (shallow-merged into existing frontmatter)\n * and/or `patch.blocks` (replaces body), then writes via writeNote with\n * the OCC token.\n *\n * Returns `{ ok: false, reason: \"not_found\" }` when the file is absent\n * (matches DeliveryAdapter contract — no implicit create on update).\n *\n * WR-05 (Plan 02-14): callers MUST supply `opts.expectedHash`. Omitting\n * it returns `{ ok: false, reason: \"hash_mismatch\" }` — symmetric with\n * `delete()`'s existing behavior. The previous implementation silently\n * fabricated `expectedHash` from the on-disk hash, racing with concurrent\n * edits and downgrading the `hashProtected: \"strong\"` capability to\n * best-effort.\n *\n * The v1 MCP `update_frontmatter` handler continues to route through\n * `src/frontmatter/update.ts` (merge-DSL semantics + diff emission). This\n * `update()` path exists primarily for conformance and for non-merge-DSL\n * callers (Phase 2+).\n */\n async update(id: DocId, patch: Partial, opts?: WriteOptions): Promise {\n const guard = await this.preflight(id, patch, opts);\n if (guard) return guard;\n\n // WR-05 (Plan 02-14): refuse if expectedHash is missing. The OCC token\n // is mandatory for hashProtected=\"strong\" adapters; silently fabricating\n // it from the on-disk hash (the previous behavior) downgraded the\n // contract to best-effort and raced with concurrent edits.\n if (opts?.expectedHash === undefined) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n message: `update() requires opts.expectedHash for hashProtected=\"strong\" adapters`,\n };\n }\n\n const path = this.docIdToPath(id);\n\n // Resolve absolute path with safety check; on traversal this throws,\n // which surfaces upstream — that is intentional, matching v1 writeNote.\n const abs = await safeJoinInsideVault(this.vault.config.path, path);\n\n let raw: string;\n try {\n raw = await fs.readFile(abs, \"utf-8\");\n } catch (err) {\n if (\n typeof err === \"object\" &&\n err !== null &&\n (err as NodeJS.ErrnoException).code === \"ENOENT\"\n ) {\n return {\n ok: false,\n reason: \"not_found\",\n message: `Document not found: ${id}`,\n };\n }\n throw err;\n }\n const parsed = matter(raw);\n const existingFm = (parsed.data ?? {}) as Record;\n const existingBody = parsed.content;\n\n // Merge properties (shallow). If patch.blocks is supplied, replace body\n // with the concatenation of paragraph-block text; otherwise preserve.\n const patchProps = patch.properties as Record | undefined;\n const nextFm =\n patchProps !== undefined ? { ...existingFm, ...stripWikilinks(patchProps) } : existingFm;\n const nextBody =\n patch.blocks !== undefined\n ? patch.blocks\n .map((b) => (b.kind === \"paragraph\" ? b.text : \"\"))\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\")\n : existingBody;\n\n // WR-05 (Plan 02-14): expectedHash is mandatory (checked above). The OCC\n // contract is honored by passing opts.expectedHash straight through;\n // writeNoteInternal surfaces a hash_mismatch for stale tokens.\n const effectiveClientId = opts?.clientId ?? this.clientId;\n // Plan 02-14 (MEM-08 follow-up, WR-08): symmetric with write() —\n // update() derives the audit-row `is_memory_sink_write` flag from\n // `registry.findSinkContaining(id)` (resolved-target truth), not\n // from `opts.sink !== undefined` (caller intent). supersede routes\n // through update() against a DocId inside a sink, so the audit row\n // is correctly stamped regardless of opts.sink presence.\n const v1 = await writeNoteInternal({\n vault: this.vault,\n relativePath: path,\n content: nextBody,\n frontmatter: Object.keys(nextFm).length > 0 ? nextFm : null,\n expectedHash: opts.expectedHash,\n clientId: effectiveClientId,\n isMemorySinkWrite: this.isMemorySinkWriteFor(id),\n });\n return v1ToV2UpdateResult(id, v1);\n }\n\n async delete(id: DocId, opts?: WriteOptions): Promise {\n // Hard-deletion of memory documents is forbidden in v2.0.0\n // (per Plan 02-03 truths + RESEARCH Pitfall 5). If the DocId\n // resolves into ANY registered sink (regardless of opts.sink),\n // refuse with sink_write_blocked. Use `supersede` instead.\n if (this.memorySinkRegistry) {\n const enclosing = this.memorySinkRegistry.findSinkContaining(id);\n if (enclosing !== null) {\n return {\n ok: false,\n reason: \"sink_write_blocked\",\n sinkName: enclosing.name,\n message:\n `Hard deletion of MemorySink \"${enclosing.name}\" documents is ` +\n `not permitted in v2.0.0.`,\n suggestion:\n \"Use supersede to retire memory documents. Hard deletion is not yet supported in v2.0.0.\",\n };\n }\n }\n\n const path = this.docIdToPath(id);\n\n // The v1 deleteNote requires expectedHash. If the caller did not\n // supply one, surface a permission-denied-style failure so the\n // hashProtected=\"strong\" guarantee holds.\n if (opts?.expectedHash === undefined) {\n // Use a \"not_found\"-style probe via the FS to distinguish a\n // missing-doc case from a missing-hash case (the conformance test\n // expects `not_found` when deleting an unknown DocId).\n try {\n const abs = await safeJoinInsideVault(this.vault.config.path, path);\n await fs.stat(abs);\n } catch {\n return {\n ok: false,\n reason: \"not_found\",\n message: `Document not found: ${id}`,\n };\n }\n return {\n ok: false,\n reason: \"hash_mismatch\",\n message: `delete() requires opts.expectedHash for hashProtected=\"strong\" adapters`,\n };\n }\n\n const effectiveClientId = opts?.clientId ?? this.clientId;\n // Plan 02-14 (MEM-08 follow-up, WR-08): symmetric flag for delete.\n // The `is_memory_sink_write` audit flag is derived from\n // `registry.findSinkContaining(id)` (resolved-target truth), not\n // from `opts.sink !== undefined` (caller intent). Sink-resolved\n // deletes are normally refused upstream with `sink_write_blocked`\n // (hard-delete of memory documents is forbidden in v2.0.0; callers\n // use `supersede`); this code path is reached only for non-sink\n // targets or future admin bypasses, but the flag derivation stays\n // symmetric with write/update so any bypass that DOES reach audit\n // is truthfully flagged.\n const v1 = await deleteNoteInternal({\n vault: this.vault,\n relativePath: path,\n expectedHash: opts.expectedHash,\n clientId: effectiveClientId,\n isMemorySinkWrite: this.isMemorySinkWriteFor(id),\n });\n if (!v1.ok) {\n // v1 returns hash_mismatch when the file is absent. Re-shape to\n // not_found for the v2 contract.\n if (v1.reason === \"hash_mismatch\" && v1.currentHash === undefined) {\n return {\n ok: false,\n reason: \"not_found\",\n message: v1.message,\n };\n }\n return v1.currentHash !== undefined\n ? { ok: false, reason: v1.reason, currentHash: v1.currentHash, message: v1.message }\n : { ok: false, reason: v1.reason, message: v1.message };\n }\n return { ok: true, doc_id: id };\n }\n\n // ── helpers ───────────────────────────────────────────────────────────────\n\n /**\n * Parse the URI authority + resource off a DocId. Asserts the authority\n * matches the configured vault name — mirrors ObsidianFsSource.docIdToPath\n * to prevent cross-vault forgery.\n */\n private docIdToPath(id: DocId): string {\n const prefix = `${SCHEME}://`;\n if (!id.startsWith(prefix)) {\n throw new Error(`DocId scheme mismatch: expected \"${SCHEME}://…\", got ${JSON.stringify(id)}`);\n }\n const rest = id.slice(prefix.length);\n const slash = rest.indexOf(\"/\");\n if (slash < 0) {\n throw new Error(`Invalid DocId shape: missing resource path in ${JSON.stringify(id)}`);\n }\n const authority = rest.slice(0, slash);\n const resource = rest.slice(slash + 1);\n if (authority !== this.vault.config.name) {\n throw new Error(\n `DocId vault mismatch: id authority \"${authority}\" does not match ` +\n `this adapter's configured vault \"${this.vault.config.name}\"`,\n );\n }\n if (resource.length === 0) {\n throw new Error(`Invalid DocId: empty resource path in ${JSON.stringify(id)}`);\n }\n return resource;\n }\n}\n\n// ─── Partial → body + frontmatter ─────────────────────────────────\n\nfunction stripWikilinks(props: Record): Record {\n // D-05: ObsidianFsSource surfaces wikilinks as Document.properties.wikilinks\n // when READING. We must NOT write that field back into the user's frontmatter.\n const { wikilinks: _w, ...rest } = props as { wikilinks?: unknown } & Record;\n return rest;\n}\n\nfunction extractBodyAndFrontmatter(doc: Partial): {\n body: string;\n frontmatter: Record | null;\n} {\n // body: concatenate flat-text paragraph blocks. Phase 1 only emits\n // single-paragraph blocks anyway (ADR-003 BodyShape=\"flat-text\").\n const body = (doc.blocks ?? [])\n .map((b) => (b.kind === \"paragraph\" ? b.text : \"\"))\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n\n const props = doc.properties;\n if (props === undefined || props === null) {\n return { body, frontmatter: null };\n }\n const stripped = stripWikilinks(props as Record);\n return {\n body,\n frontmatter: Object.keys(stripped).length > 0 ? stripped : null,\n };\n}\n","/**\n * updateFrontmatter — merge-style frontmatter editor.\n *\n * Modifies only the YAML frontmatter of a markdown note. The body is\n * preserved bytegenau. Writes are atomic and audited.\n *\n * Merge DSL (top-level keys of `merge`):\n * : → set / overwrite\n * : { $unset: true } → delete the key\n * : { $push: x } → push x onto array (create if absent)\n * : { $pull: x } → remove x from array (no-op if absent)\n * : { ...plainObj } → shallow-merge into existing object (or set)\n *\n * Concurrency: optional `expectedHash` is checked against the current\n * note hash (sha256 of `content + JSON.stringify(frontmatter ?? {})`).\n * Mismatch → conflict, no write.\n *\n * NOTE: gray-matter's stringify preserves the existing serialization\n * style for fields it knows about, but YAML key order for *new* keys is\n * insertion order. We do not guarantee a stable global key order.\n *\n * Plan 01-04 task 05: this module no longer imports `gray-matter` or\n * `node:fs` directly. The READ path goes through the v2 SourceConnector\n * (`registry.resolveSource(handle).readDocument(id)`) and the WRITE\n * path goes through the v2 DeliveryAdapter (`registry.resolveDelivery\n * (handle).write(id, partial, opts)`). The merge-DSL semantics + diff\n * emission are UNCHANGED.\n */\n\nimport type { Vault } from \"../vault/index.js\";\nimport type { Document, DocId, SourceHandle, WikilinkRef } from \"../types.js\";\nimport type { AdapterRegistry } from \"../adapters/registry.js\";\nimport { formatDocId, parseSourceHandle } from \"../adapters/registry.js\";\nimport type { MemorySinkRegistry } from \"../memory/registry.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public API\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface UpdateFrontmatterInput {\n vault: Vault;\n /**\n * Adapter registry — Source for read, Delivery for write. Optional for\n * backwards-compatibility with v1 callers that haven't been migrated;\n * when omitted, the function falls back to constructing per-call\n * adapters from `vault` (delegated by the server handler in Phase 1).\n */\n registry?: AdapterRegistry;\n /**\n * Plan 02-03b — defense-in-depth entry-point Guard. When supplied AND\n * the target lands inside a registered MemorySink, the update is\n * refused with `{ok:false, reason:\"sink_write_blocked\"}` BEFORE any\n * filesystem read. When omitted (Phase 1 unit-test fixtures + back-\n * compat callers), the guard is silently skipped. See\n * `src/adapters/delivery/obsidian-fs/write.ts:WriteNoteInput.registry`\n * for the full rationale (the authoritative chokepoint lives at the\n * DeliveryAdapter; this is defense-in-depth).\n */\n memorySinkRegistry?: MemorySinkRegistry;\n relativePath: string;\n merge: Record;\n expectedHash?: string;\n clientId?: string;\n /** Called once, immediately before the filesystem write. See\n * `WriteNoteInput.onBeforeFsWrite`. Not called when the update is a\n * no-op (empty merge or no effective change) since no fs event will\n * occur. */\n onBeforeFsWrite?: () => void;\n}\n\nexport type DiffOp = \"set\" | \"unset\" | \"push\" | \"pull\";\n\nexport interface DiffEntry {\n key: string;\n op: DiffOp;\n before?: unknown;\n after?: unknown;\n}\n\nexport interface UpdateSuccess {\n ok: true;\n newHash: string;\n noteId: number;\n diff: DiffEntry[];\n}\n\nexport interface UpdateConflict {\n ok: false;\n reason: \"hash_mismatch\" | \"permission_denied\" | \"note_not_found\" | \"sink_write_blocked\";\n currentHash?: string;\n message: string;\n /** Phase 2 envelope (sink_write_blocked). */\n sinkName?: string;\n /** Phase 2 envelope — actionable next-step hint. */\n suggestion?: string;\n}\n\nexport type UpdateResult = UpdateSuccess | UpdateConflict;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction isPlainObject(v: unknown): v is Record {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction isUnsetDirective(v: unknown): v is { $unset: true } {\n return isPlainObject(v) && v[\"$unset\"] === true;\n}\n\nfunction isPushDirective(v: unknown): v is { $push: unknown } {\n return isPlainObject(v) && \"$push\" in v;\n}\n\nfunction isPullDirective(v: unknown): v is { $pull: unknown } {\n return isPlainObject(v) && \"$pull\" in v;\n}\n\nfunction hasDirective(v: unknown): boolean {\n if (!isPlainObject(v)) return false;\n return Object.keys(v).some((k) => k.startsWith(\"$\"));\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const ak = Object.keys(a);\n const bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (!deepEqual(a[k], b[k])) return false;\n }\n return true;\n }\n return false;\n}\n\nfunction applyMerge(\n data: Record,\n merge: Record,\n): { next: Record; diff: DiffEntry[] } {\n const next: Record = { ...data };\n const diff: DiffEntry[] = [];\n\n for (const [key, instr] of Object.entries(merge)) {\n const before = next[key];\n\n if (isUnsetDirective(instr)) {\n if (key in next) {\n delete next[key];\n diff.push({ key, op: \"unset\", before });\n }\n continue;\n }\n\n if (isPushDirective(instr)) {\n const value = (instr as { $push: unknown }).$push;\n if (Array.isArray(before)) {\n const arr = [...before, value];\n next[key] = arr;\n diff.push({ key, op: \"push\", before, after: arr });\n } else if (before === undefined) {\n next[key] = [value];\n diff.push({ key, op: \"push\", before: undefined, after: [value] });\n } else {\n // Treat non-array existing scalar as wrapping into a new array\n next[key] = [value];\n diff.push({ key, op: \"push\", before, after: [value] });\n }\n continue;\n }\n\n if (isPullDirective(instr)) {\n const value = (instr as { $pull: unknown }).$pull;\n if (Array.isArray(before)) {\n const filtered = before.filter((v) => !deepEqual(v, value));\n if (filtered.length !== before.length) {\n next[key] = filtered;\n diff.push({ key, op: \"pull\", before, after: filtered });\n }\n }\n // else: no-op\n continue;\n }\n\n // Plain set or shallow-merge nested object\n if (isPlainObject(instr) && !hasDirective(instr) && isPlainObject(before)) {\n const merged = { ...before, ...instr };\n if (!deepEqual(before, merged)) {\n next[key] = merged;\n diff.push({ key, op: \"set\", before, after: merged });\n }\n } else {\n if (!deepEqual(before, instr)) {\n next[key] = instr;\n diff.push({ key, op: \"set\", before, after: instr });\n }\n }\n }\n\n return { next, diff };\n}\n\n/**\n * Strip the adapter-injected `wikilinks: WikilinkRef[]` property that\n * `ObsidianFsSource.readDocument` puts on `Document.properties` (D-05).\n * The user's frontmatter never contained this key — we must NOT carry\n * it through the merge or re-write.\n */\nfunction stripWikilinks(props: Record): Record {\n const { wikilinks: _w, ...rest } = props as { wikilinks?: WikilinkRef[] } & Record<\n string,\n unknown\n >;\n return rest;\n}\n\n/**\n * Extract the flat-text body string from `Document.blocks`. Phase 1 only\n * emits single-paragraph blocks (BodyShape=\"flat-text\") so this is\n * trivially the first block's text. Matches the inverse of\n * `extractBodyAndFrontmatter` in ObsidianFsDelivery.\n */\nfunction blocksToBody(doc: Document): string {\n return doc.blocks\n .map((b) => (b.kind === \"paragraph\" ? b.text : \"\"))\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n}\n\nexport async function updateFrontmatter(input: UpdateFrontmatterInput): Promise {\n const {\n vault,\n relativePath,\n merge,\n expectedHash,\n clientId,\n registry,\n memorySinkRegistry,\n onBeforeFsWrite,\n } = input;\n\n // Plan 02-03b — defense-in-depth entry-point Guard. Runs BEFORE the\n // write_enabled check and BEFORE any DB / FS read. When the optional\n // MemorySinkRegistry is supplied (production path) AND the target lands\n // inside a registered sink, refuse with the structured `sink_write_blocked`\n // envelope. The suggestion directs the caller to `record_observation +\n // supersede` per Plan 02-03b action notes.\n if (memorySinkRegistry) {\n const docId = formatDocId(\"obsidian-fs\", vault.config.name, relativePath);\n const sink = memorySinkRegistry.findSinkContaining(docId);\n if (sink !== null) {\n return {\n ok: false,\n reason: \"sink_write_blocked\",\n sinkName: sink.name,\n message:\n `Target ${relativePath} resolves into MemorySink \"${sink.name}\". ` +\n `v1 update_frontmatter is refused for memory-sink targets.`,\n suggestion: \"Use record_observation + supersede for memory updates.\",\n };\n }\n }\n\n if (vault.config.write_enabled !== true) {\n return {\n ok: false,\n reason: \"permission_denied\",\n message: \"Vault is not write-enabled. Set write_enabled=true in config.\",\n };\n }\n\n const noteRow = vault.db.notes.getByPath(relativePath);\n if (noteRow === null) {\n return {\n ok: false,\n reason: \"note_not_found\",\n message: `No indexed note at path: ${relativePath}`,\n };\n }\n\n // Resolve the adapter triple. Phase 1 fallback (no registry supplied):\n // construct one inline from `vault` so existing callers (handlers that\n // haven't been migrated to registry-based dispatch yet) keep working.\n const { source, delivery } = await resolveAdapters(vault, registry);\n const handle = parseSourceHandle(`obsidian-fs://${vault.config.name}`);\n void handle;\n const docId: DocId = formatDocId(\"obsidian-fs\", vault.config.name, relativePath);\n\n // ── READ via Source ────────────────────────────────────────────────────────\n let doc: Document;\n try {\n doc = await source.readDocument(docId);\n } catch (err) {\n const msg = errorMessage(err);\n return {\n ok: false,\n reason: \"note_not_found\",\n message: `Failed to read document: ${msg}`,\n };\n }\n\n const body = blocksToBody(doc);\n const existingFm = stripWikilinks(doc.properties as Record);\n // The current hash on disk is exactly `doc.hash` (ObsidianFsSource uses\n // `computeNoteHash(body, fm)`). The wikilinks injection happens AFTER\n // hash computation in the parser, so doc.hash matches the gray-matter\n // round-trip the v1 code computed.\n const currentHash = doc.hash;\n\n if (expectedHash !== undefined && expectedHash !== currentHash) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n currentHash,\n message: `Expected hash ${expectedHash} but current is ${currentHash}.`,\n };\n }\n\n // Empty merge → no-op\n if (Object.keys(merge).length === 0) {\n return {\n ok: true,\n newHash: currentHash,\n noteId: noteRow.id,\n diff: [],\n };\n }\n\n const { next, diff } = applyMerge(existingFm, merge);\n\n if (diff.length === 0) {\n // Nothing actually changed (e.g. $pull on absent value)\n return {\n ok: true,\n newHash: currentHash,\n noteId: noteRow.id,\n diff: [],\n };\n }\n\n // ── WRITE via Delivery ─────────────────────────────────────────────────────\n // Pass the suppression hook through opts? — DeliveryAdapter does not\n // expose it on the v2 surface. Instead, call it directly before\n // dispatching; this matches the v1 ordering (hook fires immediately\n // before the fs write).\n onBeforeFsWrite?.();\n\n const partial: Partial = {\n blocks: [{ kind: \"paragraph\", text: body }],\n properties: Object.keys(next).length > 0 ? next : {},\n };\n const writeOpts: {\n expectedHash: string;\n clientId?: string;\n } = {\n expectedHash: currentHash,\n };\n if (clientId !== undefined) writeOpts.clientId = clientId;\n\n const writeRes = await delivery.write(docId, partial, writeOpts);\n if (!writeRes.ok) {\n // Shape-map Delivery v2 conflict reasons back to v1 update result.\n if (writeRes.reason === \"permission_denied\") {\n return {\n ok: false,\n reason: \"permission_denied\",\n message: writeRes.message ?? \"Write rejected by delivery adapter.\",\n };\n }\n return {\n ok: false,\n reason: \"hash_mismatch\",\n ...(writeRes.currentHash !== undefined ? { currentHash: writeRes.currentHash } : {}),\n message: writeRes.message ?? \"Write conflict.\",\n };\n }\n\n return {\n ok: true,\n newHash: writeRes.newHash,\n noteId: noteRow.id,\n diff,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Phase 1 adapter resolution\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Phase 1: when callers don't supply an AdapterRegistry, construct an\n * inline source + delivery for the vault. The eventual end-state (after\n * task 06's server.ts wiring) is that every caller supplies the\n * registry; this fallback is for the legacy `updateFrontmatter({vault,\n * ...})` shape during the migration window.\n *\n * The dynamic imports avoid pulling the obsidian-fs adapter into the\n * module-load graph for callers that never reach this branch — there\n * are no current consumers besides src/server.ts which WILL pass a\n * registry post-task-06.\n */\nasync function resolveAdapters(\n vault: Vault,\n registry: AdapterRegistry | undefined,\n): Promise<{\n source: { readDocument: (id: DocId) => Promise };\n delivery: {\n write: (\n id: DocId,\n doc: Partial,\n opts?: { expectedHash?: string; clientId?: string },\n ) => Promise<\n | { ok: true; newHash: string; doc_id: DocId; created: boolean }\n | { ok: false; reason: string; currentHash?: string; message?: string }\n >;\n };\n}> {\n const handle: SourceHandle = parseSourceHandle(`obsidian-fs://${vault.config.name}`);\n if (registry !== undefined) {\n return {\n source: registry.resolveSource(handle),\n delivery: registry.resolveDelivery(handle),\n };\n }\n // Fallback path — instantiate inline. clientId=\"unknown\" because the\n // server bootstrap has not threaded an actual MCP client_info value\n // through to this call; the caller's `clientId` arg wins in writeOpts.\n const { ObsidianFsSource } = await import(\"../adapters/source/obsidian-fs/index.js\");\n const { ObsidianFsDelivery } = await import(\"../adapters/delivery/obsidian-fs/index.js\");\n return {\n source: new ObsidianFsSource(vault.config),\n delivery: new ObsidianFsDelivery(vault, \"unknown\"),\n };\n}\n","export { queryFrontmatter } from \"./query.js\";\nexport type { QueryFrontmatterInput, Predicate } from \"./query.js\";\nexport { updateFrontmatter } from \"./update.js\";\nexport type {\n UpdateFrontmatterInput,\n UpdateResult,\n UpdateSuccess,\n UpdateConflict,\n DiffEntry,\n DiffOp,\n} from \"./update.js\";\n","/**\n * `MemorySinkRegistry` — the SOLE resolver for `MemorySink` handles\n * per ADR-004 §Resolution + ADR-002 §Registry M-1.\n *\n * Responsibilities:\n * - Hold the runtime map of registered sinks keyed by handle.\n * - Track the default sink (configured by `[memory].default_sink`\n * in TOML, or the first registered sink as a fallback).\n * - Provide name-OR-handle dual lookup via `resolveMemorySink`.\n * - Expose `findSinkContaining(docId)` for entry-point Guard A\n * refusals in v1 write tools (MEM-07).\n *\n * The registry is filesystem-ignorant: provisioning (sentinel writes)\n * is delegated to a `provisioner` callback supplied by the server\n * bootstrap. In production the callback wraps\n * `provisionSink(...)` from\n * `src/adapters/delivery/obsidian-fs/sentinel.ts`; in tests it is a\n * spy. This keeps `src/memory/` free of `node:fs` per ADR-002 I-2.\n *\n * The registry uses `decomposeDocId` from `src/adapters/registry.ts`\n * for splitting `DocId`s into `(scheme, authority, resource)` — no\n * ad-hoc regex. Handle-resource splitting uses a small private helper\n * because `MemorySinkHandle` is a distinct brand from `DocId`.\n */\n\nimport { decomposeDocId } from \"../adapters/registry.js\";\nimport { getContract } from \"./contract/index.js\";\nimport { parseMemorySinkHandle } from \"./sink.js\";\nimport type { DocId, MemorySink, MemorySinkHandle } from \"../types.js\";\n\n/** TOML-shape entry from `[[memory_sinks]]`. Validated by config/loader.ts. */\nexport interface MemorySinkConfig {\n name: string;\n handle: string;\n contract: string;\n}\n\n/** Options for `registerMemorySinks`. */\nexport interface RegisterMemorySinksOptions {\n /** Resolve a vault name (handle authority) to the vault-absolute path. */\n resolveVaultAbsolutePath: (vaultName: string) => string;\n /** Name of the configured default sink (from `[memory].default_sink`). */\n defaultSinkName?: string;\n /**\n * Optional getter override (defaults to `getContract` from\n * `./contract/index.js`); test-injectable.\n */\n contractGetter?: (name: string) => { name: string };\n /**\n * Provision the sink on disk (writes the sentinel). In production\n * this wraps `provisionSink(...)` from\n * `src/adapters/delivery/obsidian-fs/sentinel.ts`; in tests it is\n * a spy. The registry must not import `node:fs` directly.\n */\n provisioner: (sink: MemorySink, vaultAbsolutePath: string) => Promise;\n}\n\n/**\n * Split a `MemorySinkHandle` into `(scheme, authority, resource)`.\n * Pure string split — the handle is already validated by\n * `parseMemorySinkHandle` so the shape is guaranteed.\n */\nfunction decomposeMemorySinkHandle(handle: MemorySinkHandle): {\n scheme: string;\n authority: string;\n resource: string;\n} {\n const schemeEnd = handle.indexOf(\"://\");\n const scheme = handle.slice(0, schemeEnd);\n const rest = handle.slice(schemeEnd + 3);\n const authoritySlash = rest.indexOf(\"/\");\n const authority = rest.slice(0, authoritySlash);\n const resource = rest.slice(authoritySlash + 1);\n return { scheme, authority, resource };\n}\n\nexport class MemorySinkRegistry {\n private readonly sinks = new Map();\n /** Insertion order — used for the \"first registered\" default fallback. */\n private readonly order: MemorySinkHandle[] = [];\n private defaultHandle: MemorySinkHandle | null = null;\n\n /**\n * Register a batch of configured sinks. Validates each handle, looks\n * up the named contract, invokes the provisioner, and stores the\n * resolved `MemorySink` record.\n *\n * Throws on the first failure — server bootstrap should treat any\n * registration error as fatal per ADR-004 §Provisioning fail-fast.\n */\n async registerMemorySinks(\n configs: MemorySinkConfig[],\n opts: RegisterMemorySinksOptions,\n ): Promise {\n const getC = opts.contractGetter ?? getContract;\n for (const cfg of configs) {\n const handle = parseMemorySinkHandle(cfg.handle);\n const parts = decomposeMemorySinkHandle(handle);\n if (parts.scheme !== \"obsidian-fs\") {\n throw new Error(\n `MemorySink \"${cfg.name}\" has unsupported scheme \"${parts.scheme}\". ` +\n `Phase 2 supports only obsidian-fs sinks.`,\n );\n }\n const vaultName = parts.authority;\n const resolveToRelativePath = parts.resource;\n const contract = getC(cfg.contract);\n const isFirst = this.sinks.size === 0;\n const isExplicitDefault = opts.defaultSinkName === cfg.name;\n const isDefault = isExplicitDefault || (opts.defaultSinkName === undefined && isFirst);\n const sink: MemorySink = {\n name: cfg.name,\n handle,\n vault: vaultName,\n resolveToRelativePath,\n contractName: contract.name,\n isDefault,\n };\n await opts.provisioner(sink, opts.resolveVaultAbsolutePath(vaultName));\n this.sinks.set(handle, sink);\n this.order.push(handle);\n if (isDefault) this.defaultHandle = handle;\n }\n }\n\n /** Return all registered sinks in insertion order. */\n listMemorySinks(): MemorySink[] {\n const out: MemorySink[] = [];\n for (const handle of this.order) {\n const s = this.sinks.get(handle);\n if (s) out.push(s);\n }\n return out;\n }\n\n /**\n * Resolve a sink by EITHER its short `name` OR its full handle\n * string. Throws with a helpful diagnostic on miss — mirrors the\n * `AdapterRegistry.resolveSource` message style.\n */\n resolveMemorySink(nameOrHandle: string): MemorySink {\n // Name lookup first (most common case).\n for (const handle of this.order) {\n const s = this.sinks.get(handle);\n if (s && s.name === nameOrHandle) return s;\n }\n // Then handle lookup (string-equal to a registered handle).\n for (const handle of this.order) {\n if (handle === nameOrHandle) {\n const s = this.sinks.get(handle);\n if (s) return s;\n }\n }\n const known =\n this.order\n .map((h) => this.sinks.get(h)?.name)\n .filter(Boolean)\n .join(\", \") || \"(none)\";\n throw new Error(`Unknown memory sink: \"${nameOrHandle}\". Registered sinks: ${known}`);\n }\n\n /** Return the default sink. Throws if no sinks are registered. */\n getDefaultMemorySink(): MemorySink {\n if (this.defaultHandle === null) {\n throw new Error(\n \"No memory sinks are registered; cannot resolve the default sink. \" +\n \"Configure [[memory_sinks]] in config.toml.\",\n );\n }\n const sink = this.sinks.get(this.defaultHandle);\n if (!sink) {\n throw new Error(\n `Internal error: default memory sink handle \"${this.defaultHandle}\" not found in registry.`,\n );\n }\n return sink;\n }\n\n /**\n * Find the sink that encloses a given `DocId`, or `null` if the\n * DocId is outside every configured sink. Used by v1 write tools\n * (MEM-07) for entry-point Guard A refusals.\n *\n * Match policy: the DocId's authority must equal the sink's vault,\n * and the DocId's resource must start with the sink's\n * `resolveToRelativePath` (which includes its trailing slash, so\n * `_memory/observations/foo.md` matches sink `_memory/` but\n * `_memory-staging/...` does not).\n */\n findSinkContaining(docId: DocId): MemorySink | null {\n const { scheme, authority, resource } = decomposeDocId(docId);\n if (scheme !== \"obsidian-fs\") return null;\n for (const handle of this.order) {\n const sink = this.sinks.get(handle);\n if (!sink) continue;\n if (sink.vault !== authority) continue;\n if (resource.startsWith(sink.resolveToRelativePath)) {\n return sink;\n }\n }\n return null;\n }\n}\n","/**\n * `vault-memory://memory/sinks` — MCP Resource enumerating the\n * configured + auto-discovered MemorySinks (Plan 02-06, MEM-09).\n *\n * Resource, not tool: agents that want to discover where they may\n * write memory documents read this URI instead of invoking a tool.\n * Polled-only — there is NO `notifyResourceUpdated` integration in\n * v2.0.0 (CONTEXT D-Q4, Deferred Ideas).\n *\n * The handler is a pure function over the `MemorySinkRegistry`. It\n * touches neither the filesystem nor the DB — the single resolver\n * rule from ADR-004 §Resolution applies.\n */\n\nimport type { MemorySinkRegistry } from \"../registry.js\";\n\nexport interface ListSinksResource {\n /** Total number of registered sinks across all vaults. */\n total: number;\n sinks: ListSinkEntry[];\n}\n\nexport interface ListSinkEntry {\n /** Short name (resolution key). */\n name: string;\n /** Full `obsidian-fs:////` URI. */\n handle: string;\n /** Owning vault name. */\n vault: string;\n /** Name of the bound `MemoryContract`. */\n contract: string;\n /** True iff this is the vault's default sink. */\n default: boolean;\n /** Vault-relative folder the sink resolves to (e.g. \"_memory/\"). */\n resolves_to: string;\n}\n\n/**\n * Pure handler — builds the resource payload from the registry's\n * `listMemorySinks()` snapshot.\n */\nexport function readListSinks(registry: MemorySinkRegistry): ListSinksResource {\n const sinks = registry.listMemorySinks();\n return {\n total: sinks.length,\n sinks: sinks.map(\n (s): ListSinkEntry => ({\n name: s.name,\n handle: s.handle,\n vault: s.vault,\n contract: s.contractName,\n default: s.isDefault,\n resolves_to: s.resolveToRelativePath,\n }),\n ),\n };\n}\n","/**\n * `vault-memory://memory/stats` — MCP Resource exposing per-sink document\n * counts and last-write timestamps (Plan 02-06, MEM-09).\n *\n * Resource, not tool. Polled-only. Build cost is a small handful of SQL\n * queries per registered sink — bounded by the number of sinks (tens at\n * most in v2.0.0), so the resource is cheap to re-read.\n *\n * Aggregation strategy:\n * - `doc_count` ← `NotesQueries.countByPathPrefix(sink.resolveToRelativePath)`\n * - `by_type` ← scan `frontmatter.type` over rows returned by\n * `NotesQueries.listByPathPrefix(...)`\n * - `by_status` ← scan `frontmatter.status` over the same rows\n * - `last_write_at` ← `AuditQueries.lastMemoryWriteAtForPathPrefix(...)`\n * (uses the v9 partial index)\n *\n * The resource is filesystem-ignorant — it pulls everything from the per-\n * vault SQLite DB via the existing Queries classes. ADR-002 I-2/I-3/I-4\n * remain satisfied (no fs / path / gray-matter imports here).\n */\n\nimport type { MemorySinkRegistry } from \"../registry.js\";\nimport type { VaultManager } from \"../../vault/manager.js\";\nimport { LIST_BY_PATH_PREFIX_DEFAULT_LIMIT } from \"../../db/queries/notes.js\";\n\nexport interface MemoryStatsResource {\n /** Aggregate document count across all sinks. */\n total_docs: number;\n sinks: MemoryStatsEntry[]; // vault-memory:no-telemetry-ok\n}\n\nexport interface MemoryStatsEntry {\n // vault-memory:no-telemetry-ok\n name: string;\n vault: string;\n handle: string;\n doc_count: number;\n by_type: Record;\n by_status: Record;\n /** Epoch ms of the most recent memory-sink write into this sink, or null. */\n last_write_at: number | null;\n /**\n * IN-03: True iff the `by_type` / `by_status` aggregation hit the\n * `LIST_BY_PATH_PREFIX_DEFAULT_LIMIT` cap. When true, `doc_count`\n * still reflects the accurate row count (it comes from\n * `countByPathPrefix`, which is unbounded), but the `by_type` /\n * `by_status` sums undercount by\n * `doc_count - LIST_BY_PATH_PREFIX_DEFAULT_LIMIT`. Omitted when\n * the cap was not hit. Consumers detecting this can either widen\n * the sink configuration or accept the undercount.\n */\n truncated?: boolean;\n}\n\n/**\n * Build the resource payload. Returns an empty resource (`total_docs: 0`,\n * `sinks: []`) when no sinks are registered — the empty case is a valid\n * response, not an error.\n */\nexport function readMemoryStats(\n registry: MemorySinkRegistry,\n manager: VaultManager,\n): MemoryStatsResource {\n const sinks = registry.listMemorySinks();\n let totalDocs = 0;\n const entries: MemoryStatsEntry[] = []; // vault-memory:no-telemetry-ok\n\n for (const sink of sinks) {\n // Sink may reference a vault that is no longer mounted (e.g. config\n // edited at runtime). Surface zero counts in that case rather than\n // throwing — keeps the resource readable for diagnostic purposes.\n let vault;\n try {\n vault = manager.require(sink.vault);\n } catch {\n entries.push({\n name: sink.name,\n vault: sink.vault,\n handle: sink.handle,\n doc_count: 0,\n by_type: {},\n by_status: {},\n last_write_at: null,\n });\n continue;\n }\n\n const prefix = sink.resolveToRelativePath;\n const doc_count = vault.db.notes.countByPathPrefix(prefix);\n totalDocs += doc_count;\n\n const by_type: Record = {};\n const by_status: Record = {};\n // Bounded scan — see TSDoc on listByPathPrefix; sinks in v2.0.0 hold\n // tens of documents, not thousands. The `truncated` marker (IN-03)\n // surfaces the rare case where the cap was hit so consumers can\n // detect the doc_count vs by_type/by_status inconsistency.\n const rows = vault.db.notes.listByPathPrefix(prefix);\n for (const row of rows) {\n const fm = parseFrontmatter(row.frontmatter);\n const type = stringField(fm, \"type\");\n const status = stringField(fm, \"status\");\n if (type !== null) by_type[type] = (by_type[type] ?? 0) + 1;\n if (status !== null) by_status[status] = (by_status[status] ?? 0) + 1;\n }\n const truncated = rows.length >= LIST_BY_PATH_PREFIX_DEFAULT_LIMIT;\n\n const last_write_at = vault.db.audit.lastMemoryWriteAtForPathPrefix(prefix);\n\n entries.push({\n name: sink.name,\n vault: sink.vault,\n handle: sink.handle,\n doc_count,\n by_type,\n by_status,\n last_write_at,\n ...(truncated ? { truncated: true } : {}),\n });\n }\n\n return {\n total_docs: totalDocs,\n sinks: entries,\n };\n}\n\nfunction parseFrontmatter(raw: string | null): Record {\n if (raw === null || raw.length === 0) return {};\n try {\n const parsed: unknown = JSON.parse(raw);\n if (parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record;\n }\n return {};\n } catch {\n // Stored frontmatter that fails to JSON-parse is silently treated as\n // empty for stats purposes. The indexer writes well-formed JSON; a\n // corrupted row should not crash the Resource.\n return {};\n }\n}\n\nfunction stringField(fm: Record, key: string): string | null {\n const v = fm[key];\n return typeof v === \"string\" && v.length > 0 ? v : null;\n}\n","/**\n * Memory MCP Resources — barrel.\n *\n * Plan 02-06 (MEM-09):\n * - `vault-memory://memory/sinks` → readListSinks\n * - `vault-memory://memory/stats` → readMemoryStats\n *\n * Polled-only (no `notifyResourceUpdated` integration in v2.0.0).\n * Registered through `server.registerResource(...)` at bootstrap.\n */\n\nexport { readListSinks } from \"./list-sinks.js\";\nexport type { ListSinksResource, ListSinkEntry } from \"./list-sinks.js\";\n\nexport { readMemoryStats } from \"./memory-stats.js\";\nexport type { MemoryStatsResource, MemoryStatsEntry } from \"./memory-stats.js\"; // vault-memory:no-telemetry-ok\n\n/** Canonical resource URIs. */\nexport const RESOURCE_URI_LIST_SINKS = \"vault-memory://memory/sinks\";\nexport const RESOURCE_URI_MEMORY_STATS = \"vault-memory://memory/stats\";\n/**\n * Phase 5 / BRF-09: brief discovery via MCP Resource. Registered by\n * slice 4 (Plan 05-04); the URI constant lands in slice 1 so later\n * slices can import it without scaffolding work.\n */\nexport const RESOURCE_URI_LIST_BRIEFS = \"vault-memory://briefs\";\n\n/**\n * Phase 6 / Plan 06-04 (CON-04 + D-A2b): contract discovery + verb-usage\n * Resources. The `{vault}` suffix is appended at registration time per\n * the SDK 1.29 Resource template pattern.\n */\nexport const RESOURCE_URI_LIST_CONTRACTS = \"vault-memory://contracts\";\nexport const RESOURCE_URI_LIST_CONTRACT_VERBS = \"vault-memory://contract-verbs\";\n\n/**\n * SOURCES-REGISTRY.md §5 (Stage 2): first-class peer-MCP source\n * discovery. Vault-independent (the PeerMcpRegistry is one global\n * instance across vaults), so `sources` has no `{vault}` segment.\n * `sources/{name}/tools` and `sources/{name}/tools/{tool}` append their\n * variables at registration time.\n */\nexport const RESOURCE_URI_SOURCES = \"vault-memory://sources\";\n\n/**\n * Phase 8 / Plan 08-05 (REL-08): 5 list-style v1 tools promoted to MCP\n * Resources to land the canonical (non-deprecated) tool surface at 32.\n *\n * The original tools (list_vaults, list_models, recent_notes, vault_stats,\n * list_backlinks) remain callable through v2.x with a DEPRECATED notice in\n * their `description`. Each Resource read handler delegates to the existing\n * internal handler function — no logic duplication (GAT-01 seam preservation).\n *\n * URIs are the BASE form here; templated forms append `/{vault}` (and\n * `/{+docId}` for backlinks) at `registerResource` time. The `+` in\n * `{+docId}` is RFC 6570 reserved-character expansion: it allows the\n * variable to include `/`, so multi-segment docIds (e.g. `notes/sub/file.md`)\n * parse as a single value instead of being truncated at the first `/`.\n */\nexport const RESOURCE_URI_VAULTS = \"vault-memory://vaults\";\nexport const RESOURCE_URI_MODELS = \"vault-memory://models\";\nexport const RESOURCE_URI_RECENT = \"vault-memory://recent\";\nexport const RESOURCE_URI_STATS = \"vault-memory://stats\";\nexport const RESOURCE_URI_BACKLINKS = \"vault-memory://backlinks\";\n","/**\n * Public surface of the memory subsystem.\n *\n * Phase 2 Plan 02-02 ships the substrate layer:\n * - `parseMemorySinkHandle` / `formatMemorySinkHandle` / SENTINEL_FILENAME\n * from `./sink.js`.\n * - `MemorySinkRegistry` from `./registry.js` (sole resolver per\n * ADR-004 §Resolution).\n * - `getContract` / `loadContractFromDisk` / `DEFAULT_MEMORY_V1` /\n * `MemoryContract` from `./contract/index.js`.\n *\n * Downstream plans (02-03..02-08) add the validator, MCP tools,\n * MCP resources, and audit-log integration; their public symbols\n * will be re-exported here.\n */\n\nexport {\n formatMemorySinkHandle,\n MEMORY_SINK_HANDLE_PATTERN,\n parseMemorySinkHandle,\n SENTINEL_FILENAME,\n} from \"./sink.js\";\n\nexport { MemorySinkRegistry } from \"./registry.js\";\nexport type { MemorySinkConfig, RegisterMemorySinksOptions } from \"./registry.js\";\n\nexport {\n DEFAULT_MEMORY_V1,\n getContract,\n loadContractFromDisk,\n MemoryContractInvalidError,\n MemoryContractNotFoundError,\n} from \"./contract/index.js\";\nexport type { MemoryContract } from \"./contract/index.js\";\n\n// Plan 02-05 — citation packet shape (D-01); shared with Phase 3 ASM-05.\nexport { displayUrlFor, toCitationPacket } from \"./citation-packet.js\";\nexport type { CitationPacket } from \"./citation-packet.js\";\n\n// Plan 02-06 (MEM-09) — MCP Resources for sink listing + per-sink stats.\n// Plan 06-04 (CON-04 + D-A2b) — contract Resource URI constants live alongside.\nexport {\n readListSinks,\n readMemoryStats,\n RESOURCE_URI_LIST_SINKS,\n RESOURCE_URI_LIST_BRIEFS,\n RESOURCE_URI_MEMORY_STATS,\n RESOURCE_URI_LIST_CONTRACTS,\n RESOURCE_URI_LIST_CONTRACT_VERBS,\n RESOURCE_URI_SOURCES,\n RESOURCE_URI_VAULTS,\n RESOURCE_URI_MODELS,\n RESOURCE_URI_RECENT,\n RESOURCE_URI_STATS,\n RESOURCE_URI_BACKLINKS,\n} from \"./resources/index.js\";\nexport type {\n ListSinksResource,\n ListSinkEntry,\n MemoryStatsResource,\n MemoryStatsEntry, // vault-memory:no-telemetry-ok\n} from \"./resources/index.js\";\n","/**\n * Canonical RESOURCES literal — the single source of truth for the\n * MCP `resources/list` surface.\n *\n * Mirrors src/tool-registry.ts (TOOLS). Consumed by:\n * - evals/v1-baseline/dump-resources.mjs (snapshot generation)\n * - evals/v1-baseline/baseline.test.ts (snapshot equality + length === 13)\n * - src/server.ts (registerResource metadata source)\n *\n * Plan 08-05 (REL-08): 10 entries — 5 pre-existing (memory-sinks,\n * memory-stats, briefs, contracts, contract-verbs) + 5 newly promoted\n * from v1 tools (vaults, models, recent, stats, backlinks).\n *\n * SOURCES-REGISTRY.md §5 (Stage 2): +3 peer-MCP source discovery\n * resources (sources, source-tools, source-tool) → 13 entries.\n *\n * Two URI shapes appear here:\n * - Static URI (e.g. `vault-memory://memory/sinks`, `vault-memory://vaults`):\n * a single concrete URI; no template variables.\n * - Templated URI (e.g. `vault-memory://models/{vault}`): SDK 1.29\n * ResourceTemplate variables expand at read time.\n *\n * The `list_backlinks` entry uses **RFC 6570 reserved expansion** on the\n * `docId` variable — `vault-memory://backlinks/{vault}/{+docId}` — so a\n * docId like `notes/sub/file.md` (with embedded `/`) parses as a single\n * value instead of being truncated at the first `/`. Without the leading\n * `+`, default expansion matches only one path segment.\n */\n\nexport interface ResourceEntry {\n readonly name: string;\n readonly uriTemplate: string;\n readonly description: string;\n readonly mimeType: \"application/json\";\n}\n\nexport const RESOURCES: readonly ResourceEntry[] = [\n // ─── Phase 2 (Plan 02-06 / MEM-09) ──────────────────────────────────────\n {\n name: \"memory-sinks\",\n uriTemplate: \"vault-memory://memory/sinks\",\n description:\n \"Configured + auto-discovered MemorySinks (name, handle, vault, contract, default). \" +\n \"Read to discover where memory documents (record_observation, supersede) land.\",\n mimeType: \"application/json\",\n },\n {\n name: \"memory-stats\",\n uriTemplate: \"vault-memory://memory/stats\",\n description:\n \"Per-sink document counts, by_type / by_status breakdowns, and last memory-write timestamp. \" +\n \"Polled — re-read to refresh.\",\n mimeType: \"application/json\",\n },\n // ─── Phase 5 (Plan 05-04 / BRF-09) ──────────────────────────────────────\n {\n name: \"briefs\",\n uriTemplate: \"vault-memory://briefs\",\n description:\n \"Discovery of compiled briefs by target. Supports optional `?target=` \" +\n \"substring filter on `properties.target`. Includes `active`, `stale`, and \" +\n \"`superseded` entries so callers can build their own filter / inspect the \" +\n \"supersede chain. BRF-09.\",\n mimeType: \"application/json\",\n },\n // ─── Phase 6 (Plan 06-04 / CON-04 + D-A2b) ──────────────────────────────\n {\n name: \"contracts\",\n uriTemplate: \"vault-memory://contracts/{vault}\",\n description:\n \"Discovery of task contracts available in a vault (CON-04). Each entry \" +\n \"carries name, description, source/sink counts, and write_back boolean. \" +\n \"Optional `?source=` filters to contracts declaring a source \" +\n \"whose handle starts with the given prefix.\",\n mimeType: \"application/json\",\n },\n {\n name: \"contract-verbs\",\n uriTemplate: \"vault-memory://contract-verbs/{vault}\",\n description:\n \"List baseline assembly verbs + custom (mcp://) verbs in use, with \" +\n \"invocation_count + last_seen aggregated from contract_audit (D-A2b). \" +\n \"Baseline verbs are constant per ADR-006 §Decision 3.\",\n mimeType: \"application/json\",\n },\n // ─── SOURCES-REGISTRY.md §5 (Stage 2) — peer-MCP source discovery ───────\n {\n name: \"sources\",\n uriTemplate: \"vault-memory://sources\",\n description:\n \"List peer MCP servers vault-memory connects to, with per-source status \" +\n \"(connected/unavailable/unreachable), tool_count, and last_refreshed. \" +\n \"vault-memory itself is not included. SOURCES-REGISTRY §5.1.\",\n mimeType: \"application/json\",\n },\n {\n name: \"source-tools\",\n uriTemplate: \"vault-memory://sources/{name}/tools\",\n description:\n \"List the cached tools/list for one peer MCP source. Empty when the \" +\n \"source is not connected. SOURCES-REGISTRY §5.2.\",\n mimeType: \"application/json\",\n },\n {\n name: \"source-tool\",\n uriTemplate: \"vault-memory://sources/{name}/tools/{tool}\",\n description:\n \"Read a single tool's schema from one peer MCP source, inlined from the \" +\n \"cached tools/list. SOURCES-REGISTRY §5.3.\",\n mimeType: \"application/json\",\n },\n // ─── Phase 8 (Plan 08-05 / REL-08) — promoted from v1 tools ─────────────\n {\n name: \"vaults\",\n uriTemplate: \"vault-memory://vaults\",\n description:\n \"List configured vaults with their status (note count, last indexed run). \" +\n \"Promoted from the `list_vaults` MCP tool in v2.0.0; the tool remains callable \" +\n \"through v2.x.\",\n mimeType: \"application/json\",\n },\n {\n name: \"models\",\n uriTemplate: \"vault-memory://models/{vault}\",\n description:\n \"List all embedding models registered for a vault, with dim, active flag, and \" +\n \"how many chunks have been embedded under each. Promoted from the `list_models` \" +\n \"MCP tool in v2.0.0; the tool remains callable through v2.x.\",\n mimeType: \"application/json\",\n },\n {\n name: \"recent\",\n uriTemplate: \"vault-memory://recent/{vault}\",\n description:\n \"List recently modified notes (mtime DESC) for a vault. Use for agent \" +\n \"self-orientation: 'what has the user been working on lately?'. Promoted from \" +\n \"the `recent_notes` MCP tool in v2.0.0; the tool remains callable through v2.x.\",\n mimeType: \"application/json\",\n },\n {\n name: \"stats\",\n uriTemplate: \"vault-memory://stats/{vault}\",\n description:\n \"Vault overview for agent self-orientation: note/word counts, top tags, top \" +\n \"frontmatter keys, embedding model, last index run. Promoted from the \" +\n \"`vault_stats` MCP tool in v2.0.0; the tool remains callable through v2.x.\",\n mimeType: \"application/json\",\n },\n {\n name: \"backlinks\",\n // RFC 6570 reserved expansion on docId: `{+docId}` preserves `/` in the\n // variable so multi-segment paths like `notes/sub/file.md` parse as a\n // single value. Without the `+`, the default expansion stops at the\n // first `/`. See Plan 08-05 §B2 for the acceptance test.\n uriTemplate: \"vault-memory://backlinks/{vault}/{+docId}\",\n description:\n \"Find all notes that link TO a given note. The `docId` segment uses RFC 6570 \" +\n \"reserved expansion ({+docId}) so multi-segment paths (e.g. `notes/sub/file.md`) \" +\n \"are preserved verbatim. Promoted from the `list_backlinks` MCP tool in v2.0.0; \" +\n \"the tool remains callable through v2.x.\",\n mimeType: \"application/json\",\n },\n];\n","/**\n * `handleRecordObservation` — the MEM-02 controller.\n *\n * Authors a new memory observation under a labeled `MemorySink`. Sugar\n * arguments (`claim`, `evidence`, `confidence`, `type`) pre-fill the\n * contract-required keys. The caller-supplied `properties` bag is\n * filtered to drop the 8 provenance-critical keys\n * (`source`, `evidence`, `confidence`, `observed_at`, `type`, `status`,\n * `superseded_by`, `superseded_reason`) BEFORE merge, then the sugar\n * values are applied LAST. Result: contract-allowed extras (tags,\n * expires_at, priority, etc.) flow through unchanged — D-02\n * escape-hatch preserved — but the provenance trail can never be\n * weakened by the caller (WR-07 closure).\n *\n * The controller never pre-validates beyond required-args presence;\n * contract enforcement is the validator's job at the\n * `DeliveryAdapter.write()` chokepoint (Plan 02-03 wired). When the\n * delivery returns a `WriteConflict`, the controller returns it\n * unchanged so the caller observes the structured Phase 2 envelope\n * (sinkName / key / observedValue / suggestion).\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. Slug derivation is pure string ops;\n * `node:crypto` is used for the 6-char hash suffix to avoid same-day\n * collisions.\n */\n\nimport { createHash, randomBytes } from \"node:crypto\";\nimport type { DeliveryAdapter, WriteResult } from \"../../adapters/delivery/types.js\";\nimport { formatDocId } from \"../../adapters/registry.js\";\nimport type { SourceConnector } from \"../../adapters/source/types.js\";\nimport type { Document } from \"../../types.js\";\nimport type { VaultManager } from \"../../vault/index.js\";\nimport type { MemorySinkRegistry } from \"../registry.js\";\n\n/** Naming subfolder used by the default-memory-v1 contract. */\nconst OBSERVATIONS_SUBFOLDER = \"observations/\";\n\n/** Max number of times we retry the DocId-collision avoidance loop. */\nconst MAX_COLLISION_RETRIES = 3;\n\n/**\n * Provenance-critical keys that callers MAY NOT override via the\n * `properties` escape-hatch. The validator at the DeliveryAdapter\n * chokepoint trusts these values; allowing caller override would let\n * a malicious or buggy agent weaken its own provenance trail.\n *\n * WR-07 closure + D-02 refinement: D-02's \"caller keys win over sugar\n * defaults\" rule is EXPLICITLY scoped to non-provenance extras (e.g.\n * tags, expires_at, priority). Provenance keys (the 8 listed below)\n * come exclusively from validated MCP args. The validator at\n * `DeliveryAdapter.write()` (Guard A/B, Plan 02-03) remains the single\n * source of truth for which non-protected keys the contract accepts.\n */\nconst PROTECTED_PROVENANCE_KEYS = new Set([\n \"source\",\n \"evidence\",\n \"confidence\",\n \"observed_at\",\n \"type\",\n \"status\",\n \"superseded_by\",\n \"superseded_reason\",\n]);\n\n/**\n * Dependencies — supplied by the server bootstrap. Pure interface so\n * tests can wire fakes without touching the file system seam.\n */\nexport interface RecordObservationDeps {\n memorySinkRegistry: MemorySinkRegistry;\n manager: VaultManager;\n /**\n * Resolve the `DeliveryAdapter` instance for a vault name. The\n * controller never instantiates adapters itself — bootstrap owns\n * adapter lifetimes.\n */\n deliveryAdapterFor: (vaultName: string) => DeliveryAdapter;\n /**\n * Resolve the `SourceConnector` instance for a vault name. The\n * controller uses `connector.exists(docId)` to detect path\n * collisions on the same-day same-claim retry path. Bootstrap\n * (Plan 02-03b) supplies this closure.\n */\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\nexport interface RecordObservationArgs {\n vault: string;\n claim: string;\n evidence: string[];\n confidence: \"direct\" | \"inferred\" | \"uncertain\";\n type: string;\n /** Bare sink name OR full `obsidian-fs://…` handle. Defaults to the vault's default sink. */\n sink?: string;\n /**\n * Escape-hatch: contract-allowed extras merged AFTER sugar args.\n * Caller-supplied keys win — D-02.\n */\n properties?: Record;\n}\n\n/**\n * Slugify a `claim` string for use in the date-slug naming pattern.\n *\n * Rules:\n * - lowercase\n * - strip accents via `normalize(\"NFD\")` + combining-mark removal\n * - replace non-ASCII-alnum with hyphens\n * - collapse repeated hyphens\n * - trim leading/trailing hyphens\n * - cap at 60 chars (without breaking mid-word past the cap)\n */\nfunction slugify(claim: string): string {\n const stripped = claim\n .normalize(\"NFD\")\n // Strip combining diacritical marks (U+0300–U+036F). IN-04: explicit\n // Unicode-escape form is source-stable; some editors / log\n // aggregators silently drop literal combining characters and\n // produce an empty char-class.\n .replace(/[\\u0300-\\u036F]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n if (stripped.length <= 60) return stripped || \"observation\";\n return stripped.slice(0, 60).replace(/-+$/g, \"\") || \"observation\";\n}\n\n/**\n * Compute a 6-character hex hash suffix for collision avoidance within\n * the same day. Mixes `claim`, `observed_at`, and an optional `salt`\n * (retry counter) so consecutive retries produce different suffixes.\n */\nfunction hashSuffix(claim: string, observedAt: string, salt = \"\"): string {\n return createHash(\"sha256\")\n .update(`${claim}\\x00${observedAt}\\x00${salt}`)\n .digest(\"hex\")\n .slice(0, 6);\n}\n\n/**\n * Extract the `YYYY-MM-DD` portion of an ISO-8601 timestamp.\n * Works for both `Z`-suffixed and `+HH:MM` variants because the date\n * prefix is always the first 10 characters of an ISO string.\n */\nfunction dateSlug(isoTimestamp: string): string {\n return isoTimestamp.slice(0, 10);\n}\n\n/**\n * Record a new memory observation. See file header for D-02 / D-03\n * semantics.\n *\n * Returns the `WriteResult` discriminated union from the delivery\n * adapter UNCHANGED — never renames `newHash` to `hash`, never re-\n * shapes a `WriteConflict`.\n */\nexport async function handleRecordObservation(\n deps: RecordObservationDeps,\n args: RecordObservationArgs,\n): Promise {\n // ── Resolve the target sink ──────────────────────────────────────────────\n const registry = deps.memorySinkRegistry;\n const sink =\n args.sink !== undefined\n ? registry.resolveMemorySink(args.sink)\n : registry.getDefaultMemorySink();\n\n if (sink.vault !== args.vault) {\n throw new Error(`Sink \"${sink.name}\" belongs to vault \"${sink.vault}\", not \"${args.vault}\"`);\n }\n\n // ── Build the property bag ───────────────────────────────────────────────\n //\n // WR-07 closure + D-02 refinement: strip provenance-critical keys from\n // caller-supplied `properties` BEFORE merging, then place sugar LAST so\n // the 8 protected keys (source / evidence / confidence / observed_at /\n // type / status / superseded_by / superseded_reason) cannot be weakened\n // by the caller. Non-provenance extras (tags, expires_at, priority,\n // custom_tag, etc.) still win over absent sugar defaults — the D-02\n // escape-hatch is preserved for contract-allowed extras.\n const observedAtDefault = new Date().toISOString();\n const sugarProps: Record = {\n source: \"agent\",\n observed_at: observedAtDefault,\n status: \"active\",\n confidence: args.confidence,\n evidence: args.evidence,\n type: args.type,\n superseded_by: null,\n };\n const callerExtras: Record = {};\n if (args.properties !== undefined) {\n for (const [k, v] of Object.entries(args.properties)) {\n if (!PROTECTED_PROVENANCE_KEYS.has(k)) {\n callerExtras[k] = v;\n }\n }\n }\n // callerExtras FIRST, sugarProps LAST — defensive ordering means even\n // if the filter is ever bypassed, sugar still wins for provenance keys.\n const properties: Record = {\n ...callerExtras,\n ...sugarProps,\n };\n\n // ── Mint a DocId, retrying with fresh hash suffix on path collision ─────\n const observedAtForNaming =\n typeof properties.observed_at === \"string\" ? properties.observed_at : observedAtDefault;\n const slug = slugify(args.claim);\n\n const delivery = deps.deliveryAdapterFor(args.vault);\n const source = deps.sourceConnectorFor(args.vault);\n\n let attempt = 0;\n while (attempt < MAX_COLLISION_RETRIES) {\n // WR-04 (b): per-retry salt is cryptographically random — six hex\n // chars of fresh entropy. Two same-millisecond calls with identical\n // claim/observed_at no longer produce identical collision chains.\n const suffix = hashSuffix(args.claim, observedAtForNaming, randomBytes(3).toString(\"hex\"));\n const filename = `${dateSlug(observedAtForNaming)}-${slug}-${suffix}.md`;\n // `sink.resolveToRelativePath` already ends in \"/\" (enforced by the\n // MemorySinkHandle regex in Plan 02-02). Safe to concatenate.\n const relativeResource = sink.resolveToRelativePath + OBSERVATIONS_SUBFOLDER + filename;\n const docId = formatDocId(\"obsidian-fs\", args.vault, relativeResource);\n\n // Path-collision check: if the candidate DocId already resolves to\n // an existing file, retry with a fresh hash6 salt rather than\n // overwriting. The delivery would otherwise create-or-overwrite per\n // its `naming: \"caller-provided\"` capability.\n const collides = await source.exists(docId);\n if (collides) {\n attempt += 1;\n continue;\n }\n\n const partialDoc: Partial = {\n id: docId,\n title: args.claim.slice(0, 80),\n properties,\n blocks: [{ kind: \"paragraph\", text: args.claim }],\n };\n\n // Delegate to the delivery — the validator at the chokepoint runs\n // Guard A + Guard B + sentinel. WriteConflicts (including\n // contract-validator rejections like non_agent_write_inside_sink)\n // are returned UNCHANGED.\n return await delivery.write(docId, partialDoc, { sink: sink.handle });\n }\n\n // WR-04 (a): distinct reason on retry exhaustion so callers can branch\n // on a meaningful recovery path (vary the claim text, the observed_at\n // timestamp, or retry later) — `permission_denied` everywhere else\n // means \"vault is read-only\" and would mislead automatic retry logic.\n return {\n ok: false,\n reason: \"collision_retry_exhausted\",\n message:\n `Failed to mint unique DocId after ${MAX_COLLISION_RETRIES} attempts. ` +\n `Vary the claim text, the observed_at timestamp, or retry the call.`,\n };\n}\n","/**\n * `handleSupersede` — the MEM-04 controller.\n *\n * Marks an existing memory document as superseded by a replacement\n * document. Forward-only per D-03: this controller writes\n * `status: \"superseded\"`, `superseded_by: `, and\n * `superseded_reason: ` on the OLD doc ONLY — the replacement\n * is never touched. Back-link materialization is deferred to the\n * Phase 4 graph layer (it can be derived from a single property scan).\n *\n * Single OCC `delivery.update()` call. The OLD doc's read-side hash\n * (`Document.hash`) is fetched via `SourceConnector.readDocument` and\n * passed as `opts.expectedHash` so concurrent edits surface as a\n * `hash_mismatch` WriteConflict — returned UNCHANGED.\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. The DocId parsing chain\n * (`parseDocId` / `decomposeDocId`) lives in `src/adapters/registry.ts`.\n */\n\nimport type { DeliveryAdapter, UpdateResult } from \"../../adapters/delivery/types.js\";\nimport type { SourceConnector } from \"../../adapters/source/types.js\";\nimport { decomposeDocId, parseDocId } from \"../../adapters/registry.js\";\nimport type { Document } from \"../../types.js\";\nimport type { VaultManager } from \"../../vault/index.js\";\nimport type { MemorySinkRegistry } from \"../registry.js\";\n\nexport interface SupersedeDeps {\n memorySinkRegistry: MemorySinkRegistry;\n manager: VaultManager;\n deliveryAdapterFor: (vaultName: string) => DeliveryAdapter;\n /** Reads the OLD doc's current hash via `connector.readDocument(id)`. */\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\nexport interface SupersedeArgs {\n /** DocId of the document being superseded. */\n doc_id: string;\n /** DocId of the replacement document. */\n replacement_doc_id: string;\n /** Non-empty rationale; written to `superseded_reason` on the OLD doc. */\n reason: string;\n}\n\n/**\n * Mark the OLD doc as superseded. See file header for D-03 semantics.\n * Returns the `UpdateResult` from the delivery adapter UNCHANGED —\n * `newHash` is the post-update hash; never re-shaped to `hash`.\n */\nexport async function handleSupersede(\n deps: SupersedeDeps,\n args: SupersedeArgs,\n): Promise {\n // Parse both DocIds at the controller boundary so malformed values\n // surface as helpful diagnostics. The replacement DocId is parsed\n // for validation only — we never dereference it (D-03).\n const oldId = parseDocId(args.doc_id);\n parseDocId(args.replacement_doc_id);\n\n // Identify the OLD doc's owning vault from its authority component.\n const { authority: vaultName } = decomposeDocId(oldId);\n\n // Confirm OLD doc lives inside a memory sink. Supersede applies only\n // to memory documents; user notes are immutable from the agent's\n // perspective per the Phase 2 safety invariant.\n const sink = deps.memorySinkRegistry.findSinkContaining(oldId);\n if (sink === null) {\n throw new Error(\n `supersede() target ${oldId} is not inside any configured MemorySink; ` +\n `supersede applies to memory documents only.`,\n );\n }\n\n // Fetch the OLD doc's current Document.hash via the read-side seam.\n // This is the canonical content hash (distinct from\n // WriteSuccess.newHash) that the OCC contract consumes. We also use\n // the read result to merge the supersede triple onto the OLD doc's\n // existing property bag — the delivery chokepoint validator runs the\n // contract schema against the PATCH ALONE (per Plan 02-03's\n // conformance test 17: \"update() routes through the SAME validator\n // (missing observed_at refused)\"), so a minimal patch like\n // `{status, superseded_by, superseded_reason}` would falsely fail\n // missing_provenance on `source`/`observed_at`/etc. We therefore\n // hand the delivery a \"full\" patch — existing props with the three\n // supersede keys layered on top — so the on-disk frontmatter\n // semantics are unchanged (the delivery itself ALSO shallow-merges\n // with disk before writing, so this is idempotent), and the\n // validator's standalone schema check passes.\n const source = deps.sourceConnectorFor(vaultName);\n const oldDoc = await source.readDocument(oldId);\n\n // Strip the adapter-injected `wikilinks` array (D-05): obsidian-fs\n // surfaces wikilinks via `Document.properties.wikilinks` when\n // reading, but the field is never written back into frontmatter.\n // The delivery's `stripWikilinks` runs the same trim later, but\n // keeping the patch clean avoids a no-op diff in the merged set.\n const { wikilinks: _w, ...existingProps } = oldDoc.properties as { wikilinks?: unknown } & Record<\n string,\n unknown\n >;\n\n // Forward-only — writes ONLY on the OLD doc. The replacement doc is\n // never touched (D-03; back-link materialization is the Phase 4\n // graph layer's responsibility).\n const patch: Partial = {\n properties: {\n ...existingProps,\n status: \"superseded\",\n superseded_by: args.replacement_doc_id,\n superseded_reason: args.reason,\n },\n };\n\n return await deps.deliveryAdapterFor(vaultName).update(oldId, patch, {\n expectedHash: oldDoc.hash,\n sink: sink.handle,\n });\n}\n","/**\n * `handleRecall` — the MEM-03 controller.\n *\n * Retrieves memory documents from one or more labeled `MemorySinks`,\n * filtered by provenance (`min_confidence`, `types`, `max_age_days`)\n * and ranked by recency (`observed_at` DESC, `mtime` DESC tiebreak).\n * Returns the Phase 3 citation-packet floor: an 8-field\n * `CitationPacket` per result (D-01).\n *\n * Pipeline (per RESEARCH §Q7 — the recommended approach):\n *\n * 1. Resolve sinks: single sink (when args.sink set) or all configured.\n * 2. Run `searchHybrid` with a generous `top_k` (200) across the\n * sinks' owning vaults.\n * 3. Post-filter the candidates to those whose path begins with one\n * of the resolved sinks' `resolveToRelativePath` prefixes.\n * 4. De-duplicate by (vault, notePath) — a single doc may surface as\n * multiple chunks; we keep the best-scoring chunk's identity.\n * 5. Load each candidate's full `Document` via the SourceConnector\n * seam so we get the canonical `Document.hash` + full property\n * bag including provenance keys.\n * 6. Apply filters in this exact order (CONTEXT.md D-01):\n * a. Hide `status: \"superseded\"` (always; opt-in retrieval is\n * Phase 3 ASM-08 territory).\n * b. `min_confidence` — ordinal compare (direct=3, inferred=2,\n * uncertain=1).\n * c. `types` — exact match against `properties.type`.\n * d. `max_age_days` — `now - Date.parse(observed_at)` ≤ window.\n * 7. Sort `observed_at` DESC with `mtime` DESC tiebreak.\n * 8. Slice to `args.limit ?? 20` AFTER filter+sort (per D-01).\n * 9. Map each surviving Document → CitationPacket.\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. All access goes through the registry\n * (sink resolution), the SourceConnector (property reads via\n * `readDocument`), and the search service.\n *\n * Contingency (NOT shipped in Phase 2): if benchmarks ever show the\n * post-filter is too slow on a large vault, the user-approved fallback\n * is to add an optional `include_paths?: string[]` parameter to\n * `search_hybrid` and pass the sinks' resolved path prefixes. That\n * change is purely additive (does not break v1.x callers). Phase 2\n * ships the post-filter approach; the fallback is documented here for\n * the day the benchmark requires it.\n */\n\nimport type { SourceConnector } from \"../../adapters/source/types.js\";\nimport { decomposeDocId, formatDocId } from \"../../adapters/registry.js\";\nimport type { Document, SearchHit } from \"../../types.js\";\nimport type { Vault, VaultManager } from \"../../vault/index.js\";\nimport type { MemorySinkRegistry } from \"../registry.js\";\nimport { type CitationPacket, displayUrlFor, toCitationPacket } from \"../citation-packet.js\";\n\n/** Default limit when the caller does not specify one. */\nconst DEFAULT_LIMIT = 20;\n/** Generous top_k for the inner hybrid search; post-filter narrows. */\nconst RECALL_HYBRID_TOP_K = 200;\n\n/**\n * Input shape for the inner `searchHybrid` call. Mirrors the subset of\n * Phase 1's `HybridSearchOptions` that recall actually uses; passed as\n * a closure rather than imported directly so unit tests can stub.\n */\nexport interface RecallSearchHybridInput {\n query: string;\n vaults: readonly Vault[];\n topK: number;\n}\n\nexport interface RecallDeps {\n memorySinkRegistry: MemorySinkRegistry;\n manager: VaultManager;\n /** Resolve the `SourceConnector` instance for a vault name. */\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n /** Hybrid-search entry point. Bootstrap supplies the production closure. */\n searchHybrid: (input: RecallSearchHybridInput) => Promise;\n}\n\nexport interface RecallArgs {\n query: string;\n min_confidence?: \"direct\" | \"inferred\" | \"uncertain\";\n types?: string[];\n max_age_days?: number;\n sink?: string;\n limit?: number;\n vaults?: string[];\n}\n\n/** Ordinal rank for `confidence`. Unknown / undefined → 0. */\nfunction confidenceRank(c?: string): number {\n switch (c) {\n case \"direct\":\n return 3;\n case \"inferred\":\n return 2;\n case \"uncertain\":\n return 1;\n default:\n return 0;\n }\n}\n\n/**\n * Coerce a property value into an `observed_at` ISO timestamp string\n * suitable for both `Date.parse` (for age math) and string-comparison\n * sort (lexicographic ISO ordering).\n *\n * YAML frontmatter can surface ISO-8601 timestamps as either:\n * - JS `Date` objects (when js-yaml / gray-matter parses canonical\n * ISO strings via the `tag:yaml.org,2002:timestamp` rule), or\n * - raw strings (when quoted or schema-coerced).\n *\n * Returns `null` when the value is missing or unparseable. Callers use\n * `null` as the signal to drop the doc (a doc without a parseable\n * `observed_at` cannot be ranked by recency).\n */\nfunction observedAtIso(value: unknown): string | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString();\n }\n if (typeof value === \"string\") {\n const t = Date.parse(value);\n return Number.isNaN(t) ? null : new Date(t).toISOString();\n }\n return null;\n}\n\n/**\n * Retrieve memory docs as citation packets. See the file header for\n * the full pipeline; this function is the public entry point.\n */\nexport async function handleRecall(deps: RecallDeps, args: RecallArgs): Promise {\n // 1) Resolve sinks. Throws on unknown name — the server wraps the\n // exception in errorResponse() at the dispatch boundary.\n const sinks = args.sink\n ? [deps.memorySinkRegistry.resolveMemorySink(args.sink)]\n : deps.memorySinkRegistry.listMemorySinks();\n if (sinks.length === 0) return [];\n\n // 2) Compute the set of vaults to search: the distinct sink.vault\n // values, optionally intersected with args.vaults.\n const sinkVaultNames = new Set(sinks.map((s) => s.vault));\n const allowedVaultNames = args.vaults\n ? new Set(args.vaults.filter((v) => sinkVaultNames.has(v)))\n : sinkVaultNames;\n if (allowedVaultNames.size === 0) return [];\n\n const vaults: Vault[] = [];\n for (const name of allowedVaultNames) {\n vaults.push(deps.manager.require(name));\n }\n\n // 3) Inner hybrid search with a generous top_k.\n const candidates = await deps.searchHybrid({\n query: args.query,\n vaults,\n topK: RECALL_HYBRID_TOP_K,\n });\n\n // 4) Post-filter to sink-resolved paths. A candidate matches a sink\n // iff hit.vault === sink.vault AND hit.notePath starts with\n // sink.resolveToRelativePath (which already carries a trailing\n // slash by the MemorySinkHandle invariant).\n const sinkMatchers = sinks\n .filter((s) => allowedVaultNames.has(s.vault))\n .map((s) => ({ vault: s.vault, prefix: s.resolveToRelativePath }));\n const inSink = candidates.filter((hit) =>\n sinkMatchers.some((m) => hit.vault === m.vault && hit.notePath.startsWith(m.prefix)),\n );\n\n // 5) De-duplicate by (vault, notePath) — a doc can produce multiple\n // chunk hits; we keep the highest-scoring chunk's metadata.\n const uniqueByPath = new Map();\n for (const hit of inSink) {\n const key = `${hit.vault}::${hit.notePath}`;\n const existing = uniqueByPath.get(key);\n if (!existing || hit.score > existing.score) {\n uniqueByPath.set(key, hit);\n }\n }\n if (uniqueByPath.size === 0) return [];\n\n // 6) Load full Documents via the source seam for canonical hash +\n // full property bag (including the provenance keys we need to\n // filter on).\n const docs: Document[] = [];\n for (const hit of uniqueByPath.values()) {\n const docId = formatDocId(\"obsidian-fs\", hit.vault, hit.notePath);\n try {\n const doc = await deps.sourceConnectorFor(hit.vault).readDocument(docId);\n docs.push(doc);\n } catch {\n // A search hit pointing to a now-deleted file is harmless;\n // silently drop it. (Watcher catch-up usually keeps the index\n // in sync, but we don't fail the whole call on one stale row.)\n }\n }\n\n // 7) Apply provenance filters in the documented order.\n const now = Date.now();\n const minRank = args.min_confidence ? confidenceRank(args.min_confidence) : 0;\n const typeSet = args.types && args.types.length > 0 ? new Set(args.types) : null;\n const maxAgeMs = args.max_age_days !== undefined ? args.max_age_days * 86_400_000 : null;\n\n const filtered = docs.filter((doc) => {\n const props = (doc.properties ?? {}) as Record;\n // 7a) Hide superseded by default.\n if (props.status === \"superseded\") return false;\n // 7b) min_confidence ordinal compare.\n if (minRank > 0) {\n const docConf = typeof props.confidence === \"string\" ? props.confidence : undefined;\n if (confidenceRank(docConf) < minRank) return false;\n }\n // 7c) types exact match.\n if (typeSet) {\n const t = typeof props.type === \"string\" ? props.type : undefined;\n if (t === undefined || !typeSet.has(t)) return false;\n }\n // 7d) max_age_days against observed_at.\n if (maxAgeMs !== null) {\n const iso = observedAtIso(props.observed_at);\n if (iso === null) return false;\n if (now - Date.parse(iso) > maxAgeMs) return false;\n }\n return true;\n });\n\n // 8) Sort: observed_at DESC, mtime DESC tiebreak.\n filtered.sort((a, b) => {\n const ao = observedAtIso((a.properties as Record)?.observed_at) ?? \"\";\n const bo = observedAtIso((b.properties as Record)?.observed_at) ?? \"\";\n if (ao !== bo) {\n // ISO-8601 strings sort lexicographically when both well-formed.\n return ao < bo ? 1 : -1;\n }\n return b.mtime - a.mtime;\n });\n\n // 9) Truncate AFTER sort (per D-01).\n const limit = args.limit ?? DEFAULT_LIMIT;\n const top = filtered.slice(0, limit);\n\n // 10) Map each surviving Document → CitationPacket. The display URL\n // is computed via the source adapter's `formatDisplayUrl` seam\n // (ADR-002 §SourceConnector) — recall does not encode adapter-\n // specific URL conventions inline.\n return top.map((doc) => {\n const { authority: vaultName } = decomposeDocId(doc.id);\n const source = deps.sourceConnectorFor(vaultName);\n return toCitationPacket(doc, displayUrlFor(doc.id, source));\n });\n}\n","/**\n * Barrel for the memory MCP-tool controllers.\n *\n * Plan 02-04 ships:\n * - `handleRecordObservation` — MEM-02 controller (record_observation tool).\n * - `handleSupersede` — MEM-04 controller (supersede tool).\n *\n * Plan 02-05 adds:\n * - `handleRecall` — MEM-03 controller (recall tool).\n */\n\nexport { handleRecordObservation } from \"./record-observation.js\";\nexport type { RecordObservationArgs, RecordObservationDeps } from \"./record-observation.js\";\n\nexport { handleSupersede } from \"./supersede.js\";\nexport type { SupersedeArgs, SupersedeDeps } from \"./supersede.js\";\n\nexport { handleRecall } from \"./recall.js\";\nexport type { RecallArgs, RecallDeps, RecallSearchHybridInput } from \"./recall.js\";\n","/**\n * Branded `ChunkId` for the public Phase 5 / D-04 chunk-identifier.\n *\n * Format: `#chunk-` where `` is the 7-hex\n * output of `computeChunkIdFragment` (`src/chunker/chunk-id.ts`).\n *\n * Mirrors the IIFE-closed branding idiom from\n * `src/adapters/registry.ts:67-94` — the only validating parser is\n * exported; the raw brand-cast (`mint`) is closed inside the IIFE so\n * arbitrary strings cannot reach the brand without passing the regex\n * check.\n *\n * Pure module. No fs / gray-matter / chokidar / path imports.\n */\n\nimport type { DocId } from \"../types.js\";\nimport type { ChunkId } from \"../types.js\";\n\n/**\n * `chunk_id_fragment` shape: exactly 7 lowercase hex characters.\n * Matches the slice from `computeChunkHash(text).slice(7, 14)`.\n */\nconst FRAGMENT_REGEX = /^[0-9a-f]{7}$/;\n\n/**\n * Public ChunkId shape: `:///#chunk-`.\n *\n * The DocId prefix is validated structurally here (lowercase scheme,\n * non-empty authority + resource); the full DocId-pattern test lives\n * in `src/adapters/registry.ts`. Both must accept the same DocId space.\n */\nconst CHUNK_ID_REGEX = /^([a-z][a-z0-9-]*:\\/\\/[^/]+\\/.+)#chunk-([0-9a-f]{7})$/;\n\nconst { parseChunkId, formatChunkId, decomposeChunkId } = (() => {\n const mint = (s: string): ChunkId => s as ChunkId;\n\n function format(docId: DocId, fragment: string): ChunkId {\n if (!FRAGMENT_REGEX.test(fragment)) {\n throw new Error(\n `Invalid chunk fragment: ${JSON.stringify(fragment)}. ` +\n \"Expected exactly 7 lowercase hex characters (per ADR-005 / D-04).\",\n );\n }\n return mint(`${docId}#chunk-${fragment}`);\n }\n\n function parse(s: string): ChunkId {\n if (!CHUNK_ID_REGEX.test(s)) {\n throw new Error(\n `Invalid ChunkId: ${JSON.stringify(s)}. ` + \"Expected #chunk-<7-hex-fragment>.\",\n );\n }\n return mint(s);\n }\n\n function decompose(id: ChunkId): { docId: DocId; fragment: string } {\n const m = CHUNK_ID_REGEX.exec(id);\n if (!m) {\n // Branding guarantees this branch is unreachable in well-typed\n // code, but a defensive check costs nothing.\n throw new Error(`Malformed ChunkId reached decomposeChunkId: ${JSON.stringify(id)}`);\n }\n return { docId: m[1] as DocId, fragment: m[2]! };\n }\n\n return { parseChunkId: parse, formatChunkId: format, decomposeChunkId: decompose };\n})();\n\nexport { parseChunkId, formatChunkId, decomposeChunkId };\nexport type { ChunkId };\n","/**\n * Phase 5 — brief `source_hashes` builder and recompute helper.\n *\n * `source_hashes: Record` is the staleness\n * contract per ADR-005 §\"Chunk-level source_hashes (ChunkId)\". The\n * brief carries one entry per cited chunk; the daemon walks\n * `brief_sources` (D-06 reverse-index) on a ChangeEvent and compares\n * `recorded_hash` to the current `computeChunkHash(text)` — divergence\n * flips the brief to `status: stale` with `changed_sources` populated.\n *\n * Pure module. The chunker helper (`src/chunker/chunk-id.ts`) is the\n * single source of truth for the hash; we re-export it here so the\n * `src/brief/` barrel is the one-stop import surface for brief\n * consumers. No fs / gray-matter / chokidar / path imports.\n */\n\nimport { computeChunkHash, computeChunkIdFragment } from \"../chunker/chunk-id.js\";\nimport { formatChunkId } from \"./chunk-id.js\";\nimport type { ChunkId } from \"../types.js\";\nimport type { BriefSourceHash, DocId } from \"../types.js\";\n\n// Re-export the canonical chunk-hash + chunk-id-fragment functions so\n// brief consumers can import everything they need from `src/brief/`.\n// The originals live in `src/chunker/chunk-id.ts` — there is exactly\n// one implementation site for the canonicalization algorithm.\nexport { computeChunkHash, computeChunkIdFragment };\n\n/**\n * Per-chunk input shape for `buildSourceHashes`. The brief layer\n * resolves source DocIds to chunks via the existing notes→chunks join\n * (the in-process resolver lives in slice 2, alongside `compile_brief`);\n * the helper here is intentionally decoupled from the DB so it can be\n * unit-tested against pure inputs.\n */\nexport interface ChunkSource {\n /** DocId of the document containing this chunk. */\n docId: DocId;\n /** 7-hex fragment from the `chunks.chunk_id_fragment` column. */\n fragment: string;\n /** Canonical chunk text (already pulled from `chunks.text`). */\n text: string;\n}\n\n/**\n * Build the `source_hashes` map for a brief. For each chunk in\n * `sources`, format the public ChunkId and compute the full\n * `\"sha256:\"` hash recorded at brief-compile time.\n *\n * Consumers (slice 2 `compile_brief`) resolve `sources` from\n * `source_doc_ids` via the notes+chunks join then pass the result here.\n * Keeping the DB join out of this module preserves the pure-function\n * discipline and lets the eval harness exercise the contract with\n * deterministic fixtures.\n */\nexport function buildSourceHashes(\n sources: readonly ChunkSource[],\n): Record {\n const out: Record = {};\n for (const chunk of sources) {\n const chunkId = formatChunkId(chunk.docId, chunk.fragment);\n out[chunkId] = computeChunkHash(chunk.text) as BriefSourceHash;\n }\n return out;\n}\n\n/**\n * Recompute the current hash for one chunk's canonical text. The\n * daemon uses this on each `ChangeEvent` to compare against\n * `brief_sources.recorded_hash` for an O(log N) staleness check.\n */\nexport function recomputeCurrentHash(text: string): BriefSourceHash {\n return computeChunkHash(text) as BriefSourceHash;\n}\n","/**\n * Phase 5 / D-10 — Capability-first LLM ladder for `compile_brief`.\n *\n * Resolves which LLM strategy a given `compile_brief` call should use,\n * in priority order:\n *\n * 1. MCP Sampling — `server.server.getClientCapabilities().sampling`\n * is present (host MCP client supports `sampling/create_message`).\n * 2. Local Ollama — `[brief.ollama] model = \"...\"` is set in\n * `config.toml` (the per-server `BriefConfig` block).\n * 3. Caller-supplied `prepared_text` — vault-memory stitches the\n * caller's verbatim text into the brief body.\n * 4. Structured error — `BriefLlmUnavailableError` carrying the\n * `attempted` array. The controller (`handleCompileBrief`)\n * translates this to `{ok: false, reason: \"no_llm_strategy_available\",\n * attempted, hint}` so the caller can choose its recovery path\n * (configure Ollama, switch to a sampling-capable client, or pass\n * prepared_text).\n *\n * `compileWithLlm` dispatches the resolved strategy and returns\n * `{body, model}`. MCP Sampling result content is a single discriminated\n * union block; we reject anything other than `type === \"text\"`.\n *\n * # Why server-level (not per-vault) Ollama config\n *\n * Slice 1 (Plan 05-01) added the `[brief]` block onto `AppConfig`, not\n * `VaultConfig`. The brief subsystem is a single LLM ladder shared by\n * all vaults the server hosts; per-vault Ollama config would let one\n * vault's brief compile bypass the server's licensed local LLM endpoint\n * without an obvious audit point. The ladder therefore consumes\n * `briefConfig?: BriefConfig` from `AppConfig`, threaded through the\n * controller's `Deps`.\n *\n * Pure module. No fs / gray-matter / chokidar / path imports.\n */\n\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n CreateMessageResult,\n CreateMessageResultWithTools,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { BriefConfig } from \"../types.js\";\nimport type { OllamaClient } from \"../ollama/client.js\";\n\n/**\n * Discriminated union returned by `resolveLlmStrategy`. The shape lets\n * `compileWithLlm` switch-dispatch without re-checking capabilities.\n */\nexport type LlmStrategy =\n | { kind: \"sampling\" }\n | { kind: \"ollama\"; model: string }\n | { kind: \"prepared_text\" }\n | { kind: \"unavailable\"; attempted: string[] };\n\n/**\n * Structured error emitted when no ladder tier resolves. The controller\n * catches this and translates to the `no_llm_strategy_available` MCP\n * tool error envelope.\n */\nexport class BriefLlmUnavailableError extends Error {\n public readonly attempted: string[];\n constructor(attempted: string[]) {\n super(`LLM unavailable; attempted: ${attempted.join(\", \")}`);\n this.name = \"BriefLlmUnavailableError\";\n this.attempted = attempted;\n }\n}\n\n/**\n * Translated when the MCP Sampling client refuses (throws). The\n * controller maps this to `{ok: false, reason: \"sampling_refused\"}`.\n * Kept distinct from `BriefLlmUnavailableError` so callers can branch\n * on it (refusal → retry later; unavailable → reconfigure).\n */\nexport class BriefLlmSamplingRefusedError extends Error {\n public override readonly cause: unknown;\n constructor(cause: unknown) {\n super(\"MCP Sampling refused\");\n this.name = \"BriefLlmSamplingRefusedError\";\n this.cause = cause;\n }\n}\n\n/**\n * Capability-first ladder resolution.\n *\n * `server` is the high-level `McpServer` (we read `.server.getClientCapabilities()`);\n * the test fixtures stand up a minimal stub of the same shape.\n *\n * `briefConfig` is the server-level `[brief]` block from `AppConfig`.\n * Undefined / missing `ollama.model` means tier 2 skips.\n */\nexport function resolveLlmStrategy(\n server: McpServer,\n briefConfig: BriefConfig | undefined,\n preparedText: string | undefined,\n): LlmStrategy {\n const attempted: string[] = [];\n\n // Tier 1: MCP Sampling capability — populated after the MCP initialize\n // handshake. Server bootstrap ordering guarantees compile_brief calls\n // always run post-handshake, so this read is safe.\n const caps = server.server.getClientCapabilities();\n if (caps?.sampling) {\n return { kind: \"sampling\" };\n }\n attempted.push(\"sampling\");\n\n // Tier 2: per-server Ollama config (`[brief.ollama] model = \"...\"`).\n const ollamaModel = briefConfig?.ollama?.model;\n if (typeof ollamaModel === \"string\" && ollamaModel.length > 0) {\n return { kind: \"ollama\", model: ollamaModel };\n }\n attempted.push(\"ollama\");\n\n // Tier 3: caller-supplied prepared_text.\n if (typeof preparedText === \"string\" && preparedText.length > 0) {\n return { kind: \"prepared_text\" };\n }\n attempted.push(\"prepared_text\");\n\n // Tier 4: structured error — `BriefLlmUnavailableError` at dispatch.\n return { kind: \"unavailable\", attempted };\n}\n\n/**\n * Tier dispatch. Returns the raw LLM body plus a `model` identifier\n * for audit-trail attribution. The body still needs to pass the D-11\n * `BriefBodyValidator` before delivery.write.\n *\n * Tier 1 wraps `server.server.createMessage` throws into\n * `BriefLlmSamplingRefusedError`; tier 2 lets `OllamaClient` errors\n * percolate (the controller wraps in try/catch and returns the\n * underlying error semantics unchanged).\n */\nexport async function compileWithLlm(\n strategy: LlmStrategy,\n server: McpServer,\n ollama: OllamaClient,\n prompt: { systemText: string; userText: string },\n maxTokens: number,\n preparedText?: string,\n): Promise<{ body: string; model: string }> {\n switch (strategy.kind) {\n case \"sampling\": {\n let result: CreateMessageResult | CreateMessageResultWithTools;\n try {\n result = await server.server.createMessage({\n messages: [\n {\n role: \"user\",\n content: { type: \"text\", text: prompt.userText },\n },\n ],\n maxTokens,\n systemPrompt: prompt.systemText,\n });\n } catch (err) {\n throw new BriefLlmSamplingRefusedError(err);\n }\n // `CreateMessageResult.content` is a single discriminated block;\n // the brief compile path only handles text. (The tool-enabled\n // overload returns an array, but we never pass `tools`.)\n const content = (result as CreateMessageResult).content;\n if (content === undefined || Array.isArray(content) || content.type !== \"text\") {\n const got =\n content === undefined ? \"undefined\" : Array.isArray(content) ? \"array\" : content.type;\n throw new Error(\n `MCP Sampling returned non-text content (type=${got}); brief compile expects text.`,\n );\n }\n return { body: content.text, model: result.model };\n }\n case \"ollama\": {\n const res = await ollama.chat({\n model: strategy.model,\n messages: [\n { role: \"system\", content: prompt.systemText },\n { role: \"user\", content: prompt.userText },\n ],\n options: { num_predict: maxTokens },\n });\n return { body: res.message.content, model: strategy.model };\n }\n case \"prepared_text\": {\n // Caller's text is stitched verbatim. The controller already\n // verified `preparedText` is a non-empty string in\n // `resolveLlmStrategy`; we re-check defensively here so a\n // misuse (calling compileWithLlm with kind:\"prepared_text\"\n // but no text) surfaces loudly instead of writing an empty body.\n if (typeof preparedText !== \"string\" || preparedText.length === 0) {\n throw new Error(\n \"compileWithLlm(prepared_text) called without a non-empty preparedText string\",\n );\n }\n return { body: preparedText, model: \"prepared_text\" };\n }\n case \"unavailable\": {\n throw new BriefLlmUnavailableError(strategy.attempted);\n }\n }\n}\n","/**\n * Phase 5 / D-11 — Brief body validator.\n *\n * Every brief carries a body that the LLM ladder produced. The\n * Phase 4 D-02 indexer materializes typed `wikilink` edges by parsing\n * `[[Title]]` references during the next index pass. To guarantee the\n * brief participates in the graph layer (so `expand` / `cluster` /\n * `list_backlinks` surface it), every cited source must appear in the\n * body as a wikilink — `[[Title]]` (preferred), `[[Title|alias]]`,\n * `[[Title#heading]]`, or `[[]]` (escape hatch).\n *\n * `validateAndPatchBody` is pure:\n * - Parses every `[[...]]` reference using the SAME regex Phase 4's\n * `src/indexer/extract-edges.ts` uses (any drift would break\n * `back-edge materialization`).\n * - Resolves each source DocId to a `Title` via `resolveTitle`.\n * - Collects DocIds that are NOT referenced (neither as title nor\n * as bare DocId).\n * - Appends `\\n\\n## Sources\\n- [[Title]]` per missing entry. The\n * footer is deliberately a markdown section so it round-trips\n * through gray-matter / js-yaml without semantic loss.\n *\n * Body validators that succeed return the body unchanged (byte-stable\n * — no whitespace insertion). Validators that patch return the\n * original body plus the footer; the LLM output is never mutated\n * mid-body.\n *\n * Pure module. No fs / gray-matter / chokidar / path imports.\n */\n\nimport type { DocId } from \"../types.js\";\n\n/**\n * Wikilink regex matching `[[Title]]`, `[[Title|alias]]`,\n * `[[Title#heading]]`, `[[Title#heading|alias]]`. Mirrors the pattern\n * `src/indexer/extract-edges.ts` uses so Phase 4 indexer back-edges\n * stay consistent.\n *\n * Note: the capture group extracts the bare title (everything before\n * `|` or `#`); the validator compares this against `resolveTitle(id)`\n * AND against the raw `id` (DocId escape hatch).\n */\nconst WIKILINK_RE = /\\[\\[([^\\]|#]+)(?:#[^\\]|]+)?(?:\\|[^\\]]+)?\\]\\]/g;\n\n/**\n * Validate the brief body for D-11 compliance; if any source is\n * missing a wikilink, append a `## Sources` footer naming the missing\n * entries.\n *\n * `resolveTitle(id)` returns the canonical title for a DocId — used\n * both for matching and for the patched footer. The controller threads\n * this through `(id) => vault.db.notes.getByPath(resource)?.title ?? id`.\n */\nexport function validateAndPatchBody(\n body: string,\n sourceDocIds: readonly DocId[],\n resolveTitle: (id: DocId) => string,\n): string {\n // Collect every wikilink target from the body (titles only — alias\n // and heading suffix are stripped by the regex group). Track BOTH\n // the raw match AND the trimmed match so callers can include\n // titles with trailing whitespace without surprise.\n const cited = new Set();\n for (const m of body.matchAll(WIKILINK_RE)) {\n const target = m[1]?.trim();\n if (target !== undefined && target.length > 0) cited.add(target);\n }\n\n // For each source, accept any one of:\n // - the resolved title (e.g. \"Atlas-1\")\n // - the bare DocId (escape hatch — the LLM may emit\n // `[[obsidian-fs://vault/notes/atlas-1.md]]` when it doesn't\n // know the human title).\n const missing: DocId[] = [];\n for (const id of sourceDocIds) {\n const title = resolveTitle(id);\n if (cited.has(title) || cited.has(id)) continue;\n missing.push(id);\n }\n\n if (missing.length === 0) return body;\n\n const footerLines = missing.map((id) => `- [[${resolveTitle(id)}]]`);\n const footer = `\\n\\n## Sources\\n${footerLines.join(\"\\n\")}\\n`;\n return body + footer;\n}\n","/**\n * `handleCompileBrief` — the BRF-03 controller.\n *\n * Compiles a brief from caller-supplied source DocIds and writes it\n * through `DeliveryAdapter.write` into `_memory/_briefs/`. The full\n * pipeline (per ADR-005 §\"compile_brief\"):\n *\n * 1. Resolve target vault + brief sink (defaults to `_memory/_briefs`).\n * 2. Validate input: dedupe `source_doc_ids`, enforce ≤50 cap (D-03),\n * gate cross-vault sources (Open Q3 RESOLVED — every source\n * DocId's `authority` MUST equal the target vault).\n * 3. Resolve sources to chunks via the notes+chunks DB join and build\n * `source_hashes` via `buildSourceHashes` (slice 1).\n * 4. Resolve the LLM strategy (D-10 ladder): MCP Sampling → Ollama →\n * `prepared_text` → structured error.\n * 5. Build prompt; dispatch to the resolved tier; capture\n * `BriefLlmSamplingRefusedError` → `{ok:false, reason:\n * \"sampling_refused\"}`.\n * 6. Validate body wikilinks (D-11): append `## Sources` footer for\n * any cited DocId missing a `[[Title]]` reference. Phase 4 D-02\n * indexer materializes back-edges on the next pass.\n * 7. Mint timestamped slug `{target}--YYYYMMDDTHHmm.md`; check for\n * existing brief with the same `target` (status !== \"superseded\")\n * → capture `oldDocId` for D-12 supersede chain.\n * 8. Build the brief Document with the `default-brief-v1` property\n * bag (slice 1's contract).\n * 9. `delivery.write(newDocId, briefDoc, {sink: briefSink.handle})`\n * — the validator at the chokepoint runs schema + sentinel checks.\n * 10. Populate `brief_sources` reverse-index (one row per chunk in\n * every source doc).\n * 11. If `oldDocId` was captured, call `handleSupersede` to mark the\n * prior brief superseded (forward-only D-03 invariant).\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. All file access goes through the\n * `SourceConnector` + `DeliveryAdapter` seams.\n */\n\nimport type { DeliveryAdapter } from \"../adapters/delivery/types.js\";\nimport { decomposeDocId, formatDocId, parseDocId } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport type { OllamaClient } from \"../ollama/client.js\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { BriefConfig, DocId, Document } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\nimport type { MemorySinkRegistry } from \"../memory/registry.js\";\nimport { handleSupersede } from \"../memory/tools/supersede.js\";\nimport { buildSourceHashes, type ChunkSource } from \"./source-hashes.js\";\nimport {\n BriefLlmSamplingRefusedError,\n BriefLlmUnavailableError,\n compileWithLlm,\n resolveLlmStrategy,\n} from \"./llm-ladder.js\";\nimport { validateAndPatchBody } from \"./body-validator.js\";\n\n/** ADR-005 D-03 hard cap; lifted only at planner discretion. */\nconst MAX_SOURCES = 50;\n\n/** Default sink name for briefs; the user may override via `args.sink`. */\nconst DEFAULT_BRIEF_SINK_NAME = \"_memory/_briefs\";\n\n/**\n * Dependencies — supplied by the server bootstrap. Pure interface so\n * tests can wire fakes without touching the file system seam.\n */\nexport interface CompileBriefDeps {\n memorySinkRegistry: MemorySinkRegistry;\n manager: VaultManager;\n deliveryAdapterFor: (vaultName: string) => DeliveryAdapter;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n /**\n * The high-level `McpServer` — the ladder reads\n * `.server.getClientCapabilities()` + `.server.createMessage()`\n * through this handle (Tier 1).\n */\n server: McpServer;\n /** OllamaClient instance (Tier 2 of the D-10 ladder). */\n ollama: OllamaClient;\n /**\n * Server-level `[brief]` block from `AppConfig`. The ladder reads\n * `briefConfig.ollama.model` to decide if Tier 2 is reachable;\n * undefined means tier 2 skips.\n */\n briefConfig: BriefConfig | undefined;\n}\n\nexport interface CompileBriefArgs {\n vault: string;\n /** Stable, vault-relative target slug (e.g. `\"atlas-q3\"`). */\n target: string;\n /** Source DocIds the brief is compiled from; deduped, capped at 50. */\n source_doc_ids: string[];\n /** Free-form purpose; 1..500 chars (validated at Zod gate). */\n purpose: string;\n /** Hint for the LLM ladder; default 2000 tokens. */\n max_tokens?: number;\n /** D-10 tier 3 fallback: verbatim body when no LLM is reachable. */\n prepared_text?: string;\n /** Override the default `_memory/_briefs` sink. */\n sink?: string;\n /** Optional override for the slug timestamp (test-only determinism). */\n _now?: Date;\n}\n\nexport type CompileBriefResult =\n | { ok: true; doc_id: string; supersededPrior?: string; model?: string }\n | {\n ok: false;\n reason: \"no_llm_strategy_available\";\n attempted: string[];\n hint: string;\n }\n | { ok: false; reason: \"too_many_sources\"; limit: number; hint: string }\n | { ok: false; reason: \"cross_vault_sources\"; offending: string[] }\n | { ok: false; reason: \"sampling_refused\"; message?: string }\n | { ok: false; reason: \"write_failed\"; message?: string };\n\n/**\n * Compact ISO slug `YYYYMMDDTHHmm` for the `{target}--.md` mint\n * (D-12). Stable, sortable, file-system-safe.\n */\nfunction compactIso(date: Date): string {\n // YYYY-MM-DDTHH:mm:ss.sssZ → YYYYMMDDTHHmm\n return date.toISOString().replace(/[-:.]/g, \"\").slice(0, 13);\n}\n\n/**\n * Resolve the brief sink: caller-supplied `args.sink` wins, otherwise\n * default sink-name `_memory/_briefs`. Throws (via registry) if the\n * sink is unknown — surfaced to the MCP caller as an error response.\n */\nfunction resolveBriefSink(deps: CompileBriefDeps, sinkArg: string | undefined) {\n const name = sinkArg ?? DEFAULT_BRIEF_SINK_NAME;\n return deps.memorySinkRegistry.resolveMemorySink(name);\n}\n\n/**\n * Build the `ChunkSource[]` array for `buildSourceHashes` by joining\n * the notes + chunks tables. Source DocIds that resolve to no row are\n * silently dropped — the LLM still cites them by DocId/title; the\n * `brief_sources` reverse-index just has nothing to track. Production\n * call sites compile against indexed docs so this branch only matters\n * for unit-test fakes.\n */\nfunction resolveSourcesToChunks(vault: Vault, docIds: readonly DocId[]): ChunkSource[] {\n const out: ChunkSource[] = [];\n for (const docId of docIds) {\n const { resource } = decomposeDocId(docId);\n const note = vault.db.notes.getByPath(resource);\n if (!note) continue;\n const chunks = vault.db.chunks.getByNote(note.id);\n for (const chunk of chunks) {\n out.push({\n docId,\n fragment: chunk.chunk_id_fragment,\n text: chunk.text,\n });\n }\n }\n return out;\n}\n\n/**\n * Lookup an existing brief for `target` via SourceConnector enumeration.\n * Returns the FIRST non-superseded match by listing order; if multiple\n * non-superseded briefs share the same `target`, the forward-only\n * supersede invariant has been violated upstream and we log a structured\n * warning (via audit). For Slice 2 we proceed with the newest by\n * `compiled_at` and document the WARN in the SUMMARY.\n */\nasync function findBriefByTarget(\n source: SourceConnector,\n briefSinkPrefix: string,\n vaultName: string,\n target: string,\n): Promise {\n // listDocuments yields refs; we readDocument each one and inspect\n // properties. Limit is broad because brief sinks are small; the\n // listing is filtered by path prefix to skip unrelated _memory/ docs.\n const candidates: Document[] = [];\n for await (const ref of source.listDocuments()) {\n const { resource } = decomposeDocId(ref.id);\n if (!resource.startsWith(briefSinkPrefix)) continue;\n let doc: Document;\n try {\n doc = await source.readDocument(ref.id);\n } catch {\n continue;\n }\n const props = doc.properties as Record;\n if (props.target !== target) continue;\n if (props.status === \"superseded\") continue;\n candidates.push(doc);\n }\n if (candidates.length === 0) return null;\n if (candidates.length === 1) return candidates[0]!;\n // Pick the newest by compiled_at. This branch should not happen\n // under the forward-only invariant.\n candidates.sort((a, b) => {\n const ai = a.properties.compiled_at as string | undefined;\n const bi = b.properties.compiled_at as string | undefined;\n return (bi ?? \"\").localeCompare(ai ?? \"\");\n });\n // Suppress unused-variable lint; vaultName parameter reserved for\n // future audit logging when the duplicate-active-briefs branch fires.\n void vaultName;\n return candidates[0]!;\n}\n\n/**\n * Resolve a DocId to a human title for the body validator + footer.\n * Falls back to the bare DocId when the notes table has no row.\n */\nfunction makeTitleResolver(vault: Vault): (id: DocId) => string {\n return (id: DocId): string => {\n try {\n const { resource } = decomposeDocId(id);\n const row = vault.db.notes.getByPath(resource);\n if (row?.title) return row.title;\n } catch {\n // fall through\n }\n return id;\n };\n}\n\n/**\n * Compile a brief. Returns the success / failure discriminated union;\n * `WriteConflict` from the delivery adapter surfaces as `write_failed`\n * (with the original message preserved) — the underlying conflict is\n * recoverable at the caller layer if needed.\n */\nexport async function handleCompileBrief(\n deps: CompileBriefDeps,\n args: CompileBriefArgs,\n): Promise {\n const vault = deps.manager.require(args.vault);\n const vaultName = vault.config.name;\n\n // ── 1. Resolve brief sink ─────────────────────────────────────────\n const briefSink = resolveBriefSink(deps, args.sink);\n if (briefSink.vault !== vaultName) {\n throw new Error(\n `Brief sink \"${briefSink.name}\" belongs to vault \"${briefSink.vault}\", not \"${vaultName}\"`,\n );\n }\n\n // ── 2. Validate args: dedupe + cap + cross-vault gate ─────────────\n const dedupedRaw = Array.from(new Set(args.source_doc_ids));\n if (dedupedRaw.length > MAX_SOURCES) {\n return {\n ok: false,\n reason: \"too_many_sources\",\n limit: MAX_SOURCES,\n hint: `Pass at most ${MAX_SOURCES} source_doc_ids. Use cluster() or expand() to narrow the corpus.`,\n };\n }\n\n const parsedSourceDocIds: DocId[] = [];\n const offending: string[] = [];\n for (const raw of dedupedRaw) {\n let parsed: DocId;\n try {\n parsed = parseDocId(raw);\n } catch {\n offending.push(raw);\n continue;\n }\n const { authority } = decomposeDocId(parsed);\n if (authority !== vaultName) {\n offending.push(raw);\n continue;\n }\n parsedSourceDocIds.push(parsed);\n }\n if (offending.length > 0) {\n return { ok: false, reason: \"cross_vault_sources\", offending };\n }\n\n // ── 3. Build source_hashes via slice-1 helper ─────────────────────\n const chunkSources = resolveSourcesToChunks(vault, parsedSourceDocIds);\n const sourceHashes = buildSourceHashes(chunkSources);\n\n // ── 4. Resolve LLM strategy ───────────────────────────────────────\n const strategy = resolveLlmStrategy(deps.server, deps.briefConfig, args.prepared_text);\n if (strategy.kind === \"unavailable\") {\n return {\n ok: false,\n reason: \"no_llm_strategy_available\",\n attempted: strategy.attempted,\n hint: \"Configure [brief.ollama] in config.toml, use a sampling-capable MCP client, or pass prepared_text.\",\n };\n }\n\n // ── 5. Build prompt + dispatch ────────────────────────────────────\n const titleOf = makeTitleResolver(vault);\n const citations = parsedSourceDocIds.map((id) => `- [[${titleOf(id)}]] (${id})`).join(\"\\n\");\n const systemText =\n \"You are compiling a concise, evidence-grounded brief from the source documents below. \" +\n \"Emit `[[Title]]` wikilinks for each cited source so the knowledge graph indexes the brief. \" +\n \"Do not invent attendees, dates, decisions, or numbers — ground every claim in the sources.\";\n const userText =\n `Purpose: ${args.purpose}\\n\\n` +\n `Sources:\\n${citations}\\n\\n` +\n `Compile the brief now. Cite every source as a [[wikilink]] at least once.`;\n\n let rawBody: string;\n let model: string;\n try {\n const compiled = await compileWithLlm(\n strategy,\n deps.server,\n deps.ollama,\n { systemText, userText },\n args.max_tokens ?? 2000,\n args.prepared_text,\n );\n rawBody = compiled.body;\n model = compiled.model;\n } catch (err) {\n if (err instanceof BriefLlmSamplingRefusedError) {\n return {\n ok: false,\n reason: \"sampling_refused\",\n message: err.message,\n };\n }\n if (err instanceof BriefLlmUnavailableError) {\n // resolveLlmStrategy already short-circuited the unavailable\n // case; this branch fires only on programmer error (e.g. a stub\n // strategy threading through). Surface it as the same structured\n // error so the caller has one branch to handle.\n return {\n ok: false,\n reason: \"no_llm_strategy_available\",\n attempted: err.attempted,\n hint: \"Configure [brief.ollama] in config.toml, use a sampling-capable MCP client, or pass prepared_text.\",\n };\n }\n throw err;\n }\n\n // ── 6. Validate body wikilinks (D-11) ─────────────────────────────\n const body = validateAndPatchBody(rawBody, parsedSourceDocIds, titleOf);\n\n // ── 7. Mint new DocId + check for existing brief on target ────────\n const now = args._now ?? new Date();\n const slug = compactIso(now);\n const briefRelative = `${briefSink.resolveToRelativePath}${args.target}--${slug}.md`;\n const newDocId = formatDocId(\"obsidian-fs\", vaultName, briefRelative);\n\n const source = deps.sourceConnectorFor(vaultName);\n const existing = await findBriefByTarget(\n source,\n briefSink.resolveToRelativePath,\n vaultName,\n args.target,\n );\n const oldDocId = existing?.id ?? null;\n\n // ── 8. Build the brief Document ───────────────────────────────────\n const nowIso = now.toISOString();\n const properties: Record = {\n source: \"agent\",\n confidence: \"inferred\",\n evidence: parsedSourceDocIds.slice(),\n status: \"active\",\n observed_at: nowIso,\n superseded_by: null,\n type: \"brief\",\n target: args.target,\n purpose: args.purpose,\n compiled_from: parsedSourceDocIds.slice(),\n compiled_at: nowIso,\n source_hashes: sourceHashes,\n // Audit-trail attribution — which LLM tier produced the body.\n // The value is whatever the LLM tier returned verbatim (the host\n // MCP client's model identifier, the Ollama model name, or the\n // sentinel string \"prepared_text\"). Per ADR-005 §\"Provenance\" the\n // audit log carries the model name.\n model,\n };\n const title = `${args.target} brief`;\n const briefDoc: Partial = {\n id: newDocId,\n title,\n properties,\n blocks: [{ kind: \"paragraph\", text: body }],\n };\n\n // ── 9. Write through DeliveryAdapter ──────────────────────────────\n const delivery = deps.deliveryAdapterFor(vaultName);\n const writeRes = await delivery.write(newDocId, briefDoc, {\n sink: briefSink.handle,\n });\n if (!writeRes.ok) {\n return {\n ok: false,\n reason: \"write_failed\",\n message: writeRes.message ?? `Delivery refused brief write: reason=${writeRes.reason}`,\n };\n }\n\n // ── 10. Populate brief_sources reverse-index ──────────────────────\n const sourceRows = chunkSources.map((cs) => ({\n chunkIdFragment: cs.fragment,\n chunkDocId: cs.docId,\n recordedHash: sourceHashes[\n `${cs.docId}#chunk-${cs.fragment}` as keyof typeof sourceHashes\n ] as string,\n }));\n if (sourceRows.length > 0) {\n vault.db.briefSources.insertBatch(newDocId, sourceRows);\n }\n\n // ── 11. D-12 supersede chain on target collision ──────────────────\n if (oldDocId !== null) {\n await handleSupersede(\n {\n memorySinkRegistry: deps.memorySinkRegistry,\n manager: deps.manager,\n deliveryAdapterFor: deps.deliveryAdapterFor,\n sourceConnectorFor: deps.sourceConnectorFor,\n },\n {\n doc_id: oldDocId,\n replacement_doc_id: newDocId,\n reason: \"recompiled\",\n },\n );\n return {\n ok: true,\n doc_id: newDocId,\n supersededPrior: oldDocId,\n model,\n };\n }\n\n return { ok: true, doc_id: newDocId, model };\n}\n","/**\n * `handleGetBrief` — the BRF-04 controller.\n *\n * Looks up a brief by target slug and applies the D-13 decision tree:\n *\n * - **Staleness dominates.** If the brief's `status === \"stale\"` and\n * the caller did not opt in via `allow_stale: true`, return\n * `{brief: null, stale: true, ...}` so the caller knows to\n * recompile.\n * - **Age is independent.** Even on a non-stale brief, if\n * `max_age_days` is set and the brief's `compiled_at` is older\n * than that window AND `allow_stale: false`, return\n * `{brief: null, too_old: true, ...}`.\n * - **Follow the supersede chain.** If the looked-up brief carries\n * `status: \"superseded\"` with a non-null `superseded_by`, follow\n * the chain via `SourceConnector.readDocument` until a terminal\n * brief is reached (or a cycle is detected — defensive 100-hop\n * cap, see Phase 2 D-03 forward-only supersede invariant).\n *\n * The \"not_found\" case is its own branch so callers can differentiate\n * \"no brief exists for this target\" from \"exists but stale/too_old\".\n *\n * Pure controller — no `node:fs`, no `node:path`, no `gray-matter`,\n * no `chokidar`. Everything goes through `SourceConnector.listDocuments`\n * + `readDocument`.\n */\n\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport { decomposeDocId, parseDocId } from \"../adapters/registry.js\";\nimport type { Document } from \"../types.js\";\nimport type { VaultManager } from \"../vault/index.js\";\nimport type { MemorySinkRegistry } from \"../memory/registry.js\";\n\n/** Defensive cycle guard for the supersede chain (forward-only invariant). */\nconst MAX_SUPERSEDE_HOPS = 100;\n\n/** Default sink name for briefs; the user may override via `args.sink`. */\nconst DEFAULT_BRIEF_SINK_NAME = \"_memory/_briefs\";\n\nexport interface GetBriefDeps {\n memorySinkRegistry: MemorySinkRegistry;\n manager: VaultManager;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\nexport interface GetBriefArgs {\n vault: string;\n target: string;\n max_age_days?: number;\n allow_stale?: boolean;\n sink?: string;\n}\n\nexport type GetBriefResult =\n | { brief: Document; stale: false; too_old: false; age_days: number }\n | {\n brief: Document;\n stale: true;\n too_old: boolean;\n age_days: number;\n changed_sources: string[];\n }\n | {\n brief: Document;\n stale: false;\n too_old: true;\n age_days: number;\n }\n | {\n brief: null;\n stale: true;\n too_old?: boolean;\n changed_sources: string[];\n reason: \"stale_blocked\";\n }\n | {\n brief: null;\n stale: false;\n too_old: true;\n age_days: number;\n reason: \"too_old_blocked\";\n }\n | { brief: null; reason: \"not_found\" };\n\n/**\n * Enumerate `_memory/_briefs/` and find the FIRST brief whose\n * `properties.target` matches. Skips superseded briefs at the listing\n * pass — the supersede chain is followed below by the controller.\n */\nasync function findBriefByTarget(\n source: SourceConnector,\n briefSinkPrefix: string,\n target: string,\n): Promise {\n const candidates: Document[] = [];\n for await (const ref of source.listDocuments()) {\n const { resource } = decomposeDocId(ref.id);\n if (!resource.startsWith(briefSinkPrefix)) continue;\n let doc: Document;\n try {\n doc = await source.readDocument(ref.id);\n } catch {\n continue;\n }\n const props = doc.properties as Record;\n if (props.target !== target) continue;\n if (props.status === \"superseded\") continue;\n candidates.push(doc);\n }\n if (candidates.length === 0) return null;\n if (candidates.length === 1) return candidates[0]!;\n // Forward-only invariant violation — pick newest by compiled_at and\n // proceed; observability lands when audit-log integration follows.\n candidates.sort((a, b) => {\n const ai = a.properties.compiled_at as string | undefined;\n const bi = b.properties.compiled_at as string | undefined;\n return (bi ?? \"\").localeCompare(ai ?? \"\");\n });\n return candidates[0]!;\n}\n\n/**\n * Walk the `superseded_by` chain forward until we hit a terminal brief\n * (status !== \"superseded\" or superseded_by is null) or the cycle\n * guard trips. Returns the terminal Document.\n */\nasync function followSupersedeChain(source: SourceConnector, start: Document): Promise {\n let current = start;\n let hops = 0;\n while (current.properties.status === \"superseded\") {\n const nextRaw = current.properties.superseded_by;\n if (nextRaw === null || nextRaw === undefined) break;\n if (typeof nextRaw !== \"string\") break;\n if (++hops > MAX_SUPERSEDE_HOPS) {\n throw new Error(\n `get_brief supersede chain exceeded ${MAX_SUPERSEDE_HOPS} hops; ` +\n `target chain rooted at ${start.id}. Indicates a forward-only ` +\n `invariant violation upstream (Phase 2 D-03).`,\n );\n }\n const nextId = parseDocId(nextRaw);\n let next: Document;\n try {\n next = await source.readDocument(nextId);\n } catch {\n // Broken chain — return what we have.\n break;\n }\n current = next;\n }\n return current;\n}\n\nfunction ageDaysFor(brief: Document): number {\n const compiledAt = brief.properties.compiled_at;\n if (typeof compiledAt !== \"string\") return Number.POSITIVE_INFINITY;\n const parsed = Date.parse(compiledAt);\n if (Number.isNaN(parsed)) return Number.POSITIVE_INFINITY;\n return Math.floor((Date.now() - parsed) / 86_400_000);\n}\n\nfunction changedSourcesFor(brief: Document): string[] {\n const raw = brief.properties.changed_sources;\n if (!Array.isArray(raw)) return [];\n return raw.filter((x): x is string => typeof x === \"string\");\n}\n\n/**\n * Look up a brief by target and apply D-13. See file header.\n */\nexport async function handleGetBrief(\n deps: GetBriefDeps,\n args: GetBriefArgs,\n): Promise {\n const vault = deps.manager.require(args.vault);\n const vaultName = vault.config.name;\n\n // Resolve the brief sink so we know which path prefix to enumerate.\n const briefSink = deps.memorySinkRegistry.resolveMemorySink(args.sink ?? DEFAULT_BRIEF_SINK_NAME);\n if (briefSink.vault !== vaultName) {\n throw new Error(\n `Brief sink \"${briefSink.name}\" belongs to vault \"${briefSink.vault}\", not \"${vaultName}\"`,\n );\n }\n\n const source = deps.sourceConnectorFor(vaultName);\n const found = await findBriefByTarget(source, briefSink.resolveToRelativePath, args.target);\n if (found === null) {\n return { brief: null, reason: \"not_found\" };\n }\n\n // Follow the supersede chain to the terminal (defensive — the\n // `findBriefByTarget` pass already filters out superseded briefs,\n // but a brief returned here that carries `superseded_by` non-null\n // means a non-superseded-status row exists with a redirect, which\n // is unusual but possible in mid-flight states).\n const terminal = await followSupersedeChain(source, found);\n\n const ageDays = ageDaysFor(terminal);\n const status = terminal.properties.status;\n const stale = status === \"stale\";\n const tooOld =\n args.max_age_days !== undefined && Number.isFinite(ageDays) && ageDays > args.max_age_days;\n const allowStale = args.allow_stale === true;\n\n if (stale && !allowStale) {\n return {\n brief: null,\n stale: true,\n ...(tooOld ? { too_old: true as const } : {}),\n changed_sources: changedSourcesFor(terminal),\n reason: \"stale_blocked\",\n };\n }\n\n if (tooOld && !allowStale) {\n return {\n brief: null,\n stale: false,\n too_old: true,\n age_days: ageDays,\n reason: \"too_old_blocked\",\n };\n }\n\n if (stale) {\n return {\n brief: terminal,\n stale: true,\n too_old: tooOld,\n age_days: ageDays,\n changed_sources: changedSourcesFor(terminal),\n };\n }\n\n if (tooOld) {\n return {\n brief: terminal,\n stale: false,\n too_old: true,\n age_days: ageDays,\n };\n }\n\n return {\n brief: terminal,\n stale: false,\n too_old: false,\n age_days: ageDays,\n };\n}\n","// vault-memory:claude-ok — process state (~/.vault-memory/locks/) not vault content.\n// See ADR-005 §\"Lockfile carve-out\" for the rationale.\n//\n// This file is the ONLY exemption to scripts/lint-adapters.sh in Phase 5.\n// Adapter-seam discipline (no fs/path.join outside src/adapters/*/) does NOT\n// apply: lockfiles are process state managed by ~/.vault-memory/, not user\n// vault content. Per D-08 + ADR-005.\n\n/**\n * Phase 5 / D-08 — `~/.vault-memory/locks/.lock` single-owner\n * primitive for the staleness daemon.\n *\n * Atomic exclusive create via `fs.open(path, 'wx')` (POSIX `O_WRONLY |\n * O_CREAT | O_EXCL`). On EEXIST, read the recorded PID; if dead\n * (POSIX `kill(pid, 0)` throws ESRCH), steal the lock — otherwise\n * return contended.\n *\n * Multi-MCP-client friendly per CONTEXT D-08: second `vault-memory\n * serve` against the same vault boots normally; only the daemon\n * subscription is gated.\n *\n * No `node:fs` imports outside this file in `src/brief/`.\n */\n\nimport { open, readFile, unlink, mkdir } from \"node:fs/promises\"; // vault-memory:claude-ok\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\"; // vault-memory:claude-ok\n\nexport interface LockAcquired {\n acquired: true;\n pid: number;\n path: string;\n stolenFromPid?: number;\n}\n\nexport interface LockContended {\n acquired: false;\n ownerPid: number;\n path: string;\n}\n\nexport type LockResult = LockAcquired | LockContended;\n\n/**\n * Override the lock directory for tests so the real\n * `~/.vault-memory/locks/` is never touched during the test suite.\n * Test-only — production call sites omit the argument.\n */\nfunction lockDir(rootOverride?: string): string {\n if (rootOverride !== undefined) return join(rootOverride, \"locks\");\n return join(homedir(), \".vault-memory\", \"locks\");\n}\n\nfunction lockPath(vaultName: string, rootOverride?: string): string {\n return join(lockDir(rootOverride), `${vaultName}.lock`);\n}\n\n/** POSIX `kill(pid, 0)`: returns true if pid is alive, false on ESRCH. */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // ESRCH means no such process. EPERM means alive but inaccessible.\n if ((err as NodeJS.ErrnoException).code === \"ESRCH\") return false;\n // Defensive: any other error treated as alive (we won't steal).\n return true;\n }\n}\n\nasync function readOwnerPid(path: string): Promise {\n try {\n const buf = await readFile(path, \"utf8\");\n const pid = parseInt(buf.trim(), 10);\n return Number.isFinite(pid) && pid > 0 ? pid : null;\n } catch {\n return null;\n }\n}\n\nexport interface AcquireLockOptions {\n /**\n * Test-only override for the `~/.vault-memory/` root. Production\n * call sites omit. When set, the lock lives at\n * `/locks/.lock`.\n */\n rootOverride?: string;\n}\n\n/**\n * Try to acquire the lock for a vault.\n * Atomic create via `fs.open(path, 'wx')` (`O_WRONLY | O_CREAT | O_EXCL`).\n * On EEXIST: read current owner PID; if dead (ESRCH) or malformed,\n * steal the lock; else return contended.\n */\nexport async function tryAcquireLock(\n vaultName: string,\n options: AcquireLockOptions = {},\n): Promise {\n const dir = lockDir(options.rootOverride);\n await mkdir(dir, { recursive: true });\n const path = lockPath(vaultName, options.rootOverride);\n\n // Defensive: bound recursion so a hostile / racing peer can't loop\n // us. Two retries is plenty (steal once, then acquire on the next).\n const MAX_ATTEMPTS = 3;\n\n const attempt = async (n: number, stolenFromPid?: number): Promise => {\n if (n > MAX_ATTEMPTS) {\n // Treat as contended with an unknown owner; caller logs WARN.\n return { acquired: false, ownerPid: stolenFromPid ?? -1, path };\n }\n try {\n const handle = await open(path, \"wx\");\n try {\n await handle.writeFile(`${process.pid}\\n`);\n } finally {\n await handle.close();\n }\n const result: LockAcquired = { acquired: true, pid: process.pid, path };\n if (stolenFromPid !== undefined) result.stolenFromPid = stolenFromPid;\n return result;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n const ownerPid = await readOwnerPid(path);\n if (ownerPid === null || !isProcessAlive(ownerPid)) {\n // Stale lock (or malformed contents): unlink and retry.\n await unlink(path).catch(() => undefined);\n return attempt(n + 1, ownerPid ?? -1);\n }\n return { acquired: false, ownerPid, path };\n }\n };\n\n return attempt(1);\n}\n\n/** Release the lock. Safe to call even if we don't hold it. */\nexport async function releaseLock(\n vaultName: string,\n options: AcquireLockOptions = {},\n): Promise {\n await unlink(lockPath(vaultName, options.rootOverride)).catch(() => undefined);\n}\n","/**\n * Phase 5 / BRF-05/06/07/08 — `BriefStalenessDaemon`.\n *\n * In-process daemon that subscribes to the same `ChangeFeed` as the\n * `VaultWatcher` and flips affected briefs to `status: \"stale\"` when\n * chunk-hash divergence is observed.\n *\n * # Lifecycle (mirrors `VaultWatcher.start/stop`)\n *\n * 1. `start(vault, feed, deps)`:\n * a. acquire `~/.vault-memory/locks/.lock`; on contention\n * log structured WARN to stderr + audit (`daemon_already_owned`)\n * and return early — second-server boots fine without a daemon.\n * b. read `daemon_state.last_seen_doc_mtime` cursor (diagnostic).\n * c. run a startup full scan over `brief_sources.listBriefDocIds()`\n * and mark divergent briefs stale (D-09 correctness floor).\n * d. subscribe to the feed for create/update/delete/rename events.\n *\n * 2. handler — on each ChangeEvent:\n * - create/update → `evaluateChangedDocId(id)` (recompute hashes,\n * flip divergent briefs stale via `delivery.update`).\n * - delete → record in pendingDeletes (5s grace-window); when\n * the grace-window expires without a matching create, mark\n * briefs stale with reason `\"source_deleted\"`.\n * - rename — adapter-native rename → update\n * `brief_sources.chunk_doc_id` in place (BRF-08 preserve\n * brief→source links).\n *\n * 3. `shutdown()` — dispose subscription FIRST, then releaseLock LAST.\n * A crashed shutdown that fails to release the lock leaves it for\n * `kill(pid, 0)` stale-detection to recover.\n *\n * # Anti-Pattern 2 — never direct DB writes\n *\n * Brief staleness writes route through `delivery.update(briefId, patch,\n * {expectedHash, sink})` so the MEM-05 validator runs at the\n * `DeliveryAdapter` chokepoint (`default-brief-v1` permits\n * `status: \"stale\"` per slice 1 contract). Direct\n * `vault.db.notes.upsert(...)` would bypass the validator AND the\n * existing watcher suppression-set hook → Pitfall 3.\n *\n * # Adapter-seam discipline\n *\n * Zero `fs` / `path.join` / `gray-matter` / `chokidar` imports here.\n * The daemon delegates to `lock.ts` (the only lockfile carve-out) for\n * `~/.vault-memory/locks/` access; everything else routes through the\n * `DeliveryAdapter` / `SourceConnector` / `ChangeFeed` seams.\n */\n\nimport type { ChangeEvent, ChangeFeed, Disposable } from \"../adapters/change-feed/types.js\";\nimport type { DeliveryAdapter } from \"../adapters/delivery/types.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport { decomposeDocId } from \"../adapters/registry.js\";\nimport type { MemorySinkRegistry } from \"../memory/index.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { DocId, Document } from \"../types.js\";\nimport { recomputeCurrentHash } from \"./source-hashes.js\";\nimport { releaseLock, tryAcquireLock } from \"./lock.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\n/** Default sink name for briefs. Mirrors compile.ts / get.ts. */\nconst DEFAULT_BRIEF_SINK_NAME = \"_memory/_briefs\";\n\n/**\n * 5-second grace-window for rename survival (BRF-08). chokidar surfaces\n * a true OS-level rename as `unlink + add`; this window correlates the\n * pair by matching chunk hash sets.\n */\nconst RENAME_GRACE_MS = 5_000;\n\n/**\n * Defensive hop cap for shutdown-period grace-window expiry — should\n * never fire in normal operation.\n */\nconst MAX_EXPIRE_PER_TICK = 1024;\n\nexport interface DaemonDeps {\n memorySinkRegistry: MemorySinkRegistry;\n deliveryAdapterFor: (vaultName: string) => DeliveryAdapter;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n /** Optional override for the lock root (tests inject mkdtemp dir). */\n lockRootOverride?: string;\n /** Optional override for the brief sink name (defaults to _memory/_briefs). */\n briefSinkName?: string;\n /** Optional logger; defaults to stderr writer. */\n log?: (msg: string) => void;\n /** Optional clock override for tests (default `Date.now`). */\n now?: () => number;\n}\n\ninterface PendingDelete {\n id: DocId;\n /** Set of full chunk hashes (`\"sha256:...\"`) captured at delete time. */\n chunkHashes: Set;\n timestamp: number;\n}\n\nexport interface DaemonStartResult {\n acquired: boolean;\n ownerPid?: number;\n}\n\nexport class BriefStalenessDaemon {\n private disposable: Disposable | null = null;\n private vault: Vault | null = null;\n private deps: DaemonDeps | null = null;\n private acquired = false;\n private readonly pendingDeletes = new Map();\n private now: () => number = Date.now;\n private log: (msg: string) => void = (m) => process.stderr.write(`[brief-daemon] ${m}\\n`);\n\n /**\n * Acquire the per-vault lock, run the startup scan, subscribe to\n * the feed. Multi-MCP-client friendly: returns\n * `{acquired: false, ownerPid}` on lock contention WITHOUT\n * subscribing or throwing — the second server boots normally.\n */\n async start(vault: Vault, feed: ChangeFeed, deps: DaemonDeps): Promise {\n this.vault = vault;\n this.deps = deps;\n if (deps.now) this.now = deps.now;\n if (deps.log) this.log = deps.log;\n\n const lockOpts =\n deps.lockRootOverride !== undefined ? { rootOverride: deps.lockRootOverride } : {};\n const lock = await tryAcquireLock(vault.config.name, lockOpts);\n if (!lock.acquired) {\n // D-08: structured WARN + return early. The lock-contention path\n // is a NORMAL multi-MCP-client outcome, not an error. We log to\n // stderr in a structured (single-line JSON) shape so external\n // collectors can parse it. Audit-log integration uses the\n // `audit.recordWrite` shape — but that table is `write_audit`\n // (per-note write history); a daemon-ownership event does not\n // bind to a note row, so we emit stderr only.\n const payload = JSON.stringify({\n kind: \"daemon_already_owned\",\n vault: vault.config.name,\n ownerPid: lock.ownerPid,\n path: lock.path,\n });\n this.log(`WARN ${payload}`);\n return { acquired: false, ownerPid: lock.ownerPid };\n }\n this.acquired = true;\n // Diagnostic: capture starting cursor (slice 1 D-09 cursor table).\n // The startup scan is the correctness floor regardless of cursor\n // value, but we log the value so operators can compare against\n // the post-scan cursor to verify the daemon is current.\n const startCursor = vault.db.daemonState.getCursor(vault.config.name);\n this.log(`start vault=${vault.config.name} startCursor=${startCursor ?? \"null\"}`);\n\n // ── Startup full scan (D-09 correctness floor) ─────────────────\n await this.runStartupScan();\n\n // ── Subscribe to ChangeFeed (D-07) ─────────────────────────────\n this.disposable = feed.subscribe(async (event: ChangeEvent) => {\n try {\n await this.handleEvent(event);\n vault.db.daemonState.setCursor(vault.config.name, this.now());\n } catch (err) {\n const message = errorMessage(err);\n const payload = JSON.stringify({\n kind: \"brief_staleness_error\",\n vault: vault.config.name,\n event_kind: event.kind,\n event_id: \"id\" in event ? event.id : null,\n message,\n });\n this.log(`ERROR ${payload}`);\n }\n });\n\n // Set initial cursor.\n vault.db.daemonState.setCursor(vault.config.name, this.now());\n return { acquired: true };\n }\n\n /**\n * Force any pending grace-window deletes to expire and propagate.\n * Test hook + shutdown-flush helper.\n */\n async drainPending(): Promise {\n await this.expireGraceWindow(true);\n }\n\n async shutdown(): Promise {\n // Dispose subscription FIRST so no more events arrive mid-shutdown.\n if (this.disposable) {\n this.disposable[Symbol.dispose]();\n this.disposable = null;\n }\n // Release lock LAST. A crashed shutdown that fails here leaves the\n // lock for `kill(pid, 0)` stale-detection (lock.ts) to recover.\n if (this.acquired && this.vault && this.deps) {\n const lockOpts =\n this.deps.lockRootOverride !== undefined\n ? { rootOverride: this.deps.lockRootOverride }\n : {};\n await releaseLock(this.vault.config.name, lockOpts);\n this.acquired = false;\n }\n }\n\n /** True iff the daemon currently owns the lock (test hook). */\n get isOwner(): boolean {\n return this.acquired;\n }\n\n // ────────────────────────────────────────────────────────────────────\n // Internal — handlers\n // ────────────────────────────────────────────────────────────────────\n\n private async runStartupScan(): Promise {\n const vault = this.requireVault();\n const briefIds = vault.db.briefSources.listBriefDocIds();\n for (const briefId of briefIds) {\n try {\n await this.evaluateBrief(briefId as DocId);\n } catch (err) {\n const message = errorMessage(err);\n const payload = JSON.stringify({\n kind: \"brief_staleness_error\",\n vault: vault.config.name,\n phase: \"startup_scan\",\n brief_id: briefId,\n message,\n });\n this.log(`ERROR ${payload}`);\n }\n }\n }\n\n private async handleEvent(event: ChangeEvent): Promise {\n // Always tick the grace-window expiries at the start of each event\n // so pending deletes propagate even when the next event is itself\n // a non-matching create on a different doc.\n await this.expireGraceWindow(false);\n\n switch (event.kind) {\n case \"create\":\n await this.handleCreate(event.id);\n break;\n case \"update\":\n await this.evaluateChangedDocId(event.id);\n break;\n case \"delete\":\n await this.handleDelete(event.id);\n break;\n case \"rename\":\n await this.handleRenameDirect(event.old_id, event.new_id);\n break;\n }\n }\n\n /**\n * For each brief that cites `docId`, re-evaluate its source_hashes\n * and flip the brief stale if any chunk diverges (or sources were\n * removed entirely).\n */\n private async evaluateChangedDocId(docId: DocId): Promise {\n const vault = this.requireVault();\n const affected = vault.db.briefSources.briefsForChunkDoc(docId);\n const briefIds = new Set(affected.map((a) => a.briefDocId));\n for (const briefId of briefIds) {\n await this.evaluateBrief(briefId as DocId);\n }\n }\n\n /**\n * Read the brief Document, walk its `brief_sources` rows, and\n * compare each `recorded_hash` to the current chunk hash. On\n * divergence, call `delivery.update` to flip status → \"stale\".\n *\n * Errors per-brief are caught + logged; the loop never crashes.\n */\n private async evaluateBrief(briefId: DocId): Promise {\n const vault = this.requireVault();\n const deps = this.requireDeps();\n\n const sources = vault.db.briefSources.sourcesForBrief(briefId);\n if (sources.length === 0) return; // Brief was never recorded.\n\n // Recompute the current hash for each cited chunk.\n const currentHashes = new Map();\n for (const row of sources) {\n const key = `${row.chunkDocId}#${row.chunkIdFragment}`;\n if (currentHashes.has(key)) continue;\n try {\n const { resource } = decomposeDocId(row.chunkDocId as DocId);\n const note = vault.db.notes.getByPath(resource);\n if (!note) {\n currentHashes.set(key, null); // Source doc disappeared.\n continue;\n }\n const chunks = vault.db.chunks.getByNote(note.id);\n const found = chunks.find((c) => c.chunk_id_fragment === row.chunkIdFragment);\n if (!found) {\n currentHashes.set(key, null); // Chunk was renamed / deleted.\n continue;\n }\n currentHashes.set(key, recomputeCurrentHash(found.text));\n } catch {\n currentHashes.set(key, null);\n }\n }\n\n // Build the changed_sources list — unique DocIds whose any chunk\n // diverged or disappeared.\n const changedSourceIds = new Set();\n for (const row of sources) {\n const key = `${row.chunkDocId}#${row.chunkIdFragment}`;\n const current = currentHashes.get(key);\n if (current === null || current !== row.recordedHash) {\n changedSourceIds.add(row.chunkDocId as DocId);\n }\n }\n\n if (changedSourceIds.size === 0) return;\n\n // Read the brief Document for its current hash + properties.\n const source = deps.sourceConnectorFor(vault.config.name);\n let briefDoc: Document;\n try {\n briefDoc = await source.readDocument(briefId);\n } catch (err) {\n const message = errorMessage(err);\n const payload = JSON.stringify({\n kind: \"brief_staleness_error\",\n vault: vault.config.name,\n brief_id: briefId,\n phase: \"read_brief\",\n message,\n });\n this.log(`ERROR ${payload}`);\n return;\n }\n\n // If the brief is already stale or superseded, skip the write —\n // re-flipping a stale brief would churn the suppression set.\n const currentStatus = briefDoc.properties.status;\n if (currentStatus === \"stale\" || currentStatus === \"superseded\") return;\n\n const briefSink = this.resolveBriefSink(vault.config.name);\n const delivery = deps.deliveryAdapterFor(vault.config.name);\n\n // Preserve existing properties; flip status + record changed_sources.\n const patchProperties: Record = {\n ...briefDoc.properties,\n status: \"stale\",\n changed_sources: Array.from(changedSourceIds),\n };\n const updateRes = await delivery.update(\n briefId,\n { properties: patchProperties },\n {\n expectedHash: briefDoc.hash,\n sink: briefSink.handle,\n },\n );\n if (!updateRes.ok) {\n const payload = JSON.stringify({\n kind: \"brief_staleness_error\",\n vault: vault.config.name,\n brief_id: briefId,\n phase: \"update\",\n reason: updateRes.reason,\n message: updateRes.message,\n });\n this.log(`ERROR ${payload}`);\n }\n }\n\n /**\n * Delete handler — capture the deleted doc's chunk hashes into the\n * grace-window so a matching `create` can survive the link via\n * rename heuristic (BRF-08).\n */\n private async handleDelete(docId: DocId): Promise {\n const vault = this.requireVault();\n // Capture the set of chunk hashes for this doc BEFORE the deletion\n // propagates through the indexer. We read from the chunks table\n // which is still populated at this point — the watcher's removeNote\n // happens on its own debounced flush, not synchronously with our\n // event handler.\n const chunkHashes = new Set();\n try {\n const { resource } = decomposeDocId(docId);\n const note = vault.db.notes.getByPath(resource);\n if (note) {\n for (const chunk of vault.db.chunks.getByNote(note.id)) {\n chunkHashes.add(recomputeCurrentHash(chunk.text));\n }\n }\n } catch {\n // Doc may already be gone; we keep an empty set so the\n // grace-window will eventually expire and propagate as a \"real\"\n // delete (mark briefs stale).\n }\n this.pendingDeletes.set(docId, {\n id: docId,\n chunkHashes,\n timestamp: this.now(),\n });\n }\n\n /**\n * Create handler — look for a matching pendingDelete by chunk-hash\n * set; if found, rewrite `brief_sources.chunk_doc_id` from old → new\n * in place (BRF-08).\n */\n private async handleCreate(docId: DocId): Promise {\n const vault = this.requireVault();\n // Compute the new doc's chunk hashes.\n const newHashes = new Set();\n try {\n const { resource } = decomposeDocId(docId);\n const note = vault.db.notes.getByPath(resource);\n if (note) {\n for (const chunk of vault.db.chunks.getByNote(note.id)) {\n newHashes.add(recomputeCurrentHash(chunk.text));\n }\n }\n } catch {\n // If we can't read the new doc, skip the rename heuristic —\n // it's purely an optimization on top of the staleness fallback.\n return;\n }\n if (newHashes.size === 0) return;\n\n // Find a pending delete with the same chunk-hash set.\n for (const [oldId, pending] of this.pendingDeletes) {\n if (chunkSetMatch(pending.chunkHashes, newHashes)) {\n this.pendingDeletes.delete(oldId);\n // UPDATE brief_sources.chunk_doc_id = newId WHERE chunk_doc_id = oldId.\n // We use a low-level prepared statement against the same DB\n // handle the BriefSourcesQueries class uses, surfaced as a\n // dedicated method below to keep the SQL string in one place.\n rewriteBriefSourceDocId(vault, oldId, docId);\n return;\n }\n }\n }\n\n /**\n * Native rename handler — for adapters that surface `rename` events\n * directly. Today's obsidian-fs ChangeFeed emits delete+create\n * (`emitsRename: false`); this branch fires only when a future\n * adapter (notion-api, github-api) emits a real rename.\n */\n private async handleRenameDirect(oldId: DocId, newId: DocId): Promise {\n const vault = this.requireVault();\n rewriteBriefSourceDocId(vault, oldId, newId);\n }\n\n /**\n * Walk the pendingDeletes map; for each entry older than the grace\n * window, treat as a real delete and mark its dependent briefs stale.\n */\n private async expireGraceWindow(force: boolean): Promise {\n const cutoff = force ? Number.POSITIVE_INFINITY : RENAME_GRACE_MS;\n const nowMs = this.now();\n let processed = 0;\n for (const [id, pending] of this.pendingDeletes) {\n if (processed++ > MAX_EXPIRE_PER_TICK) break;\n if (force || nowMs - pending.timestamp >= cutoff) {\n this.pendingDeletes.delete(id);\n // Mark briefs stale with reason: \"source_deleted\".\n try {\n await this.evaluateChangedDocId(id);\n } catch (err) {\n const message = errorMessage(err);\n const vault = this.vault;\n const payload = JSON.stringify({\n kind: \"brief_staleness_error\",\n vault: vault?.config.name ?? \"unknown\",\n brief_id: id,\n phase: \"grace_expire\",\n message,\n });\n this.log(`ERROR ${payload}`);\n }\n }\n }\n }\n\n // ────────────────────────────────────────────────────────────────────\n\n private resolveBriefSink(vaultName: string) {\n const deps = this.requireDeps();\n const name = deps.briefSinkName ?? DEFAULT_BRIEF_SINK_NAME;\n const sink = deps.memorySinkRegistry.resolveMemorySink(name);\n if (sink.vault !== vaultName) {\n throw new Error(`Brief sink \"${name}\" belongs to vault \"${sink.vault}\", not \"${vaultName}\"`);\n }\n return sink;\n }\n\n private requireVault(): Vault {\n if (!this.vault) throw new Error(\"daemon used before start()\");\n return this.vault;\n }\n\n private requireDeps(): DaemonDeps {\n if (!this.deps) throw new Error(\"daemon used before start()\");\n return this.deps;\n }\n}\n\n/** Set equality for chunk-hash multisets. Order-independent. */\nfunction chunkSetMatch(a: Set, b: Set): boolean {\n if (a.size !== b.size) return false;\n for (const x of a) if (!b.has(x)) return false;\n return true;\n}\n\n/**\n * Rewrite every `brief_sources.chunk_doc_id` from `oldId` to `newId`.\n * Idempotent and INSERT-OR-IGNORE friendly. The dedicated method lives\n * here (not in `BriefSourcesQueries`) because the rename heuristic is\n * a daemon concern; the query class stays focused on read-side lookups.\n */\nfunction rewriteBriefSourceDocId(vault: Vault, oldId: DocId, newId: DocId): void {\n // We reach into the shared db handle to issue the UPDATE. The query\n // class doesn't ship a `updateChunkDocId` method (yet); doing it\n // here keeps the migration surface minimal. If a future slice\n // promotes this to a first-class API on BriefSourcesQueries, the\n // signature is already correct.\n // Note: the UNIQUE(brief_doc_id, chunk_id_fragment) constraint is\n // unaffected because we only change `chunk_doc_id` — not the unique\n // key columns.\n vault.db.handle\n .prepare(\n `UPDATE brief_sources\n SET chunk_doc_id = ?\n WHERE chunk_doc_id = ?`,\n )\n .run(newId, oldId);\n}\n","/**\n * `vault-memory://briefs` — MCP Resource enumerating compiled briefs\n * (Plan 05-04, BRF-09). Mirrors the structural analog\n * `src/memory/resources/list-sinks.ts`.\n *\n * Resource, not Tool: brief discovery is a read-only side-effect-free\n * enumeration surface. Agents that want to find briefs by target read\n * this URI instead of invoking a tool. Per CONTEXT D-Q4 this is\n * polled-only — no `notifyResourceUpdated` integration in v2.x.\n *\n * The handler is a pure function over (`MemorySinkRegistry`,\n * `VaultManager`, per-vault `SourceConnector`). It MUST NOT touch\n * `node:fs`, `node:path`, `gray-matter`, or `chokidar` — all reads\n * route through `SourceConnector.listDocuments` + `readDocument`.\n * `scripts/lint-adapters.sh` enforces this.\n *\n * Status surfacing:\n * The resource projects `properties.status` verbatim so agents see\n * `active`, `stale`, and `superseded` entries. Callers filter\n * client-side; the registry-style listing intentionally lets the\n * chain be inspectable (this matches the \"let agents see the chain\"\n * stance in the Phase 5 CONTEXT discretion).\n *\n * Source-count semantics:\n * `source_count` is the row count from `brief_sources` — the\n * reverse-index of record per ADR-005. It is independent of\n * `properties.compiled_from`; if the brief was compiled without\n * chunk-level sources, `source_count === 0` even though the brief\n * exists.\n */\n\nimport type { MemorySinkRegistry } from \"../memory/registry.js\";\nimport type { VaultManager } from \"../vault/index.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\n\n/** Default sink name for briefs; the caller may override via `opts.sink`. */\nconst DEFAULT_BRIEF_SINK_NAME = \"_memory/_briefs\";\n\nexport interface ListBriefEntry {\n /** Canonical brief DocId (e.g. `obsidian-fs:///_memory/_briefs/----.md`). */\n doc_id: string;\n /** Brief target slug (e.g. `\"atlas-q3\"`). */\n target: string;\n /** Free-form purpose recorded at compile time. */\n purpose: string;\n /** ISO-8601 compile timestamp (UTC, milliseconds precision). */\n compiled_at: string;\n /** Lifecycle status: `\"active\"`, `\"stale\"`, or `\"superseded\"`. */\n status: string;\n /** Number of source-chunk rows in `brief_sources` for this brief. */\n source_count: number;\n /** Days since `compiled_at` (`floor((now - compiled_at) / 86400000)`). */\n age_days: number;\n /** Owning vault name. */\n vault: string;\n}\n\nexport interface ListBriefsResource {\n /** Total number of briefs across all enumerated vaults (post-filter). */\n total: number;\n briefs: ListBriefEntry[];\n}\n\nexport interface ListBriefsOpts {\n /** Restrict enumeration to a single vault. */\n vault?: string;\n /** Substring filter applied to `properties.target` (case-sensitive). */\n target?: string;\n /** Override the default `_memory/_briefs` sink. */\n sink?: string;\n /** Test override; defaults to `Date.now()`. */\n _now?: number;\n}\n\nexport interface ListBriefsDeps {\n registry: MemorySinkRegistry;\n manager: VaultManager;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\n/**\n * Build the resource payload by enumerating each vault's brief sink\n * through `SourceConnector.listDocuments`, reading each candidate\n * document, and projecting briefs that satisfy `properties.type ===\n * \"brief\"`. Substring-filter on `properties.target` when\n * `opts.target` is set.\n */\nexport async function readListBriefs(\n deps: ListBriefsDeps,\n opts: ListBriefsOpts = {},\n): Promise {\n const sinkName = opts.sink ?? DEFAULT_BRIEF_SINK_NAME;\n const now = opts._now ?? Date.now();\n\n // Resolve which vaults to enumerate. When `opts.vault` is set we go\n // single-vault via `manager.require()` (which throws on unknown vault\n // — same contract as `handleGetBrief`). Otherwise we fan out over\n // every vault the manager knows about.\n const vaults =\n opts.vault !== undefined ? [deps.manager.require(opts.vault)] : deps.manager.list();\n\n const out: ListBriefEntry[] = [];\n for (const vault of vaults) {\n const vaultName = vault.config.name;\n // The brief sink might not be registered in every vault (the\n // sink-registry is per-vault). Skip vaults that have no brief\n // sink — they have no briefs to list.\n let resolveTo: string;\n try {\n const briefSink = deps.registry.resolveMemorySink(sinkName);\n if (briefSink.vault !== vaultName) continue;\n resolveTo = briefSink.resolveToRelativePath;\n } catch {\n continue;\n }\n\n const connector = deps.sourceConnectorFor(vaultName);\n for await (const ref of connector.listDocuments()) {\n // listDocuments returns DocumentRef; the path-prefix filter\n // applies to the resource portion of the DocId. We use a\n // substring check on the canonical id (`:///`)\n // since the brief sink's `resolveToRelativePath` is part of\n // the DocId resource segment. Cheap pre-filter before the\n // expensive `readDocument()` call.\n if (!String(ref.id).includes(`/${resolveTo}`)) continue;\n\n let doc;\n try {\n doc = await connector.readDocument(ref.id);\n } catch {\n // Tolerate transient read errors so the discovery surface\n // never crashes the resource read. The brief simply won't\n // show up; subsequent reads can retry.\n continue;\n }\n const props = doc.properties as Record;\n if (props.type !== \"brief\") continue;\n const target = typeof props.target === \"string\" ? props.target : \"\";\n if (opts.target !== undefined && !target.includes(opts.target)) continue;\n\n const compiledAt = typeof props.compiled_at === \"string\" ? props.compiled_at : \"\";\n const purpose = typeof props.purpose === \"string\" ? props.purpose : \"\";\n const status = typeof props.status === \"string\" ? props.status : \"active\";\n const sourceCount = vault.db.briefSources.sourcesForBrief(doc.id).length;\n const compiledAtMs = compiledAt ? Date.parse(compiledAt) : NaN;\n const ageDays = Number.isNaN(compiledAtMs)\n ? Number.POSITIVE_INFINITY\n : Math.floor((now - compiledAtMs) / 86_400_000);\n\n out.push({\n doc_id: String(doc.id),\n target,\n purpose,\n compiled_at: compiledAt,\n status,\n source_count: sourceCount,\n age_days: ageDays,\n vault: vaultName,\n });\n }\n }\n\n return { total: out.length, briefs: out };\n}\n","/**\n * Phase 5 — `src/brief/` barrel.\n *\n * Wave 0 (Plan 05-01) — slice-1 exports only:\n * - canonical chunk-hash / fragment helpers (re-exported from the\n * chunker so brief consumers have one import surface);\n * - branded `ChunkId` + `parseChunkId` / `formatChunkId` /\n * `decomposeChunkId`;\n * - `buildSourceHashes` / `recomputeCurrentHash`.\n *\n * Later slices (05-02, 05-03, 05-04) extend this barrel with:\n * - `handleCompileBrief`, `handleGetBrief` (slice 2);\n * - `BriefBodyValidator`, `BriefStalenessDaemon`, lockfile (slice 3);\n * - `list_briefs` Resource (slice 4).\n *\n * No fs / gray-matter / chokidar / path imports in any slice-1 file\n * (`scripts/lint-adapters.sh` enforces).\n */\n\nexport { computeChunkHash, computeChunkIdFragment } from \"./source-hashes.js\";\nexport { buildSourceHashes, recomputeCurrentHash, type ChunkSource } from \"./source-hashes.js\";\nexport { parseChunkId, formatChunkId, decomposeChunkId, type ChunkId } from \"./chunk-id.js\";\n\n// ── Slice 2 (Plan 05-02) — LLM ladder + body validator ──────────────\nexport {\n resolveLlmStrategy,\n compileWithLlm,\n BriefLlmUnavailableError,\n BriefLlmSamplingRefusedError,\n type LlmStrategy,\n} from \"./llm-ladder.js\";\nexport { validateAndPatchBody } from \"./body-validator.js\";\nexport {\n handleCompileBrief,\n type CompileBriefArgs,\n type CompileBriefDeps,\n type CompileBriefResult,\n} from \"./compile.js\";\nexport {\n handleGetBrief,\n type GetBriefArgs,\n type GetBriefDeps,\n type GetBriefResult,\n} from \"./get.js\";\n\n// ── Slice 3 (Plan 05-03) — staleness daemon + lockfile primitive ────\nexport {\n tryAcquireLock,\n releaseLock,\n isProcessAlive,\n type LockResult,\n type LockAcquired,\n type LockContended,\n} from \"./lock.js\";\nexport { BriefStalenessDaemon, type DaemonDeps, type DaemonStartResult } from \"./daemon.js\";\n\n// ── Slice 4 (Plan 05-04) — list_briefs MCP Resource (BRF-09) ────────\nexport { readListBriefs } from \"./resources.js\";\nexport type {\n ListBriefsResource,\n ListBriefEntry,\n ListBriefsOpts,\n ListBriefsDeps,\n} from \"./resources.js\";\n","/**\n * `searchSections` — the ASM-03 controller.\n *\n * Section-level retrieval that COMPOSES (does not reimplement) the v1\n * chunk-level hybrid pipeline (`hybridSearch`) with a chunk → section\n * promotion step.\n *\n * Composition algorithm (per 03-RESEARCH.md §3 option 3):\n *\n * 1. Run `hybridSearch` with an inflated `topK = limit * 5`. The\n * multiplier is a cushion: a section may span 1..N chunks, so we\n * need enough chunk candidates that the top `limit` sections all\n * land in the post-promotion set.\n * 2. Promote each chunk hit to its enclosing section via\n * `findContainingChunk`. A chunk that does NOT map to any section\n * (legacy pre-migration-010 row, or a chunk whose section has\n * NULL `chunk_id_first`/`chunk_id_last`) is silently dropped.\n * 3. De-duplicate by `(note_id, heading_path, anchor)` — the section\n * identity per ADR-032. When multiple chunk hits\n * land in the same section, the section's score is the MAX of\n * the constituent chunk scores — the natural reading of\n * \"how relevant is this section\". RRF rank-position scores would\n * systematically punish short sections under summation; max is\n * both fairer and easier to reason about.\n * 4. Sort by score DESC; tie-break by `chunk_id_first` ASC so\n * earlier sections in document order win deterministic ties.\n * 5. Slice to `limit`. Hydrate the surviving sections into\n * `SectionHit` packets via the `Document` returned by the\n * injected `SourceConnector` so callers always get the canonical\n * 8-field citation floor PLUS the section-specific extras\n * (anchor, score, chunk_ids, snippet).\n *\n * Adapter-seam discipline (ADR-002 §Invariants, enforced by\n * `scripts/lint-adapters.sh`): this module imports NOTHING from\n * `node:fs`, `node:path`, `gray-matter`, or `chokidar`. All FS / vault-\n * content access goes through injected dependencies (`searchHybrid`,\n * `sectionForHit`, `readDocument`, `displayUrlFor`).\n *\n * Inflight-dependency note: slice 03-05 extends `hybridSearch` with\n * optional `recency_weight`, `authority_weight`, `include_superseded`\n * params (additive). This controller accepts those args from callers\n * but, until 03-05 merges, the production wiring forwards only the\n * subset that `hybridSearch` currently understands. The Zod schema in\n * `tool-registry.ts` accepts the full set so the wire surface is\n * forward-compatible; see `.planning/phases/03-bundles-authority-staleness/03-03-DEVIATIONS.md`.\n */\n\nimport type { DocId, Document, SearchHit, SourceHandle } from \"../types.js\";\nimport type { CitationPacket } from \"../memory/citation-packet.js\";\nimport { toCitationPacket } from \"../memory/citation-packet.js\";\n\n/**\n * Input shape for the `search_sections` MCP tool. Validated upstream\n * by Zod in `tool-registry.ts`; this is the post-validation shape.\n */\nexport interface SearchSectionsArgs {\n query: string;\n limit: number;\n vaults?: string[];\n /** Forwarded to `hybridSearch` once slice 03-05 lands. Placeholder\n * until then — see file-header inflight note. */\n recency_weight?: number;\n authority_weight?: number;\n include_superseded?: boolean;\n}\n\n/**\n * Minimal projection of a `SectionRow` carrying only what the promotion\n * step needs. Keeps the dependency surface narrow so test stubs do not\n * have to fabricate the full DB row.\n */\nexport interface SectionResolution {\n /** Numeric DB note id; carried only for the dedup key. */\n noteId: number;\n /** Section's content-hash anchor (ADR-003 H-7). */\n anchor: string;\n /** Section heading path (root → leaf). Empty for preamble (level 0). */\n headingPath: string[];\n /** For deterministic tiebreak: section's earliest chunk_id_first. */\n chunkIdFirst: number;\n}\n\n/**\n * Input passed to the injected `searchHybrid` dep. Mirrors the subset\n * of `HybridSearchOptions` this controller drives. Server bootstrap\n * supplies the production closure; tests inject a stub.\n *\n * Slice 03-05 will additively widen this with the rescore params; the\n * fields are already accepted (and IGNORED) here so a one-line wiring\n * change suffices once 03-05 lands.\n */\nexport interface SearchSectionsHybridInput {\n query: string;\n topK: number;\n vaults?: string[];\n}\n\nexport interface SearchSectionsDeps {\n /** Inner chunk-level hybrid search. */\n searchHybrid: (input: SearchSectionsHybridInput) => Promise;\n /**\n * Resolve the section enclosing a chunk hit. Returns `null` when no\n * containing section exists (orphan chunk — silently dropped).\n *\n * The hit identifies a chunk via `(vault, notePath, chunkIdx)`. The\n * adapter wiring is responsible for the chunkIdx → chunk_id lookup\n * and the `SectionsQueries.findContainingChunk` call.\n */\n sectionForHit: (\n vaultName: string,\n notePath: string,\n chunkIdx: number,\n ) => SectionResolution | null;\n /**\n * Read the canonical `Document` for a (vault, notePath) so the\n * SectionHit can carry the full 8-field citation packet floor.\n * Throws when the doc is missing — callers may silently drop on\n * throw (stale index row), but this controller surfaces the error.\n */\n readDocument: (vaultName: string, notePath: string) => Promise;\n /**\n * Adapter-mediated display URL for a `DocId`. Same seam used by\n * recall (`citation-packet.displayUrlFor`); the wiring passes a\n * closure that resolves via `SourceConnector.formatDisplayUrl`.\n */\n displayUrlFor: (docId: DocId, vaultName: string) => string;\n}\n\n/**\n * Section-level retrieval response item. Extends the 8-field citation\n * packet floor (D-01) with the section-specific extras called out in\n * the plan's \"Section hit shape\" table.\n */\nexport interface SectionHit extends CitationPacket {\n /** Section's canonical content-hash anchor (ADR-003 H-7). */\n anchor: string;\n /** MAX of the constituent chunk scores. */\n score: number;\n /** Snippet from the highest-scoring contributing chunk. */\n snippet?: string;\n /** Every chunk_idx that contributed to this section in this query. */\n chunk_ids: number[];\n}\n\n/**\n * Multiplier applied to `limit` when sizing the inner `hybridSearch`\n * candidate pool. Rationale: a section may span 1..N chunks, so we\n * need enough chunk candidates that the top `limit` sections (post-\n * promotion + dedup) are all represented. 5× is the same cushion the\n * v1 reranker uses for its fan-out (`hybrid.ts:rerankFanOut`).\n */\nconst TOP_K_INFLATION_FACTOR = 5;\n\n/**\n * Internal accumulator shape — tracks the constituent chunks of a\n * section as we walk the chunk hits.\n */\ninterface SectionAccumulator {\n resolution: SectionResolution;\n /** The hit whose score is currently the section's max. */\n bestHit: SearchHit;\n bestScore: number;\n /** Every contributing `chunkIdx` (used for `SectionHit.chunk_ids`). */\n chunkIdxs: number[];\n /** Owning vault name — needed for the per-hit `Document` read. */\n vaultName: string;\n /** Owning note path — needed for the per-hit `Document` read. */\n notePath: string;\n}\n\n/**\n * Run section-level retrieval. See the file header for the full\n * composition algorithm.\n */\nexport async function searchSections(\n deps: SearchSectionsDeps,\n args: SearchSectionsArgs,\n): Promise {\n // 1) Inflate topK and call the inner hybrid pipeline. A single call\n // keeps the v1 RRF (+ optional rerank) byte-identical.\n const chunkHits = await deps.searchHybrid({\n query: args.query,\n topK: args.limit * TOP_K_INFLATION_FACTOR,\n vaults: args.vaults,\n });\n\n if (chunkHits.length === 0) return [];\n\n // 2) Promote each chunk hit to its enclosing section, accumulating\n // by `(note_id, anchor)`. Drop orphan chunks silently.\n const sectionMap = new Map();\n for (const hit of chunkHits) {\n const resolution = deps.sectionForHit(hit.vault, hit.notePath, hit.chunkIdx);\n if (!resolution) continue;\n // Plan acceptance: heading_path always non-empty. Preamble\n // (level 0, empty heading_path) is dropped — preamble has no\n // human-readable anchor and would surface as a citation with no\n // heading, which is precisely what the acceptance excludes.\n if (resolution.headingPath.length === 0) continue;\n\n // Dedup key matches the section identity (note_id, heading_path, anchor)\n // per ADR-032: two byte-identical sections in DIFFERENT contexts are\n // distinct citations and must not merge here. heading_path is joined with\n // a separator that cannot appear in a heading slug segment.\n const key = `${resolution.noteId}#${resolution.headingPath.join(\"\u0000\")}#${resolution.anchor}`;\n const existing = sectionMap.get(key);\n if (!existing) {\n sectionMap.set(key, {\n resolution,\n bestHit: hit,\n bestScore: hit.score,\n chunkIdxs: [hit.chunkIdx],\n vaultName: hit.vault,\n notePath: hit.notePath,\n });\n continue;\n }\n // Section already seen — add chunkIdx, raise max score if needed.\n existing.chunkIdxs.push(hit.chunkIdx);\n if (hit.score > existing.bestScore) {\n existing.bestScore = hit.score;\n existing.bestHit = hit;\n }\n }\n\n if (sectionMap.size === 0) return [];\n\n // 3) Sort by score DESC, tie-break by `chunk_id_first` ASC. Earlier\n // sections in document order win deterministic ties.\n const sorted = [...sectionMap.values()].sort((a, b) => {\n if (b.bestScore !== a.bestScore) return b.bestScore - a.bestScore;\n return a.resolution.chunkIdFirst - b.resolution.chunkIdFirst;\n });\n\n // 4) Slice to limit BEFORE hydration — avoids paying for `Document`\n // reads on losing sections.\n const winners = sorted.slice(0, args.limit);\n\n // 5) Hydrate each surviving section into a `SectionHit`. The\n // citation packet is built from the full `Document` (via the\n // injected `readDocument` dep) so we get the canonical hash +\n // full property bag. The section-specific fields override\n // `heading_path` (with the section's path) and add anchor /\n // score / chunk_ids / snippet.\n const hits: SectionHit[] = [];\n for (const acc of winners) {\n let doc: Document;\n try {\n doc = await deps.readDocument(acc.vaultName, acc.notePath);\n } catch {\n // Stale index pointer to a deleted doc — silently drop, as\n // recall does. The post-slice + drop means the result count\n // may dip below `limit` in this edge case; we accept that\n // rather than re-running with a larger inflation factor.\n continue;\n }\n const packet = toCitationPacket(\n {\n id: doc.id,\n source: doc.source,\n title: doc.title,\n mtime: doc.mtime,\n hash: doc.hash,\n properties: doc.properties,\n heading_path: acc.resolution.headingPath,\n },\n deps.displayUrlFor(doc.id, acc.vaultName),\n );\n const hit: SectionHit = {\n ...packet,\n anchor: acc.resolution.anchor,\n score: acc.bestScore,\n chunk_ids: [...acc.chunkIdxs],\n };\n if (acc.bestHit.chunkText.length > 0) {\n hit.snippet = acc.bestHit.chunkText;\n }\n hits.push(hit);\n }\n\n return hits;\n}\n\n// Re-exports for ergonomic imports.\nexport type { CitationPacket, DocId, Document, SearchHit, SourceHandle };\n","/**\n * Phase 3 (ASM-02) — `get_outline` controller.\n *\n * Resolves a DocId into a nested outline tree of `OutlineNode`s built\n * from the `sections` table (landed in 03-01, migration 010) plus the\n * document-level citation packet (title/mtime/hash/display_url) read\n * through the `SourceConnector` seam.\n *\n * Pipeline:\n *\n * 1. Decompose the DocId into `{vaultName, path}`. The DocId scheme\n * portion (e.g. `obsidian-fs`) is preserved on the response via\n * `source_handle`. Optional `vaults` filter — when set, the\n * decomposed vault MUST appear in it (otherwise `doc_not_found`).\n * 2. Resolve the `Vault` from the manager (throws `doc_not_found`\n * shaped error on unknown vault).\n * 3. Load the note row by path (`notes.getByPath`). Missing row →\n * `doc_not_found` (the indexer hasn't seen this doc yet, OR it\n * was deleted between the catch-up scan and this call).\n * 4. Read the canonical `Document` via the SourceConnector. The\n * adapter's `formatDisplayUrl(id)` mints the deep-link URL.\n * 5. Query `sections.getByNote(noteId)` — returns rows in\n * parent-id-NULL-first, then parent-id ASC, then ord ASC order\n * (one DFS-friendly pass).\n * 6. Build the tree by parent-pointer reconstruction.\n * 7. Resolve each section's `chunk_ids` from a single\n * `chunks.getByNote(noteId)` lookup, filtered per-section by the\n * stored `[chunk_id_first, chunk_id_last]` range. Sections with\n * NULL ranges produce `chunk_ids: []`.\n *\n * Adapter-seam discipline (per `scripts/lint-adapters.sh`): NO `fs`,\n * `gray-matter`, `chokidar`, or `path.*` imports. Document reads route\n * through the injected `SourceConnector`. SQLite access (the `sections`,\n * `notes`, `chunks` query namespaces) is permitted — that is the L0\n * substrate, not the adapter tier.\n *\n * Error contract (per plan §\"Empty / unknown doc_id\"): an unknown\n * `doc_id` is an exceptional case — callers asked about a specific doc\n * by ID. Throw a tagged error; the server dispatch wraps it into\n * `{ isError: true, content: [...JSON.stringify({error: \"doc_not_found\", doc_id})...] }`.\n */\n\nimport { decomposeDocId, parseDocId, parseSourceHandle } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport { toCitationPacket, displayUrlFor } from \"../memory/citation-packet.js\";\nimport type { ChunkRow, SectionRow } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\nimport type { GetOutlineArgs, OutlineNode, OutlineResult } from \"./types.js\";\n\n/**\n * Dedicated error class for the \"unknown doc_id\" case. The server\n * handler catches this and emits the structured `{error: \"doc_not_found\",\n * doc_id}` payload required by the plan's error contract — distinct\n * from generic exception messages (validation errors, etc).\n */\nexport class DocNotFoundError extends Error {\n override readonly name = \"DocNotFoundError\";\n readonly doc_id: string;\n constructor(docId: string) {\n super(`Document not found: ${docId}`);\n this.doc_id = docId;\n }\n}\n\n/**\n * Injected dependencies for `getOutline`. Mirrors `RecallDeps` in\n * `src/memory/tools/recall.ts` — the server bootstrap supplies the\n * production wiring, tests inject in-memory stubs.\n */\nexport interface GetOutlineDeps {\n manager: VaultManager;\n /** Resolve the `SourceConnector` for a vault name. */\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\n/**\n * Public entry point. See file header for the full pipeline.\n */\nexport async function getOutline(\n deps: GetOutlineDeps,\n args: GetOutlineArgs,\n): Promise {\n // 1) Validate-decompose the DocId. `parseDocId` throws on malformed\n // input — surface as `doc_not_found` (callers gave us a bad id).\n let parsed: { scheme: string; authority: string; resource: string };\n try {\n const docId = parseDocId(args.doc_id);\n parsed = decomposeDocId(docId);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n const { scheme, authority: vaultName, resource: path } = parsed;\n\n // Optional vault-filter narrowing. The DocId already names a vault;\n // the filter exists for callers that want to assert they're talking\n // to a known set (e.g. a multi-vault agent guarding a tenant boundary).\n if (args.vaults && args.vaults.length > 0 && !args.vaults.includes(vaultName)) {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 2) Resolve the Vault. `manager.require` throws on unknown — map to\n // DocNotFoundError so the wire response is consistent.\n let vault: Vault;\n try {\n vault = deps.manager.require(vaultName);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 3) Look up the note row by path. Missing row → doc_not_found.\n const noteRow = vault.db.notes.getByPath(path);\n if (!noteRow) {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 4) Read the canonical Document via the source seam. We use this\n // for the doc-level citation-packet fields (title/mtime/hash/\n // display_url) — staying off the DB-cached row keeps us aligned\n // with `read_note` (which also reads fresh through the seam per\n // Plan 01-03 Task 06).\n const source = deps.sourceConnectorFor(vaultName);\n const docId = parseDocId(args.doc_id);\n let docFields: { title: string; mtime: number; hash: string };\n let displayUrl: string;\n try {\n const doc = await source.readDocument(docId);\n docFields = { title: doc.title, mtime: doc.mtime, hash: doc.hash };\n // Use the canonical packet helpers so display-URL resolution\n // matches recall + Phase 3 conformance assertions byte-for-byte.\n const packet = toCitationPacket(doc, displayUrlFor(doc.id, source));\n displayUrl = packet.display_url;\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 5) Read all sections for the note. `getByNote` returns rows in\n // parent-NULL-first, then by parent_id ASC, then ord ASC — exactly\n // the order needed to populate `byId` before any child references\n // its parent in step 6.\n const sectionRows = vault.db.sections.getByNote(noteRow.id);\n\n // 7-prep) Load all chunks for the note once. Sections will filter\n // this list by their stored [chunk_id_first, chunk_id_last] range.\n // For a note with N chunks and S sections, this is one O(N) read\n // + S × O(N) filters — totally fine for v2 doc sizes (N≤low\n // thousands; S≤low hundreds). Could be optimized later with a\n // range-keyed SQL helper, but kept simple here.\n const allChunks: ChunkRow[] = vault.db.chunks.getByNote(noteRow.id);\n\n // 6) Build the tree. Parent-pointer reconstruction in one pass.\n const root = buildOutlineTree(sectionRows, allChunks);\n\n // Compose the response. `source_handle` is derived from the DocId's\n // scheme + vault — minted via `parseSourceHandle` so the brand is\n // valid at the type level.\n const sourceHandle = parseSourceHandle(`${scheme}://${vaultName}`);\n\n return {\n doc_id: docId,\n source_handle: sourceHandle,\n title: docFields.title,\n root,\n mtime: docFields.mtime,\n hash: docFields.hash,\n display_url: displayUrl,\n };\n}\n\n/**\n * Build the outline tree from a flat `SectionRow[]` (in\n * `getByNote` order — NULL parents first) plus the note's chunks\n * (for `chunk_ids` resolution).\n *\n * Exported only for unit tests; production callers use `getOutline`.\n */\nexport function buildOutlineTree(rows: SectionRow[], allChunks: ChunkRow[]): OutlineNode[] {\n const byId = new Map();\n const roots: OutlineNode[] = [];\n for (const r of rows) {\n const node: OutlineNode = {\n anchor: r.anchor,\n heading_path: parseHeadingPath(r.heading_path),\n heading_text: r.heading_text,\n level: r.level,\n chunk_ids: collectChunkIdsInRange(allChunks, r.chunk_id_first, r.chunk_id_last),\n children: [],\n };\n byId.set(r.id, node);\n if (r.parent_id == null) {\n roots.push(node);\n } else {\n const parent = byId.get(r.parent_id);\n // Defensive: a row whose `parent_id` has not yet been seen would\n // indicate a getByNote ordering regression. The 03-01 contract\n // guarantees NULL-first ordering, so this branch is unreachable\n // in production. Tests that violate the contract will surface\n // the bug as a visibly-orphan node rather than a silent drop.\n if (parent) {\n parent.children.push(node);\n } else {\n roots.push(node);\n }\n }\n }\n return roots;\n}\n\n/**\n * Parse the stringified-JSON `heading_path` column with one defensive\n * fallback: a `null` / malformed payload yields `[]` (no crash). The\n * 03-01 indexer always writes a valid JSON array; this defense is\n * cheap and prevents one bad row from poisoning the whole tree.\n */\nfunction parseHeadingPath(raw: string): string[] {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (Array.isArray(parsed) && parsed.every((s) => typeof s === \"string\")) {\n return parsed as string[];\n }\n return [];\n } catch {\n return [];\n }\n}\n\n/**\n * Resolve a section's chunk-id range into the actual chunk IDs (as\n * strings — opaque tokens for downstream consumers). Returns `[]`\n * when either bound is `null` (a heading with no body content).\n *\n * Chunks are filtered from the pre-loaded `allChunks` list rather\n * than re-queried per-section, which keeps the overall outline build\n * at O(N + S × N) — fine for v2 doc sizes.\n */\nfunction collectChunkIdsInRange(\n allChunks: ChunkRow[],\n first: number | null,\n last: number | null,\n): string[] {\n if (first === null || last === null) return [];\n const ids: string[] = [];\n for (const c of allChunks) {\n if (c.id >= first && c.id <= last) {\n ids.push(String(c.id));\n }\n }\n return ids;\n}\n","/**\n * DebouncedQueue — coalesces rapid filesystem events on the same path\n * into a single onFlush call.\n *\n * Semantics:\n * - Multiple \"change\" events on the same path within debounceMs collapse\n * to one flush.\n * - \"delete\" overrides a pending \"change\" (delete is final).\n * - A later \"change\" on a pending \"delete\" replaces it (file came back).\n * - maxLatencyMs caps how long an entry may sit pending; once exceeded,\n * the next enqueue (or scheduled timer) flushes it immediately.\n */\n\nexport interface QueueEvent {\n /** Vault-relative path, forward slashes. */\n path: string;\n /** \"change\" or \"delete\". add/change are merged to \"change\". */\n kind: \"change\" | \"delete\";\n}\n\nexport interface DebouncedQueueOptions {\n /** Debounce window in ms. Default 500. */\n debounceMs?: number;\n /** Maximum age (ms) a pending event may sit before forced flush. Default 5000. */\n maxLatencyMs?: number;\n /** Called when an event is ready to be processed. Errors are caught + logged. */\n onFlush: (event: QueueEvent) => Promise | void;\n /** Optional error sink — invoked with (event, error) when onFlush throws. */\n onError?: (event: QueueEvent, err: unknown) => void;\n}\n\ninterface PendingEntry {\n kind: \"change\" | \"delete\";\n /** Insertion order — first time this path was enqueued in the current pending cycle. */\n firstSeen: number;\n /** Timer for the debounce window. */\n timer: ReturnType;\n}\n\nexport class DebouncedQueue {\n private readonly debounceMs: number;\n private readonly maxLatencyMs: number;\n private readonly onFlush: (event: QueueEvent) => Promise | void;\n private readonly onError: (event: QueueEvent, err: unknown) => void;\n private readonly pending = new Map();\n /** Tracks in-flight flush promises so flushAll can await them. */\n private readonly inFlight = new Set>();\n private stopped = false;\n\n constructor(options: DebouncedQueueOptions) {\n this.debounceMs = options.debounceMs ?? 500;\n this.maxLatencyMs = options.maxLatencyMs ?? 5000;\n this.onFlush = options.onFlush;\n this.onError =\n options.onError ??\n ((event, err) => {\n // eslint-disable-next-line no-console\n console.error(`[DebouncedQueue] onFlush failed for ${event.path} (${event.kind}):`, err);\n });\n }\n\n /**\n * Enqueue an event. After shutdown() this is a no-op.\n */\n enqueue(event: QueueEvent): void {\n if (this.stopped) return;\n\n const now = Date.now();\n const existing = this.pending.get(event.path);\n\n // maxLatencyMs guard: if we already have a pending entry that has been\n // sitting longer than the cap, flush it now (with its current kind)\n // before recording the new event.\n if (existing && now - existing.firstSeen >= this.maxLatencyMs) {\n clearTimeout(existing.timer);\n this.pending.delete(event.path);\n this.dispatch({ path: event.path, kind: existing.kind });\n // Fall through and record the new event fresh.\n }\n\n const prior = this.pending.get(event.path);\n const firstSeen = prior?.firstSeen ?? now;\n if (prior) clearTimeout(prior.timer);\n\n // Coalesce kind. Later events overwrite. (Both delete-after-change and\n // change-after-delete simply take the latest event's kind, matching the\n // documented behavior.)\n const kind: \"change\" | \"delete\" = event.kind;\n\n // Schedule debounce. If the entry is close to maxLatency, fire sooner.\n const age = now - firstSeen;\n const remaining = this.maxLatencyMs - age;\n const delay = Math.max(0, Math.min(this.debounceMs, remaining));\n\n const timer = setTimeout(() => {\n const entry = this.pending.get(event.path);\n if (!entry) return;\n this.pending.delete(event.path);\n this.dispatch({ path: event.path, kind: entry.kind });\n }, delay);\n\n this.pending.set(event.path, { kind, firstSeen, timer });\n }\n\n /** Force-flush all pending events. Resolves once all onFlush calls settle. */\n async flushAll(): Promise {\n // Snapshot in insertion order (Map preserves it).\n const entries = [...this.pending.entries()];\n for (const [path, entry] of entries) {\n clearTimeout(entry.timer);\n this.pending.delete(path);\n this.dispatch({ path, kind: entry.kind });\n }\n // Await any in-flight promises (including just-dispatched ones).\n while (this.inFlight.size > 0) {\n await Promise.all([...this.inFlight]);\n }\n }\n\n /** Cancel timers, drop pending events. Idempotent. After this enqueue is a no-op. */\n shutdown(): void {\n if (this.stopped) return;\n this.stopped = true;\n for (const entry of this.pending.values()) {\n clearTimeout(entry.timer);\n }\n this.pending.clear();\n }\n\n /** Pending event count (excludes in-flight). */\n size(): number {\n return this.pending.size;\n }\n\n private dispatch(event: QueueEvent): void {\n let result: Promise | void;\n try {\n result = this.onFlush(event);\n } catch (err) {\n this.safeOnError(event, err);\n return;\n }\n if (result && typeof (result as Promise).then === \"function\") {\n const p = (result as Promise)\n .catch((err: unknown) => this.safeOnError(event, err))\n .finally(() => {\n this.inFlight.delete(p);\n });\n this.inFlight.add(p);\n }\n }\n\n private safeOnError(event: QueueEvent, err: unknown): void {\n try {\n this.onError(event, err);\n } catch {\n // swallow — onError is best-effort\n }\n }\n}\n","/**\n * Chokidar watcher options for the obsidian-fs adapters.\n *\n * Shared by:\n * - `VaultWatcher` (v1 live-indexing path; see ./watcher.ts)\n * - `ObsidianFsChangeFeed` (v2 ChangeFeed seam; see ./index.ts)\n *\n * The four critical fields originated BYTE-FOR-BYTE from v1\n * (`src/watcher/watcher.ts:79-96` pre-plan-01-05) per RESEARCH Pitfall 6.\n * Modifying these values may break the suppression-set integration (the\n * watcher could race the atomic-rename suppression window) — DO NOT\n * change without first re-running the suppression conformance test in\n * `src/adapters/change-feed/conformance.test.ts`.\n *\n * - awaitWriteFinish: { stabilityThreshold: 400, pollInterval: 50 }\n * - ignored: [/(^|[\\\\/])\\../, \"**\\/*.tmp.*\"] (+ caller excludes)\n * - followSymlinks: false\n * - ignoreInitial: true (initial state arrives via indexVault catch-up)\n *\n * Note (quick-task 260515-hkc): stabilityThreshold was bumped 200→400ms\n * to give a 300–400ms safety margin over the 700–800ms test sleeps in\n * change-feed.test.ts:91 and watcher.test.ts:95, which intermittently\n * raced under full-suite load. It remains safely below the two\n * 400ms-sleep test cases (closed-feed + drain()) which pass for\n * unrelated reasons. The suppression integration test (Pitfall 6) was\n * re-run and remains green — extending the stability window only\n * widens the favorable race for own-write suppression.\n */\n\nimport { posix } from \"node:path\";\nimport type { ChokidarOptions } from \"chokidar\";\n\n/**\n * Build chokidar options for a vault root.\n *\n * The caller-provided `excludes` are joined with `vaultPath` (absolute\n * glob patterns) and pre-pended to the v1 baseline filters (`hidden\n * files at any level` regex + `**\\/*.tmp.*` atomic-write artifacts).\n */\nexport function buildChokidarOptions(\n vaultPath: string,\n excludes: ReadonlyArray,\n): ChokidarOptions {\n return {\n persistent: true,\n ignoreInitial: true, // we expect initial state via indexVault\n ignored: [\n // chokidar handles glob-like patterns. Provide both raw and absolute.\n ...excludes.map((g) => posix.join(vaultPath, g)),\n /(^|[\\\\/])\\../, // hidden files at any level\n \"**/*.tmp.*\", // our atomic-write artifacts\n ],\n // Only watch markdown files — saves event volume.\n // chokidar's `ignored` runs against absolute paths, so we filter via\n // an after-the-fact event check (cheaper than a glob).\n awaitWriteFinish: {\n stabilityThreshold: 400,\n pollInterval: 50,\n },\n followSymlinks: false,\n };\n}\n","/**\n * VaultWatcher — chokidar-driven incremental re-indexing.\n *\n * Lifecycle: start() opens a chokidar watcher on the vault path, routes\n * change/add/unlink events through a DebouncedQueue, and on flush invokes\n * indexNote / removeNote.\n *\n * Suppression: writes from the MCP server itself (writeNote, deleteNote,\n * updateFrontmatter) mark the path on a shared SuppressionSet just before\n * touching the filesystem. The watcher checks + consumes the entry; if\n * present, the event is dropped. This prevents endless write→watch→reindex\n * loops.\n */\n\nimport chokidar from \"chokidar\";\nimport type { FSWatcher } from \"chokidar\";\nimport { sep as nativeSep } from \"node:path\";\nimport type { Vault } from \"../../../vault/index.js\";\nimport type { OllamaClient } from \"../../../ollama/index.js\";\nimport { indexNote, removeNote } from \"../../../indexer/index.js\";\nimport { DebouncedQueue, type QueueEvent } from \"./queue.js\";\nimport type { SuppressionSet } from \"./suppression.js\";\nimport { buildChokidarOptions } from \"./chokidar-config.js\";\nimport { errorMessage } from \"../../../errors/format.js\";\n\nexport interface VaultWatcherOptions {\n vault: Vault;\n embeddingModel: string;\n /** Phase 7c: optional shadow model name; passed through to indexNote so\n * the secondary index stays current on live file edits. Silently\n * ignored if the model is not yet registered in the DB. */\n secondaryEmbeddingModel?: string;\n ollama: OllamaClient;\n suppression: SuppressionSet;\n /** Debounce window (ms) for coalescing rapid file changes. Default 500. */\n debounceMs?: number;\n /** Log sink — defaults to stderr. */\n log?: (msg: string) => void;\n}\n\nexport class VaultWatcher {\n private fsWatcher: FSWatcher | null = null;\n private queue: DebouncedQueue;\n private readonly opts: Required<\n Omit\n > & {\n log: (msg: string) => void;\n debounceMs: number;\n secondaryEmbeddingModel: string | undefined;\n };\n private started = false;\n /** ADR-008: debounce timer for ContextFit KB re-ingest (coalesces bursts). */\n private cfReingestTimer: ReturnType | null = null;\n private cfReingestInFlight = false;\n\n constructor(options: VaultWatcherOptions) {\n this.opts = {\n vault: options.vault,\n embeddingModel: options.embeddingModel,\n secondaryEmbeddingModel: options.secondaryEmbeddingModel,\n ollama: options.ollama,\n suppression: options.suppression,\n debounceMs: options.debounceMs ?? 500,\n log: options.log ?? ((m) => process.stderr.write(`[watcher] ${m}\\n`)),\n };\n\n this.queue = new DebouncedQueue({\n debounceMs: this.opts.debounceMs,\n maxLatencyMs: 5000,\n onFlush: (event) => this.handleFlush(event),\n onError: (event, err) => {\n const message = errorMessage(err);\n this.opts.log(`error processing ${event.path}: ${message}`);\n },\n });\n }\n\n async start(): Promise {\n if (this.started) return;\n const vaultPath = this.opts.vault.config.path;\n const excludes = this.opts.vault.config.exclude_globs ?? [];\n\n this.fsWatcher = chokidar.watch(vaultPath, buildChokidarOptions(vaultPath, excludes));\n\n this.fsWatcher.on(\"add\", (path) => this.onFsEvent(path, \"change\"));\n this.fsWatcher.on(\"change\", (path) => this.onFsEvent(path, \"change\"));\n this.fsWatcher.on(\"unlink\", (path) => this.onFsEvent(path, \"delete\"));\n this.fsWatcher.on(\"error\", (err) => {\n const message = errorMessage(err);\n this.opts.log(`fs watcher error: ${message}`);\n });\n\n await new Promise((resolve) => {\n this.fsWatcher!.once(\"ready\", () => resolve());\n });\n\n this.started = true;\n this.opts.log(`watching ${vaultPath}`);\n }\n\n /** Force-process any pending events. Used during shutdown. */\n async drain(): Promise {\n await this.queue.flushAll();\n }\n\n async stop(): Promise {\n if (!this.started) return;\n this.started = false;\n this.queue.shutdown();\n if (this.cfReingestTimer) {\n clearTimeout(this.cfReingestTimer);\n this.cfReingestTimer = null;\n }\n if (this.fsWatcher) {\n await this.fsWatcher.close();\n this.fsWatcher = null;\n }\n }\n\n // ─── internal ──────────────────────────────────────────────────────────\n\n /**\n * ADR-008: schedule a debounced full ContextFit KB re-ingest. Per-note\n * changes update the SQLite layer immediately (via indexNote); the ContextFit\n * search KB is rebuilt in one coalesced pass ~1.5s after the last change so a\n * burst of edits triggers a single re-ingest. CPU-only and fast.\n */\n private scheduleContextFitReingest(): void {\n if (this.cfReingestTimer) clearTimeout(this.cfReingestTimer);\n this.cfReingestTimer = setTimeout(() => {\n this.cfReingestTimer = null;\n void this.runContextFitReingest();\n }, 1500);\n }\n\n private async runContextFitReingest(): Promise {\n if (this.cfReingestInFlight) {\n // A re-ingest is already running; schedule another pass after it so the\n // latest changes are captured.\n this.scheduleContextFitReingest();\n return;\n }\n this.cfReingestInFlight = true;\n try {\n const { indexVaultWithContextFit } = await import(\"../../retrieval/contextfit/index.js\");\n const r = await indexVaultWithContextFit(this.opts.vault.config, {});\n if (r.status === \"completed\") {\n this.opts.log(`ContextFit KB refreshed (${r.durationMs}ms)`);\n } else {\n this.opts.log(`ContextFit KB refresh failed: ${r.error}`);\n }\n } catch (err) {\n this.opts.log(\n `ContextFit KB refresh error: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n this.cfReingestInFlight = false;\n }\n }\n\n private onFsEvent(absolutePath: string, kind: \"change\" | \"delete\"): void {\n // Filter to .md only — Obsidian writes other artifacts (.obsidian/*) that\n // we either don't care about or already excluded.\n if (!absolutePath.endsWith(\".md\")) return;\n\n const relativePath = this.toRelative(absolutePath);\n\n // Suppression: was this just written by the MCP server itself?\n if (this.opts.suppression.consume(relativePath)) {\n this.opts.log(`suppressed ${kind} ${relativePath} (own write)`);\n return;\n }\n\n this.queue.enqueue({ path: absolutePath, kind });\n }\n\n private toRelative(absolutePath: string): string {\n const root = this.opts.vault.config.path;\n let rel = absolutePath;\n if (rel.startsWith(root)) rel = rel.slice(root.length);\n if (rel.startsWith(nativeSep) || rel.startsWith(\"/\")) rel = rel.slice(1);\n return rel.split(nativeSep).join(\"/\");\n }\n\n private async handleFlush(event: QueueEvent): Promise {\n const relativePath = this.toRelative(event.path);\n\n const isContextFit = this.opts.vault.config.backend === \"contextfit\";\n\n if (event.kind === \"delete\") {\n const result = removeNote(this.opts.vault, event.path);\n if (result.removed) {\n this.opts.log(`removed ${relativePath}`);\n // ADR-008: a deleted note must drop out of the ContextFit KB too.\n if (isContextFit) this.scheduleContextFitReingest();\n } else {\n this.opts.log(`delete event for unknown ${relativePath} (skip)`);\n }\n return;\n }\n\n const result = await indexNote({\n vault: this.opts.vault,\n absolutePath: event.path,\n embeddingModel: this.opts.embeddingModel,\n secondaryEmbeddingModel: this.opts.secondaryEmbeddingModel,\n // ADR-008: ContextFit vaults build the SQLite layer without embeddings;\n // their search KB is refreshed by the debounced re-ingest below.\n ...(isContextFit ? { embeddings: \"none\" as const } : { ollama: this.opts.ollama }),\n });\n\n switch (result.status) {\n case \"indexed\":\n this.opts.log(\n `indexed ${relativePath} (${result.isNew ? \"new\" : \"updated\"}, ${result.chunksCreated} chunks)`,\n );\n // ADR-008: refresh the ContextFit search KB (debounced full re-ingest).\n if (isContextFit) this.scheduleContextFitReingest();\n break;\n case \"unchanged\":\n // Common when chokidar fires for a re-save with no content delta —\n // log at debug level (skip entirely for now).\n break;\n case \"outside_vault\":\n this.opts.log(`event for path outside vault ignored: ${event.path}`);\n break;\n case \"missing\":\n // File disappeared between event and parse — treat as delete.\n this.opts.log(`file missing on parse — removing ${relativePath}`);\n removeNote(this.opts.vault, event.path);\n break;\n }\n }\n}\n","/**\n * SuppressionSet — short-lived registry of paths the server itself just\n * touched, so the file watcher can ignore the resulting filesystem events.\n *\n * Entries auto-expire after `ttlMs` (default 2000). `consume(path)` returns\n * true exactly once per add — repeated consumes return false. This ensures\n * a legitimate later edit to the same file is NOT suppressed.\n *\n * # Phase 7 / Plan 07-07 / CAN-08 — hash-keyed suppression (additive)\n *\n * Phase 6 (RESEARCH §6 Pitfall 1) discovered that a pure path/TTL gate\n * cannot distinguish \"the agent's own write echoed back\" from \"the user\n * edited the file in another editor within the TTL window\". Plan 07-07\n * extends the API additively:\n *\n * - `add(path)` — existing path-only behavior; second arg may be a\n * number for `ttlMs` (legacy callers in writer/indexer pass this).\n * - `add(path, { ttlMs?, hash? })` — new options form. When `hash` is\n * recorded, `consume(path, hash)` only suppresses if hashes match;\n * a mismatch leaves the entry intact (so a later legitimate match\n * can still drop it).\n * - `consume(path)` — unconditional; matches today's semantics.\n * - `consume(path, hash)` — if the recorded entry has a hash, requires\n * equality; entries without a recorded hash always match (legacy\n * path-only entries fall through, so existing callers stay correct).\n *\n * Choice rationale (planner option (a) — overloaded `add`): the second\n * argument's type discriminates legacy vs. new shape. `typeof ttlMs ===\n * \"number\"` continues to mean \"TTL override\"; `typeof === \"object\"` is\n * the new options form. The option (b) split (`add` + `addHashed`) was\n * rejected on call-site simplicity grounds — the new `suppress_contract_write`\n * MCP tool wants the options-object form so its handler reads cleanly.\n *\n * # Trust boundary (THREAT-T-07-07-02 mitigation)\n *\n * TTL is bounded by the caller (the `suppress_contract_write` Zod schema\n * caps it at 30s). Hash mismatch on consume keeps the entry intact so\n * the next legitimate match still works — this guards against a\n * suppression entry \"swallowing\" a real external edit.\n *\n * @see plan 07-07 §\"Task 1\" — full behavior matrix.\n * @see ADR-007 §D-WATCH-PLUGIN-OUT — hash-keyed contract for the\n * plugin's YAML companion emission.\n */\n\nexport interface SuppressionOptions {\n /** Default TTL for new entries in ms. Default 2000. */\n ttlMs?: number;\n /** Override for testing: a clock function returning epoch ms. Default Date.now. */\n now?: () => number;\n}\n\n/** Per-entry options for the additive `add(path, opts)` overload. */\nexport interface SuppressionEntryOptions {\n /** Per-entry TTL override; falls back to the set's default. */\n ttlMs?: number;\n /**\n * Optional content hash. When present, `consume(path, hash)` only\n * suppresses on hash equality; mismatches leave the entry intact.\n * See file header for the full semantics matrix.\n */\n hash?: string;\n}\n\ninterface Entry {\n expiresAt: number;\n /** Recorded content hash (when the caller supplied one). */\n hash?: string;\n}\n\nexport class SuppressionSet {\n private readonly defaultTtlMs: number;\n private readonly now: () => number;\n private readonly entries = new Map();\n\n constructor(options: SuppressionOptions = {}) {\n this.defaultTtlMs = options.ttlMs ?? 2000;\n this.now = options.now ?? Date.now;\n }\n\n /**\n * Mark a path as \"expect a filesystem event for this — please ignore it\".\n *\n * Legacy form: `add(path)` or `add(path, ttlMs)`.\n * Hash-keyed form: `add(path, { ttlMs?, hash? })`.\n *\n * @see file header for the full backwards-compatibility matrix.\n */\n add(path: string, ttlMsOrOpts?: number | SuppressionEntryOptions): void {\n this.prune();\n let ttl: number;\n let hash: string | undefined;\n if (typeof ttlMsOrOpts === \"number\") {\n ttl = ttlMsOrOpts;\n } else if (ttlMsOrOpts !== undefined) {\n ttl = ttlMsOrOpts.ttlMs ?? this.defaultTtlMs;\n hash = ttlMsOrOpts.hash;\n } else {\n ttl = this.defaultTtlMs;\n }\n const entry: Entry = { expiresAt: this.now() + ttl };\n if (hash !== undefined) entry.hash = hash;\n this.entries.set(path, entry);\n }\n\n /**\n * If path is suppressed, return true and (usually) remove the entry.\n *\n * Hash semantics:\n * - `consume(path)` — unconditional; removes the entry.\n * - `consume(path, undefined)` — same as above.\n * - `consume(path, hash)` — if the recorded entry has a hash\n * and it does NOT equal `hash`, leave the entry intact and return\n * false (RESEARCH §6 Pitfall 1: don't let an arbitrary external\n * edit consume our suppression slot). When hashes match, remove\n * and return true. When the recorded entry has no hash, treat it\n * as a legacy path-only entry and match unconditionally.\n */\n consume(path: string, hash?: string): boolean {\n this.prune();\n const entry = this.entries.get(path);\n if (!entry) return false;\n if (entry.expiresAt <= this.now()) {\n this.entries.delete(path);\n return false;\n }\n // Hash-aware path: when the caller supplies a hash AND the entry has\n // one, require equality. If they don't match, preserve the entry so\n // a later legitimate match can still consume it.\n if (hash !== undefined && entry.hash !== undefined && entry.hash !== hash) {\n return false;\n }\n this.entries.delete(path);\n return true;\n }\n\n /** Read-only check; does not consume. */\n has(path: string): boolean {\n this.prune();\n const entry = this.entries.get(path);\n if (!entry) return false;\n if (entry.expiresAt <= this.now()) {\n this.entries.delete(path);\n return false;\n }\n return true;\n }\n\n /** Drop expired entries. */\n prune(): void {\n const t = this.now();\n for (const [path, entry] of this.entries) {\n if (entry.expiresAt <= t) {\n this.entries.delete(path);\n }\n }\n }\n\n size(): number {\n this.prune();\n return this.entries.size;\n }\n}\n","/**\n * ObsidianFsChangeFeed — the ChangeFeed adapter for filesystem-backed\n * Obsidian vaults (ADR-002 §ChangeFeed; plan 01-05 task 02).\n *\n * # What this is\n *\n * The watch seam. Subscribers receive `ChangeEvent`s as the underlying\n * filesystem changes. Internally backed by a chokidar watcher configured\n * via the shared `buildChokidarOptions` helper (`./chokidar-config.ts`)\n * — the SAME four-field config used by the v1 `VaultWatcher`, preserved\n * BYTE-FOR-BYTE from v1 per RESEARCH Pitfall 6:\n *\n * - awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }\n * - ignored: [/(^|[\\\\/])\\../, \"**\\/*.tmp.*\"] (+ caller excludes)\n * - followSymlinks: false\n * - ignoreInitial: true\n *\n * Modifying these values breaks the suppression-set integration. The\n * conformance test (\"suppression marker registered → no ChangeEvent\n * emitted\") is the safety net.\n *\n * # Event mapping\n *\n * chokidar `add` → ChangeEvent { kind: \"create\", id, at }\n * chokidar `change` → ChangeEvent { kind: \"update\", id, at }\n * chokidar `unlink` → ChangeEvent { kind: \"delete\", id, at }\n *\n * Rename emission is DEFERRED to Phase 4 (RESEARCH A3 / Risk #3) — a\n * true OS-level rename surfaces in chokidar as `unlink` + `add` and\n * Phase 1 keeps that v1 behavior. `ChangeFeedCapabilities.emitsRename`\n * is FALSE for honest publication per Invariant I-7.\n *\n * # Suppression-set integration (Pitfall 6)\n *\n * The MCP server marks paths on a shared `SuppressionSet` immediately\n * before atomic-rename writes (see `handleWriteNote` / `handleDelete` /\n * `handleUpdateFrontmatter` in `src/server.ts`). On every chokidar event,\n * this feed checks `suppression.consume(relativePath)` first; if hit,\n * the event is dropped. This prevents the write → watch → re-index loop.\n *\n * # Filtering\n *\n * Only `.md` files emit events. Other artifacts (`.obsidian/*`, lock\n * files, etc.) are filtered by either chokidar's `ignored` regex or a\n * post-event suffix check — same as v1.\n *\n * # Lifecycle\n *\n * - `subscribe(handler)` registers a handler; multiple subscribers each\n * get a copy of every event. Returns a `Disposable` whose\n * `Symbol.dispose` unregisters the handler synchronously.\n * - `close()` is idempotent. After close, no more events fire. Future\n * `subscribe` calls register but receive no events (the watcher is\n * gone). The conformance suite gates this assertion on\n * `capabilities.watch === \"push\"`.\n *\n * # Coexistence with v1 VaultWatcher\n *\n * Phase 1 wires BOTH the v1 `VaultWatcher` (live-indexing path, drives\n * indexNote/removeNote) AND this `ObsidianFsChangeFeed` (registry-\n * exposed ChangeFeed seam, used by conformance tests + future Phase 2+\n * indexer rewiring) into the bootstrap. Both watch the same vault with\n * the SAME chokidar options — duplicate event volume, but each event is\n * cheap and suppression filters own-writes in both watchers. A future\n * plan will retire the v1 VaultWatcher in favor of an indexer that\n * subscribes through the ChangeFeed seam directly (RESEARCH §Recommended\n * Decomposition note).\n */\n\nimport chokidar from \"chokidar\";\nimport type { FSWatcher } from \"chokidar\";\nimport { sep as nativeSep } from \"node:path\";\nimport type { Vault } from \"../../../vault/index.js\";\nimport type { ChangeEvent, DocId, SourceHandle } from \"../../../types.js\";\nimport type { ChangeFeed, ChangeFeedCapabilities, Disposable } from \"../types.js\";\nimport { formatDocId, parseSourceHandle } from \"../../registry.js\";\nimport { SuppressionSet } from \"./suppression.js\";\nimport { buildChokidarOptions } from \"./chokidar-config.js\";\nimport { errorMessage } from \"../../../errors/format.js\";\n\nconst SCHEME = \"obsidian-fs\";\n\nexport interface ObsidianFsChangeFeedOptions {\n /** Vault providing config (path + name + exclude_globs). */\n vault: Vault;\n /**\n * Shared with `ObsidianFsDelivery` so own-writes (atomic rename\n * artifacts) don't fire events. The delivery adapter adds the\n * vault-relative path before `atomicWriteFile`; this feed\n * `consume()`s on every chokidar event. (Pitfall 6 invariant.)\n */\n suppression: SuppressionSet;\n /** Optional stderr logger; defaults to silent. */\n log?: (msg: string) => void;\n}\n\nexport class ObsidianFsChangeFeed implements ChangeFeed {\n readonly handle: SourceHandle;\n readonly capabilities: ChangeFeedCapabilities = {\n watch: \"push\",\n /**\n * Phase 1 emits delete+create rather than a tagged rename event.\n * Honest publication per Invariant I-7 — the conformance test\n * asserts no `{kind: \"rename\"}` event is observed when this flag\n * is false.\n */\n emitsRename: false,\n };\n\n private readonly vault: Vault;\n private readonly suppression: SuppressionSet;\n private readonly log: (msg: string) => void;\n private readonly handlers = new Set<(e: ChangeEvent) => void | Promise>();\n private fsWatcher: FSWatcher | null = null;\n private startPromise: Promise | null = null;\n private closed = false;\n\n constructor(options: ObsidianFsChangeFeedOptions) {\n this.vault = options.vault;\n this.suppression = options.suppression;\n this.log = options.log ?? ((_m) => {});\n this.handle = parseSourceHandle(`${SCHEME}://${this.vault.config.name}`);\n }\n\n subscribe(handler: (e: ChangeEvent) => void | Promise): Disposable {\n if (this.closed) {\n // After close, register-but-never-fire is the contract floor for\n // the conformance suite. Returning an inert Disposable mirrors\n // what users get if they subscribe before start() has resolved.\n return { [Symbol.dispose]: () => void 0 };\n }\n this.handlers.add(handler);\n // Lazy start — first subscribe brings the watcher up. Subsequent\n // subscribes attach to the same watcher.\n if (!this.startPromise) {\n this.startPromise = this.start();\n }\n return {\n [Symbol.dispose]: () => {\n this.handlers.delete(handler);\n },\n };\n }\n\n /**\n * Wait until the chokidar watcher has reported \"ready\". Test-only\n * helper — the conformance test awaits this between `subscribe` and\n * its first synthetic event so the watcher has surveyed the dir.\n */\n async ready(): Promise {\n if (this.startPromise) {\n await this.startPromise;\n }\n }\n\n async close(): Promise {\n if (this.closed) return; // idempotent\n this.closed = true;\n this.handlers.clear();\n if (this.fsWatcher) {\n await this.fsWatcher.close();\n this.fsWatcher = null;\n }\n }\n\n // ─── internal ──────────────────────────────────────────────────────────\n\n private async start(): Promise {\n if (this.closed) return;\n const vaultPath = this.vault.config.path;\n const excludes = this.vault.config.exclude_globs ?? [];\n\n const watcher = chokidar.watch(vaultPath, buildChokidarOptions(vaultPath, excludes));\n this.fsWatcher = watcher;\n\n watcher.on(\"add\", (absolutePath) => this.onFsEvent(absolutePath, \"create\"));\n watcher.on(\"change\", (absolutePath) => this.onFsEvent(absolutePath, \"update\"));\n watcher.on(\"unlink\", (absolutePath) => this.onFsEvent(absolutePath, \"delete\"));\n watcher.on(\"error\", (err) => {\n const message = errorMessage(err);\n this.log(`fs watcher error: ${message}`);\n });\n\n await new Promise((resolve) => {\n watcher.once(\"ready\", () => resolve());\n });\n }\n\n private onFsEvent(absolutePath: string, kind: \"create\" | \"update\" | \"delete\"): void {\n if (this.closed) return;\n // Only emit for markdown files — same v1 filter.\n if (!absolutePath.endsWith(\".md\")) return;\n\n const relativePath = this.toRelative(absolutePath);\n\n // Pitfall 6: own-write suppression. The delivery adapter marked this\n // path on the shared SuppressionSet before its atomic rename; consume\n // the entry and drop the event so we don't loop.\n if (this.suppression.consume(relativePath)) {\n this.log(`suppressed ${kind} ${relativePath} (own write)`);\n return;\n }\n\n const id: DocId = formatDocId(SCHEME, this.vault.config.name, relativePath);\n const event: ChangeEvent = { kind, id, at: Date.now() };\n this.fanout(event);\n }\n\n private toRelative(absolutePath: string): string {\n const root = this.vault.config.path;\n let rel = absolutePath;\n if (rel.startsWith(root)) rel = rel.slice(root.length);\n if (rel.startsWith(nativeSep) || rel.startsWith(\"/\")) rel = rel.slice(1);\n return rel.split(nativeSep).join(\"/\");\n }\n\n private fanout(event: ChangeEvent): void {\n // Snapshot handlers before iterating — a handler may dispose during\n // its own callback, which would otherwise corrupt the iteration.\n for (const handler of [...this.handlers]) {\n try {\n const result = handler(event);\n if (result && typeof (result as Promise).then === \"function\") {\n (result as Promise).catch((err: unknown) => {\n const message = errorMessage(err);\n this.log(`handler error: ${message}`);\n });\n }\n } catch (err) {\n const message = errorMessage(err);\n this.log(`handler error: ${message}`);\n }\n }\n }\n}\n","/**\n * `obsidian-fs` ChangeFeed adapter barrel.\n *\n * Re-exports the relocated v1 VaultWatcher / DebouncedQueue / SuppressionSet\n * primitives PLUS the new `ObsidianFsChangeFeed` facade implementing\n * the `ChangeFeed` interface (ADR-002 §ChangeFeed, plan 01-05 task 02).\n *\n * Invariant I-1 (ADR-002): chokidar imports live ONLY under this directory.\n * Plan 01-06 ships the lint script that enforces this mechanically.\n */\n\nexport { VaultWatcher } from \"./watcher.js\";\nexport type { VaultWatcherOptions } from \"./watcher.js\";\nexport { DebouncedQueue } from \"./queue.js\";\nexport type { QueueEvent, DebouncedQueueOptions } from \"./queue.js\";\nexport { SuppressionSet } from \"./suppression.js\";\nexport type { SuppressionOptions } from \"./suppression.js\";\nexport { ObsidianFsChangeFeed } from \"./change-feed.js\";\nexport type { ObsidianFsChangeFeedOptions } from \"./change-feed.js\";\n","// Single literal source of truth for v1 tools/list. Imported by src/server.ts (runtime) and evals/v1-baseline/dump-tools.mjs (snapshot generator).\n//\n// Two exports:\n//\n// - `TOOLS`: ReadonlyArray of `{name, description, inputSchema}` — the\n// JSON Schema literal source of truth. Drives `dump-tools.mjs` and the\n// pinned `evals/v1-baseline/tools-list.snapshot.json`. MUST stay\n// JSON-serializable / snapshot-stable. Do not add non-serializable\n// fields here.\n//\n// - `TOOL_SCHEMAS`: Record — the Zod 4 raw\n// shapes paired with each tool. Passed to `McpServer.registerTool`\n// (SDK 1.29) for type-safe argument parsing + auto-derived\n// `tools/list` publication. The shapes carry per-field `.describe()`\n// calls so the SDK-published JSON Schema retains rich descriptions.\n//\n// Plan 01-05 design note (deviation from plan literal): the plan asked\n// for a single `TOOLS` entry carrying both `inputSchema` and `zodSchema`,\n// and for `registerTool` to receive `inputSchema: tool.inputSchema` (raw\n// JSON Schema literal). Both proved blocking under SDK 1.29:\n//\n// 1. Adding a Zod schema field onto each `TOOLS` entry breaks the\n// snapshot generator (Zod objects are not JSON-serializable; the\n// pinned snapshot would change shape).\n// 2. SDK 1.29 `registerTool` validates that `inputSchema` is either a\n// Zod schema instance or a Zod raw shape (see\n// node_modules/@modelcontextprotocol/sdk/.../mcp.js:861-872 —\n// `getZodSchemaObject` throws on plain JSON Schema). Passing the\n// raw JSON Schema literal is not supported by the API.\n//\n// The two-export design preserves the plan's INTENT:\n// - Snapshot stability (TOOLS literal unchanged).\n// - Single source of truth for v1 tools/list shape (TOOLS).\n// - Zod 4 at handler time + Zod-driven publication via the SDK's\n// own `toJsonSchemaCompat` (TOOL_SCHEMAS).\n// - End-to-end description propagation verified empirically — the\n// Pitfall 2 / SDK#1143 workaround is moot in SDK 1.29 (descriptions\n// pass through both the top-level `description` and per-field\n// `.describe()` chains).\n\nexport const TOOLS = [\n {\n name: \"list_vaults\",\n description:\n \"List configured vaults with their status (note count, last indexed run). \" +\n \"DEPRECATED since v2.0.0 — prefer MCP Resource `vault-memory://vaults` for agent discovery. \" +\n \"The tool remains callable through v2.x; removal scheduled for v3.0.0.\",\n inputSchema: { type: \"object\", properties: {} },\n },\n {\n name: \"read_note\",\n description: \"Read the full content + frontmatter of a note by its vault-relative path.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\"],\n properties: {\n vault: { type: \"string\", description: \"Configured vault name\" },\n path: {\n type: \"string\",\n description: \"Vault-relative path with forward slashes, ending in .md\",\n },\n },\n },\n },\n {\n name: \"search_semantic\",\n description: \"Semantic search via embedding cosine similarity. Searches all vaults by default.\",\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: { type: \"string\" },\n vaults: { type: \"array\", items: { type: \"string\" } },\n top_k: {\n type: \"integer\",\n minimum: 1,\n maximum: 100,\n default: 10,\n },\n exclude_paths: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"Glob patterns (e.g. '_research/eval.md', '**/index.md') of paths to exclude.\",\n },\n },\n },\n },\n {\n name: \"search_text\",\n description: \"Full-text BM25 search via SQLite FTS5. Best for exact-word and phrase matches.\",\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: {\n type: \"string\",\n description: \"FTS5 query — whitespace-separated tokens are AND'd; use OR explicitly.\",\n },\n vaults: { type: \"array\", items: { type: \"string\" } },\n top_k: {\n type: \"integer\",\n minimum: 1,\n maximum: 100,\n default: 10,\n },\n exclude_paths: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Glob patterns of paths to exclude.\",\n },\n },\n },\n },\n {\n name: \"search_hybrid\",\n description:\n \"Hybrid search: combines semantic (embedding) and BM25 (full-text) results via Reciprocal Rank Fusion. Best general-purpose query. Pass `expand: {hops: 1}` to auto-attach 1–2 hop typed-edge neighbors as `expansions[]` per hit (preserves ranking; runs after recency/authority rescore).\",\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: { type: \"string\" },\n vaults: { type: \"array\", items: { type: \"string\" } },\n top_k: {\n type: \"integer\",\n minimum: 1,\n maximum: 100,\n default: 10,\n },\n rrf_k: {\n type: \"integer\",\n minimum: 1,\n maximum: 1000,\n default: 60,\n description: \"RRF constant — higher dampens emphasis on top ranks.\",\n },\n exclude_paths: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Glob patterns of paths to exclude.\",\n },\n rerank: {\n type: \"boolean\",\n default: false,\n description:\n \"Apply a cross-encoder rerank over the top candidates. Requires `reranker_model` in server config; silently ignored otherwise.\",\n },\n recency_weight: {\n type: \"number\",\n default: 0,\n description:\n \"Phase 3 (D-07, ASM-07): additive recency term coefficient. final_score = rrf + recency_weight * exp(-age_days / half_life_days). Default 0 (no recency pressure — v1 behavior).\",\n },\n authority_weight: {\n type: \"number\",\n default: 0,\n description:\n \"Phase 3 (D-07, ASM-07): additive authority term coefficient. Adds `authority_weight * 1` for docs whose frontmatter has `authoritative: true`. Default 0.\",\n },\n half_life_days: {\n type: \"number\",\n minimum: 0,\n default: 30,\n description:\n \"Phase 3 (D-07): half-life for the recency exponential decay, in days. Default 30. Only meaningful when recency_weight > 0.\",\n },\n include_superseded: {\n type: \"boolean\",\n default: false,\n description:\n \"Phase 3 (D-08, ASM-08): when false (default), docs whose frontmatter has `status: superseded` are excluded at SQL level via the notes_status partial index. Set true to reveal them.\",\n },\n // ── Phase 4 / 04-04 / GRA-03 (D-15): additive auto-expansion ──\n // When omitted, search_hybrid behavior is byte-identical to v1.\n expand: {\n type: \"object\",\n required: [\"hops\"],\n description:\n \"Phase 4 (D-15, D-16): auto-attach 1–2 hop typed-edge neighbors as `expansions[]` per hit. Runs AFTER recency/authority rescore (D-16); never participates in score computation; top-K ranking unchanged.\",\n properties: {\n hops: { type: \"number\", enum: [1, 2] },\n direction: {\n type: \"string\",\n enum: [\"forward\", \"backward\", \"both\"],\n default: \"both\",\n },\n edge_types: {\n type: \"array\",\n items: {\n type: \"string\",\n enum: [\"wikilink\", \"mention\", \"frontmatter-ref\", \"hyperlink\"],\n },\n },\n },\n },\n },\n },\n },\n {\n name: \"list_backlinks\",\n description:\n \"Find all notes that link TO a given note. \" +\n \"DEPRECATED since v2.0.0 — prefer MCP Resource `vault-memory://backlinks/{vault}/{+docId}` \" +\n \"for agent discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\"],\n properties: {\n vault: { type: \"string\" },\n path: { type: \"string\" },\n },\n },\n },\n {\n name: \"list_forward_links\",\n description: \"List all wikilinks FROM a given note. Optionally include broken links.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\"],\n properties: {\n vault: { type: \"string\" },\n path: { type: \"string\" },\n include_broken: { type: \"boolean\", default: true },\n },\n },\n },\n {\n name: \"find_broken_links\",\n description: \"List all wikilinks in a vault that point to non-existent notes.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: { vault: { type: \"string\" } },\n },\n },\n {\n name: \"query_frontmatter\",\n description:\n \"Filter notes by their YAML frontmatter. Supports equality, $in, $exists, $contains predicates. Multiple keys are AND-combined.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"where\"],\n properties: {\n vault: { type: \"string\" },\n where: {\n type: \"object\",\n description:\n \"Field-name → predicate map. Predicate is a scalar (equality) or { $in: [...] } | { $exists: bool } | { $contains: scalar }.\",\n },\n limit: {\n type: \"integer\",\n minimum: 1,\n maximum: 1000,\n default: 100,\n },\n },\n },\n },\n {\n name: \"write_note\",\n description:\n \"Atomically create or overwrite a note. Requires write_enabled=true. Use expected_hash for safe overwrites (read the note first, pass its hash). Omit expected_hash only when creating a new note.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\", \"content\"],\n properties: {\n vault: { type: \"string\" },\n path: {\n type: \"string\",\n description: \"Vault-relative .md path, forward slashes.\",\n },\n content: {\n type: \"string\",\n description: \"Markdown body WITHOUT --- frontmatter delimiters.\",\n },\n frontmatter: {\n type: [\"object\", \"null\"],\n description: \"Optional frontmatter object. Set null to write no frontmatter block.\",\n },\n expected_hash: {\n type: \"string\",\n description: \"Required for overwrites — get it from read_note.\",\n },\n client_id: { type: \"string\" },\n },\n },\n },\n {\n name: \"update_frontmatter\",\n description:\n \"Modify a note's frontmatter only. The body is preserved bytegenau. Merge DSL: scalar=set, {$unset:true}=delete, {$push:x}=array append, {$pull:x}=array remove.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\", \"merge\"],\n properties: {\n vault: { type: \"string\" },\n path: { type: \"string\" },\n merge: {\n type: \"object\",\n description: \"Field → value | {$unset:bool} | {$push:scalar} | {$pull:scalar}\",\n },\n expected_hash: { type: \"string\" },\n client_id: { type: \"string\" },\n },\n },\n },\n {\n name: \"delete_note\",\n description: \"Delete a note. Requires write_enabled=true AND expected_hash (no blind deletes).\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\", \"expected_hash\"],\n properties: {\n vault: { type: \"string\" },\n path: { type: \"string\" },\n expected_hash: { type: \"string\" },\n client_id: { type: \"string\" },\n },\n },\n },\n {\n name: \"audit_log\",\n description:\n \"Query the write audit trail for a vault. Filterable by note path, operation type, or time. Default limit 50.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: {\n vault: { type: \"string\" },\n note_path: { type: \"string\" },\n op: { type: \"string\", enum: [\"create\", \"update\", \"delete\"] },\n since: {\n type: \"integer\",\n description: \"Epoch ms — entries at or after this timestamp.\",\n },\n limit: { type: \"integer\", minimum: 1, maximum: 1000, default: 50 },\n is_memory_sink_write: {\n type: \"boolean\",\n description:\n \"Filter rows to memory-sink writes only (true) or non-memory writes only (false). Omit to include all. See docs/tools/audit_log.md.\",\n },\n },\n },\n },\n {\n name: \"list_models\",\n description:\n \"List all embedding models registered for a vault, with dim, \" +\n \"active flag, and how many chunks have been embedded under each. \" +\n \"Use before start_shadow_index / switch_active_model. \" +\n \"DEPRECATED since v2.0.0 — prefer MCP Resource `vault-memory://models/{vault}` for agent \" +\n \"discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: { vault: { type: \"string\" } },\n },\n },\n {\n name: \"start_shadow_index\",\n description:\n \"Backfill embeddings for a secondary (shadow) model over every \" +\n \"chunk in the vault. The active model is untouched — search keeps \" +\n \"working during the run. Idempotent (resumable). Run \" +\n \"switch_active_model once complete to promote the shadow.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"model\"],\n properties: {\n vault: { type: \"string\" },\n model: {\n type: \"string\",\n description: \"Ollama model name, e.g. 'bge-m3' or 'embeddinggemma'.\",\n },\n batch_size: {\n type: \"integer\",\n minimum: 1,\n maximum: 256,\n description: \"Embed batch size — default 16.\",\n },\n },\n },\n },\n {\n name: \"switch_active_model\",\n description:\n \"Atomically promote a registered model to active. Fails with \" +\n \"ok:false / reason:'incomplete' if any chunk is missing a shadow \" +\n \"embedding for the target model.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"model_name\"],\n properties: {\n vault: { type: \"string\" },\n model_name: { type: \"string\" },\n },\n },\n },\n {\n name: \"vacuum_embeddings\",\n description:\n \"Drop orphaned embedding rows whose chunk_id no longer exists in \" +\n \"the chunks table. Safe and idempotent; does not touch live data. \" +\n \"Useful after migrations from pre-v0.7.0 schemas where chunk \" +\n \"deletion did not always cascade to the derived layer.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: { vault: { type: \"string\" } },\n },\n },\n {\n name: \"index_runs\",\n description: \"List recent index runs for a vault — what was scanned, when, how long, errors.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: {\n vault: { type: \"string\" },\n limit: { type: \"integer\", minimum: 1, maximum: 200, default: 20 },\n },\n },\n },\n {\n name: \"search\",\n // Tool description names \"Claude.ai\" + \"Deep-Research\" as the // vault-memory:claude-ok\n // real OB1-connector-ecosystem product names; not a Claude-only coupling.\n description:\n \"OB1-compatible search adapter. Returns a flat list of {id, title, url, snippet} for connector ecosystems (ChatGPT Custom Connectors, Claude.ai, Deep-Research). Backed by hybrid (semantic+BM25+RRF) search. For richer output use search_hybrid.\", // vault-memory:claude-ok\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: { type: \"string\" },\n limit: { type: \"integer\", minimum: 1, maximum: 50, default: 10 },\n },\n },\n },\n {\n name: \"fetch\",\n description:\n \"OB1-compatible fetch adapter. Resolves an opaque id (from `search`) to {id, title, text, url, metadata}. Backed by read_note.\",\n inputSchema: {\n type: \"object\",\n required: [\"id\"],\n properties: {\n id: {\n type: \"string\",\n description: \"Opaque id from `search` results, format: :\",\n },\n },\n },\n },\n {\n name: \"vault_stats\",\n description:\n \"Vault overview for agent self-orientation: note/word counts, top tags, top frontmatter keys, embedding model, last index run. Omit `vault` to get all configured vaults. \" +\n \"DEPRECATED since v2.0.0 — prefer MCP Resource `vault-memory://stats/{vault}` for agent \" +\n \"discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.\",\n inputSchema: {\n type: \"object\",\n properties: {\n vault: { type: \"string\", description: \"Optional. Omit for all vaults.\" },\n },\n },\n },\n {\n name: \"recent_notes\",\n description:\n \"List recently modified notes (mtime DESC). Use for agent self-orientation: 'what has the user been working on lately?'. No vector search, just SQL. \" +\n \"DEPRECATED since v2.0.0 — prefer MCP Resource `vault-memory://recent/{vault}` for agent \" +\n \"discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.\",\n inputSchema: {\n type: \"object\",\n properties: {\n vault: { type: \"string\", description: \"Optional. Omit for all vaults.\" },\n limit: { type: \"integer\", minimum: 1, maximum: 200, default: 20 },\n since: {\n type: \"integer\",\n description: \"Optional unix-ms threshold. Only notes with mtime > since.\",\n },\n },\n },\n },\n {\n name: \"suggest_frontmatter\",\n description:\n \"Suggest frontmatter fields for a note based on folder-conventions, wikilink-neighborhood, and title/body content-heuristics. Returns {existing, suggestions, conflicts}. Two input modes: (1) existing note via {path}; (2) draft via {content, folder_hint, title}. At least one of path/content required. Suggestions sorted by confidence DESC; conflicts list disagreements between sources.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: {\n vault: { type: \"string\" },\n path: {\n type: \"string\",\n description:\n \"Vault-relative path. Required for existing-note mode; for drafts, pass content instead (folder_hint controls folder-inference).\",\n },\n content: {\n type: \"string\",\n description:\n \"Draft markdown body. When set, content-heuristics layer runs. If path is set AND content is omitted, the existing note's stored content is used.\",\n },\n title: {\n type: \"string\",\n description:\n \"Title for content-heuristics. Falls back to path basename or first heading.\",\n },\n folder_hint: {\n type: \"string\",\n description:\n \"For draft mode: the target folder (e.g. 'Personen/'). Ignored when `path` is set.\",\n },\n },\n },\n },\n // ── Phase 2 memory tools (Plan 02-04 + 02-05) ─────────────────────────────\n {\n name: \"record_observation\",\n description:\n \"Record a new memory observation under the labeled MemorySink for a vault. \" +\n \"Required provenance properties (source, confidence, evidence, status, observed_at, type, superseded_by) \" +\n \"are auto-filled from arguments; `properties` is an escape hatch for contract-allowed extras \" +\n \"and overrides any sugar default (D-02 — caller-last merge). \" +\n \"Writes route through DeliveryAdapter.write() and pass through the centralized provenance validator.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"claim\", \"evidence\", \"confidence\", \"type\"],\n properties: {\n vault: { type: \"string\", description: \"Vault name (registered in [vaults] config)\" },\n claim: {\n type: \"string\",\n description:\n \"Short natural-language statement of the observation (becomes title + body).\",\n },\n evidence: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"DocIds or quoted source spans supporting the claim; empty array allowed.\",\n },\n confidence: {\n type: \"string\",\n enum: [\"direct\", \"inferred\", \"uncertain\"],\n description: \"How the agent arrived at this claim.\",\n },\n type: {\n type: \"string\",\n description:\n \"Observation type per the sink contract (e.g. 'observation', 'hypothesis', 'decision').\",\n },\n sink: {\n type: \"string\",\n description:\n \"Memory sink name OR full obsidian-fs://… handle. Defaults to the vault's default sink.\",\n },\n properties: {\n type: \"object\",\n additionalProperties: true,\n description:\n \"Escape-hatch: contract-allowed extra properties; merged AFTER sugar args (caller wins).\",\n },\n },\n },\n },\n {\n name: \"supersede\",\n description:\n \"Mark an existing memory document as superseded by a replacement document. \" +\n \"Forward-only — the replacement doc is NOT touched; back-links are derived by the Phase 4 \" +\n 'graph layer at query time. Atomic single OCC update on the OLD doc; sets status=\"superseded\", ' +\n \"superseded_by, and superseded_reason.\",\n inputSchema: {\n type: \"object\",\n required: [\"doc_id\", \"replacement_doc_id\", \"reason\"],\n properties: {\n doc_id: {\n type: \"string\",\n description: \"DocId of the document being superseded.\",\n },\n replacement_doc_id: {\n type: \"string\",\n description: \"DocId of the replacement document.\",\n },\n reason: {\n type: \"string\",\n description: \"Why the old document is being retired; written to superseded_reason.\",\n },\n },\n },\n },\n // ── Phase 5 brief tools (Plan 05-02 / BRF-03) ────────────────────────────\n {\n name: \"compile_brief\",\n description:\n \"Compile a brief from caller-supplied source documents and write it to the briefs sink. \" +\n \"Resolves the LLM via the D-10 capability-first ladder (MCP Sampling → local Ollama → \" +\n \"caller `prepared_text` → structured error). Enforces D-11 wikilink emission per source \" +\n \"(appends a `## Sources` footer when the LLM omits them) and writes through DeliveryAdapter. \" +\n \"On target collision, auto-supersedes the prior brief via the Phase 2 supersede chain (D-12).\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"target\", \"source_doc_ids\", \"purpose\"],\n properties: {\n vault: { type: \"string\", description: \"Vault name (registered in [vaults] config)\" },\n target: {\n type: \"string\",\n description: \"Stable cross-version handle for the brief (e.g. 'atlas-q3').\",\n },\n source_doc_ids: {\n type: \"array\",\n items: { type: \"string\" },\n minItems: 1,\n maxItems: 50,\n description: \"DocIds the brief is compiled from; deduped, capped at 50 (D-03).\",\n },\n purpose: {\n type: \"string\",\n minLength: 1,\n maxLength: 500,\n description: \"Free-form purpose; bounded so list_briefs stays scannable.\",\n },\n max_tokens: {\n type: \"integer\",\n minimum: 1,\n default: 2000,\n description: \"Hint for the LLM ladder; default 2000.\",\n },\n prepared_text: {\n type: \"string\",\n description:\n \"D-10 tier 3 fallback when no LLM is reachable — verbatim body to stitch in.\",\n },\n sink: {\n type: \"string\",\n description: \"Override the default `_memory/_briefs` sink.\",\n },\n },\n },\n },\n {\n name: \"get_brief\",\n description:\n \"Look up a brief by target slug. D-13 decision tree: staleness dominates; age is \" +\n \"independent; follow the supersede chain to the terminal brief. Returns null when the \" +\n \"caller MUST recompile (stale + !allow_stale OR too_old + !allow_stale).\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"target\"],\n properties: {\n vault: { type: \"string\", description: \"Vault name (registered in [vaults] config)\" },\n target: { type: \"string\", description: \"Stable cross-version handle for the brief.\" },\n max_age_days: {\n type: \"integer\",\n minimum: 0,\n description: \"Reject briefs older than this many days unless allow_stale=true.\",\n },\n allow_stale: {\n type: \"boolean\",\n default: false,\n description:\n \"When true, return briefs flagged stale or too_old with annotation rather than null.\",\n },\n },\n },\n },\n // ── Phase 3 assembly tools (Plan 03-02 / ASM-02) ─────────────────────────\n {\n name: \"get_outline\",\n description:\n \"Return the navigable section tree for a document. Each OutlineNode \" +\n \"carries an `anchor` (the section's citation token), `heading_path` \" +\n \"(root → leaf), `heading_text`, `level`, and `chunk_ids` (v1 chunk-table \" +\n \"IDs in that section). Consume `anchor` + `heading_path` as the section-\" +\n \"level half of the citation packet. Unknown doc_id returns an error \" +\n \"response with {error:'doc_not_found', doc_id}.\",\n inputSchema: {\n type: \"object\",\n required: [\"doc_id\"],\n properties: {\n doc_id: {\n type: \"string\",\n description: \"Opaque DocId (obsidian-fs:///) of the document\",\n },\n vaults: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional vault filter; usually omitted (the DocId names a vault).\",\n },\n },\n },\n },\n // ── Phase 3 assembly tools (Plan 03-03) ──────────────────────────────────\n {\n name: \"search_sections\",\n description:\n \"Section-level retrieval. Composes the v1 hybrid (semantic + BM25 + RRF) pipeline with \" +\n \"a chunk-to-section promotion step: runs hybrid with an inflated top_k = limit × 5, \" +\n \"promotes each chunk hit to its enclosing section, dedupes by (note, section anchor), \" +\n \"scores each section as the MAX of its constituent chunks, tie-breaks by \" +\n \"chunk_id_first ASC, and returns the top `limit` sections. Each hit carries an 8-field \" +\n \"citation packet (D-01) with a non-empty section heading_path PLUS the section anchor, \" +\n \"score, contributing chunk_ids, and an optional snippet from the best-scoring chunk. \" +\n \"Use when you want WHOLE-SECTION context, not a chunk window.\",\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: { type: \"string\" },\n limit: {\n type: \"integer\",\n minimum: 1,\n maximum: 50,\n default: 10,\n },\n vaults: { type: \"array\", items: { type: \"string\" } },\n recency_weight: {\n type: \"number\",\n minimum: 0,\n default: 0,\n description:\n \"Forward-compat with slice 03-05's authority/staleness rescore. \" +\n \"Accepted today; ignored until 03-05 lands.\",\n },\n authority_weight: {\n type: \"number\",\n minimum: 0,\n default: 0,\n description:\n \"Forward-compat with slice 03-05's authority/staleness rescore. \" +\n \"Accepted today; ignored until 03-05 lands.\",\n },\n include_superseded: {\n type: \"boolean\",\n default: false,\n description:\n \"Forward-compat with slice 03-05. When false (default), superseded docs are \" +\n \"filtered out at the chunk level inside hybrid; accepted today, ignored until 03-05.\",\n },\n },\n },\n },\n // ── Phase 2 memory tools (Plan 02-05) ────────────────────────────────────\n {\n name: \"recall\",\n description:\n \"Retrieve memory documents from one or more labeled MemorySinks, filtered by \" +\n \"provenance (min_confidence, types, max_age_days) and ranked by recency (observed_at \" +\n \"DESC). Returns citation packets (doc_id, source_handle, title, heading_path, mtime, \" +\n \"hash, display_url, properties) — the same 8-field shape Phase 3 assembly tools use. \" +\n \"Superseded documents are hidden by default.\",\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: {\n type: \"string\",\n description: \"Natural-language query; routes through hybrid (semantic + BM25) search.\",\n },\n min_confidence: {\n type: \"string\",\n enum: [\"direct\", \"inferred\", \"uncertain\"],\n description:\n \"Exclude docs whose confidence ordinal is lower than this (direct=3, inferred=2, uncertain=1).\",\n },\n types: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Restrict to docs whose `type` property is in this set.\",\n },\n max_age_days: {\n type: \"integer\",\n minimum: 1,\n description: \"Exclude docs whose `observed_at` is older than this many days.\",\n },\n sink: {\n type: \"string\",\n description:\n \"Memory sink name OR full obsidian-fs://… handle. Defaults to all configured sinks.\",\n },\n limit: {\n type: \"integer\",\n minimum: 1,\n maximum: 200,\n default: 20,\n description: \"Max results AFTER filter+sort.\",\n },\n vaults: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Restrict to these vault names; defaults to all configured.\",\n },\n },\n },\n },\n // ── Phase 3 assembly tools (Plan 03-04 / ASM-01) ─────────────────────────\n {\n name: \"get_document_bundle\",\n description:\n \"Document-tree retrieval. Returns a structured bundle for a single document: \" +\n \"{ anchor (citation packet + optional status/superseded_by), outline (section tree \" +\n \"via buildOutlineTree — same shape as get_outline.root), backlinks (citation packets \" +\n '+ property_snippet + relation:\"wikilink\"), forward_links (same shape; broken links ' +\n \"omitted), recent_edits (≤10 most recent audit_log rows mapped to {at, op, client_id, \" +\n \"is_memory_sink_write?}) }. Every citation packet is the full 8-field D-01 shape from \" +\n \"src/memory/citation-packet.ts. v2.0.0 accepts only depth:1 (one-hop links); the field \" +\n \"is zod-pinned to z.literal(1) for forward compatibility. recent_edits is keyed by the \" +\n \"anchor's CURRENT note path — pre-rename history is preserved in audit_log but not \" +\n \"surfaced here (Phase 4 widens). Unknown doc_id returns \" +\n '{ isError: true, error: \"doc_not_found\", doc_id }.',\n inputSchema: {\n type: \"object\",\n required: [\"doc_id\"],\n properties: {\n doc_id: {\n type: \"string\",\n description: \"Opaque DocId (obsidian-fs:///) of the anchor document.\",\n },\n depth: {\n type: \"integer\",\n enum: [1],\n default: 1,\n description:\n \"Depth of the link walk. v2.0.0 accepts only depth:1 (one-hop). Phase 4 may widen.\",\n },\n vaults: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional vault filter; usually omitted (the DocId names a vault).\",\n },\n },\n },\n },\n // ── Phase 4 graph tools (Plan 04-03 / GRA-01) ───────────────────────────\n {\n name: \"expand\",\n description:\n \"Typed-edge BFS retrieval. Returns the typed-edge neighborhood of one or more \" +\n \"seed documents as a flat array of citation packets, each carrying \" +\n \"`via: {seed_doc_id, hop, edge_type, direction}` provenance. Hops hard-capped \" +\n \"at 2 (v2.0.0). Default direction = 'both'. Filterable by edge_type and by \" +\n \"document properties (strict equality, no operators). Memory-sink documents \" +\n \"(`_memory/...`) surface only when they are already linked from a user note in \" +\n \"the result set (per ADR-004 memory-namespace opacity rule). Frontmatter-ref \" +\n \"edges are extracted heuristically: `[[...]]` syntax in any property value OR \" +\n \"allowlisted property names (`assignee`, `owner`, `project`, `related`, \" +\n \"`parent`, `child`, `attendees`, `superseded_by`) matched against \" +\n \"`note_aliases`. `include_superseded` defaults to false (Phase 2 D-03 forward-\" +\n \"only supersede). Unknown seed_doc_ids do not throw — they are returned in a \" +\n \"`warnings: [{seed_doc_id, reason: 'unknown_doc'}]` array. Shortest path wins \" +\n \"on dedup; ties broken by (seed_doc_id, edge_type, direction).\",\n inputSchema: {\n type: \"object\",\n required: [\"seed_doc_ids\", \"hops\"],\n properties: {\n seed_doc_ids: {\n type: \"array\",\n minItems: 1,\n items: {\n type: \"string\",\n description: \"Opaque DocId (e.g. obsidian-fs:///).\",\n },\n },\n hops: {\n type: \"number\",\n enum: [1, 2],\n description: \"Hop cap (1 or 2). v2.0.0 hard-caps at 2.\",\n },\n direction: {\n type: \"string\",\n enum: [\"forward\", \"backward\", \"both\"],\n default: \"both\",\n description: \"Edge traversal direction; default 'both'.\",\n },\n edge_types: {\n type: \"array\",\n items: {\n type: \"string\",\n enum: [\"wikilink\", \"mention\", \"frontmatter-ref\", \"hyperlink\"],\n },\n description: \"Optional filter on edge types; default = all four types.\",\n },\n filter_properties: {\n type: \"object\",\n additionalProperties: true,\n description: \"Strict-equality predicate on document properties (e.g. {type: 'Project'}).\",\n },\n include_superseded: {\n type: \"boolean\",\n default: false,\n description:\n \"When false (default), docs whose properties.status === 'superseded' are dropped.\",\n },\n },\n },\n },\n // ── Phase 4 graph tools (Plan 04-05 / GRA-02) ───────────────────────────\n {\n name: \"cluster\",\n description:\n \"Community detection over the typed-edge graph via Louvain \" +\n \"modularity (Blondel et al. 2008) using `graphology` + \" +\n \"`graphology-communities-louvain`. Deterministic: same input \" +\n \"produces byte-identical cluster_id assignment via DocId-sorted \" +\n \"node insertion + seeded RNG (`vault-memory-cluster-v1`). \" +\n \"cluster_id = smallest member DocId per community. Hard-capped at \" +\n \"5000 nodes; pass `force: true` to override. Either `query` \" +\n \"(composes search_hybrid + expand 1-hop) OR `seed_doc_ids` (uses \" +\n \"provided seeds + induced 1-hop neighborhood); not both — passing \" +\n \"both returns {ok:false, reason:'both_seeds_and_query'}. On the \" +\n \"`query` path with multiple vaults configured, the `vault` field \" +\n \"is required so search scope is deterministic; single-vault setups \" +\n \"may omit it (returns {ok:false, reason:'vault_required'} otherwise). \" +\n \"Returns per-cluster {cluster_id, size, members[], summary: {top_types, \" +\n \"top_titles, edge_density}}. No LLM enrichment — summary fields \" +\n \"are pure-deterministic computations (LLM enrichment is Phase 5 \" +\n \"brief layer's job). _memory opacity inherited from expand() \" +\n \"(Plan 04-03).\",\n inputSchema: {\n type: \"object\",\n required: [\"method\"],\n properties: {\n query: {\n type: \"string\",\n description:\n \"Natural-language query. When set, composes search_hybrid + expand(hops=1, both). Mutually exclusive with seed_doc_ids.\",\n },\n seed_doc_ids: {\n type: \"array\",\n minItems: 1,\n items: { type: \"string\" },\n description:\n \"1+ opaque DocIds. When set, cluster() uses these seeds + their induced 1-hop neighborhood. Mutually exclusive with query.\",\n },\n vault: {\n type: \"string\",\n description:\n \"Vault name to scope the `query` search against (CR-02). Required on the `query` path when multiple vaults are configured; optional on single-vault setups. Ignored on the `seed_doc_ids` path (the vault is inferred from each DocId).\",\n },\n method: {\n type: \"string\",\n enum: [\"edge-community\"],\n description: \"Clustering algorithm. v2.0.0 supports only 'edge-community' (Louvain).\",\n },\n query_top_k: {\n type: \"integer\",\n minimum: 1,\n maximum: 200,\n default: 50,\n description:\n \"Only used in the query path: how many top hits to retrieve before expansion. Default 50.\",\n },\n force: {\n type: \"boolean\",\n default: false,\n description:\n \"Bypass the 5000-node hard cap. When false (default), oversized inputs return {ok:false, reason:'node_count_exceeded'}.\",\n },\n },\n },\n },\n // ── Phase 3 assembly tools (Plan 03-06) ──────────────────────────────────\n {\n name: \"assemble_dossier\",\n description:\n \"Resolve a {type, key} pair to an anchor document and walk its backlinks \" +\n \"into a structured dossier: { anchor (citation packet), linked_documents \" +\n \"(citation packets + relation), property_rollups (linked_count, linked_types, \" +\n \"status_distribution) }. Strict properties.type match (D-03). The key matches \" +\n \"the candidate's title OR any entry in properties.aliases (D-04). \" +\n 'v2.0.0 returns relation:\"wikilink\" on every linked_documents entry (the v1 ' +\n \"wikilinks table is the only edge source); Phase 4 (GRA-04) widens to typed edges. \" +\n \"Superseded backlinks are NOT filtered — dossiers show the whole picture (CONTEXT D-04).\",\n inputSchema: {\n type: \"object\",\n required: [\"type\", \"key\"],\n properties: {\n type: {\n type: \"string\",\n description:\n \"Exact-match value for properties.type on the anchor document \" +\n \"(e.g. 'Person', 'Project', 'Meeting'). No fuzzy / synonym matching.\",\n },\n key: {\n type: \"string\",\n description:\n \"Candidate key. Matches the document's title OR any entry in \" +\n \"properties.aliases (a string[] from frontmatter). Exact-string match.\",\n },\n vaults: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Restrict to these vault names; defaults to all configured.\",\n },\n },\n },\n },\n // ── Phase 6 task-contract DSL (Plan 06-02 / D-A1 escape valve) ───────────\n {\n name: \"register_contracts_as_tools\",\n description:\n \"Explicit-control escape valve (D-A1) — scans the per-vault contract \" +\n \"registry and updates the dynamic MCP tool list (registers new contracts \" +\n \"as vm_ tools, unregisters removed ones) regardless of the \" +\n \"[contracts.auto_register_tools] config gate. Always callable. \" +\n \"Returns a per-vault diff of {registered, unregistered}. Omit `vault` \" +\n \"to apply to every configured vault.\",\n inputSchema: {\n type: \"object\",\n properties: {\n vault: {\n type: \"string\",\n description: \"Vault name; omit to apply to all vaults.\",\n },\n },\n },\n },\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-05, Q-DESCRIBE) ──────────\n {\n name: \"describe_contract\",\n description:\n \"Return the input JSON Schema + an auto-generated markdown summary for a \" +\n \"contract (Q-DESCRIBE). Pure function — does not execute the contract. \" +\n \"Summary lists Inputs / Sources / Sinks / Assembly (numbered) / write_back / \" +\n \"Output Shape. Omit `vault` on single-vault setups; on multi-vault setups, \" +\n \"pass `vault` to disambiguate (returns `{ok:false, reason:'ambiguous_vault'}` \" +\n \"otherwise).\",\n inputSchema: {\n type: \"object\",\n required: [\"name\"],\n properties: {\n name: {\n type: \"string\",\n description: \"Registered contract name (see register_contracts_as_tools).\",\n },\n vault: {\n type: \"string\",\n description: \"Vault name; omit on single-vault setups.\",\n },\n },\n },\n },\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-06) ──────────────────────\n {\n name: \"instantiate_contract\",\n description:\n \"Execute a registered contract end-to-end. Zod-validates inputs against the \" +\n \"contract's inputZodSchema (additionalProperties:false rejects typos). \" +\n \"Resolves source/sink overrides per D-A4b default chain (explicit → config → \" +\n \"contract literal → error if required); sinks are MemorySink-only per D-A4c \" +\n \"(MEM-05 invariant un-bypassable). Runs each assembly step through verbDispatcher \" +\n \"with template resolution + named-binding accumulation. write_back routes through \" +\n \"DeliveryAdapter.write() (MEM-05 chokepoint). Returns the Q-OUTPUT bundle \" +\n \"{steps, write_back} on success OR a structured InstantiateError envelope \" +\n \"(12 sealed reasons per ADR-006 §Decision 7). Omit `vault` on single-vault \" +\n \"setups; multi-vault setups require it (returns `ambiguous_vault` otherwise).\",\n inputSchema: {\n type: \"object\",\n required: [\"name\"],\n properties: {\n name: {\n type: \"string\",\n description: \"Registered contract name.\",\n },\n inputs: {\n type: \"object\",\n additionalProperties: true,\n description: \"Contract inputs; validated against the contract's inputZodSchema.\",\n },\n source_overrides: {\n type: \"object\",\n additionalProperties: { type: \"string\" },\n description:\n \"Override declared source handles by handle name (e.g. {default_source: 'obsidian-fs://x'}).\",\n },\n sink_overrides: {\n type: \"object\",\n additionalProperties: { type: \"string\" },\n description:\n \"Override declared sink handles by handle name. Targets MUST resolve through MemorySinkRegistry (D-A4c).\",\n },\n vault: {\n type: \"string\",\n description: \"Vault name; omit on single-vault setups.\",\n },\n },\n },\n },\n] as const;\n\nexport type ToolName = (typeof TOOLS)[number][\"name\"];\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TOOL_SCHEMAS — Zod 4 raw shapes per tool (passed to McpServer.registerTool)\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport { z, type ZodRawShape } from \"zod\";\n\n/**\n * Canonical DocId pattern (mirrors `DOC_ID_PATTERN` in\n * `src/adapters/registry.ts`). Inlined here so the snapshot generator\n * (`evals/v1-baseline/dump-tools.mjs`) — which is a plain Node ESM\n * script that imports `.ts` via Node's native type-stripping — does not\n * need to traverse into `./adapters/`; Node cannot resolve the `.js`\n * extension of a sibling `.ts` file at runtime when only one of the\n * pair exists.\n *\n * Single-source-of-truth invariant: any change to the canonical regex\n * in `src/adapters/registry.ts` MUST be mirrored here (and vice\n * versa). The `tool-registry.test.ts > supersede schema` cases pin the\n * expected reject/accept behavior and will fail if the two patterns\n * drift.\n */\nconst DOC_ID_PATTERN = /^[a-z][a-z0-9-]*:\\/\\/[^/]+\\/.+$/;\n\n/** Reusable predicate shape for `query_frontmatter.where` values. */\nconst PredicateSchema: z.ZodType = z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.object({ $in: z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])) }),\n z.object({ $exists: z.boolean() }),\n z.object({ $contains: z.union([z.string(), z.number(), z.boolean(), z.null()]) }),\n]);\n\n/**\n * Per-tool Zod 4 raw shapes. Keys mirror TOOLS[].name; the shape is the\n * argument-object schema passed to `z.object({...})` (and to\n * `McpServer.registerTool({ inputSchema: shape })` per SDK 1.29).\n *\n * Tools with no input arguments declare `{}` (an empty raw shape — valid\n * per the SDK's `isZodRawShapeCompat` check).\n */\nexport const TOOL_SCHEMAS = {\n list_vaults: {},\n\n read_note: {\n vault: z.string(),\n path: z.string(),\n },\n\n search_semantic: {\n query: z.string().min(1),\n vaults: z.array(z.string()).optional(),\n top_k: z.number().int().positive().max(100).optional().default(10),\n exclude_paths: z.array(z.string()).optional(),\n },\n\n search_text: {\n query: z.string().min(1),\n vaults: z.array(z.string()).optional(),\n top_k: z.number().int().positive().max(100).optional().default(10),\n exclude_paths: z.array(z.string()).optional(),\n },\n\n search_hybrid: {\n query: z.string().min(1),\n vaults: z.array(z.string()).optional(),\n top_k: z.number().int().positive().max(100).optional().default(10),\n rrf_k: z.number().int().positive().max(1000).optional().default(60),\n exclude_paths: z.array(z.string()).optional(),\n rerank: z.boolean().optional().default(false),\n // Phase 3 / 03-05 additive params — D-07, D-08, ASM-07, ASM-08.\n // All `.optional()` with defaults that vanish when unset, so v1\n // callers see no behavior change.\n recency_weight: z.number().optional().default(0),\n authority_weight: z.number().optional().default(0),\n half_life_days: z.number().positive().optional().default(30),\n include_superseded: z.boolean().optional().default(false),\n // ── Phase 4 / 04-04 / GRA-03 (D-15): additive auto-expansion ──\n // Nested under a single optional `expand` object per D-15. When\n // omitted, hybridSearch behavior is byte-identical to v1 (the\n // guard `if (opts.expand && opts.expandDeps && ...)` at the end of\n // `src/search/hybrid.ts` short-circuits entirely). The literal-\n // union for `hops` enforces the D-05 hop cap at the boundary.\n expand: z\n .object({\n hops: z.union([z.literal(1), z.literal(2)]),\n direction: z.enum([\"forward\", \"backward\", \"both\"]).optional(),\n edge_types: z\n .array(z.enum([\"wikilink\", \"mention\", \"frontmatter-ref\", \"hyperlink\"]))\n .optional(),\n })\n .optional(),\n },\n\n list_backlinks: {\n vault: z.string(),\n path: z.string(),\n },\n\n list_forward_links: {\n vault: z.string(),\n path: z.string(),\n include_broken: z.boolean().optional().default(true),\n },\n\n find_broken_links: {\n vault: z.string(),\n },\n\n query_frontmatter: {\n vault: z.string(),\n where: z.record(z.string(), PredicateSchema),\n limit: z.number().int().positive().max(1000).optional().default(100),\n },\n\n write_note: {\n vault: z.string(),\n path: z.string(),\n content: z.string(),\n frontmatter: z.record(z.string(), z.unknown()).nullable().optional(),\n expected_hash: z.string().optional(),\n client_id: z.string().optional(),\n },\n\n update_frontmatter: {\n vault: z.string(),\n path: z.string(),\n merge: z.record(z.string(), z.unknown()),\n expected_hash: z.string().optional(),\n client_id: z.string().optional(),\n },\n\n delete_note: {\n vault: z.string(),\n path: z.string(),\n expected_hash: z.string(),\n client_id: z.string().optional(),\n },\n\n audit_log: {\n vault: z.string(),\n note_path: z.string().optional(),\n op: z.enum([\"create\", \"update\", \"delete\"]).optional(),\n since: z.number().int().nonnegative().optional(),\n limit: z.number().int().positive().max(1000).optional().default(50),\n // Plan 02-06 (MEM-08): additive optional filter. The MCP tool's\n // `description` string is INTENTIONALLY unchanged — Phase 1 byte-identity\n // is preserved. New capability is documented in docs/tools/audit_log.md.\n is_memory_sink_write: z.boolean().optional(),\n },\n\n list_models: {\n vault: z.string(),\n },\n\n start_shadow_index: {\n vault: z.string(),\n model: z.string().min(1),\n batch_size: z.number().int().positive().max(256).optional(),\n },\n\n switch_active_model: {\n vault: z.string(),\n model_name: z.string().min(1),\n },\n\n vacuum_embeddings: {\n vault: z.string(),\n },\n\n index_runs: {\n vault: z.string(),\n limit: z.number().int().positive().max(200).optional().default(20),\n },\n\n search: {\n query: z.string().min(1),\n limit: z.number().int().positive().max(50).optional().default(10),\n },\n\n fetch: {\n id: z.string().min(1),\n },\n\n vault_stats: {\n vault: z.string().optional(),\n },\n\n recent_notes: {\n vault: z.string().optional(),\n limit: z.number().int().positive().max(200).optional().default(20),\n since: z.number().int().nonnegative().optional(),\n },\n\n suggest_frontmatter: {\n vault: z.string(),\n path: z.string().optional(),\n content: z.string().optional(),\n title: z.string().optional(),\n folder_hint: z.string().optional(),\n },\n\n // ── Phase 2 memory tools (Plan 02-04) ───────────────────────────────────\n record_observation: {\n vault: z.string().min(1).describe(\"Vault name (registered in [vaults] config block)\"),\n claim: z\n .string()\n .min(1)\n .describe(\"Short natural-language statement of the observation (becomes title + body)\"),\n evidence: z\n .array(z.string())\n .describe(\"DocIds or quoted source spans supporting the claim; empty array allowed\"),\n confidence: z\n .enum([\"direct\", \"inferred\", \"uncertain\"])\n .describe(\"How the agent arrived at this claim\"),\n type: z\n .string()\n .min(1)\n .describe(\n \"Observation type per the sink contract (e.g. 'observation', 'hypothesis', 'decision')\",\n ),\n sink: z\n .string()\n .min(1)\n .optional()\n .describe(\n \"Memory sink name OR full obsidian-fs://… handle. Defaults to the vault's default sink.\",\n ),\n properties: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n \"Escape-hatch: contract-allowed extra properties; merged AFTER sugar args (caller wins)\",\n ),\n },\n\n supersede: {\n doc_id: z.string().regex(DOC_ID_PATTERN).describe(\"DocId of the document being superseded\"),\n replacement_doc_id: z\n .string()\n .regex(DOC_ID_PATTERN)\n .describe(\"DocId of the replacement document\"),\n reason: z\n .string()\n .min(1)\n .describe(\"Why the old document is being retired; written to superseded_reason\"),\n },\n\n // ── Phase 5 brief tools (Plan 05-02 / BRF-03, BRF-04) ───────────────────\n compile_brief: {\n vault: z.string().min(1).describe(\"Vault name (registered in [vaults] config block)\"),\n target: z\n .string()\n .min(1)\n .describe(\"Stable cross-version handle for the brief (e.g. 'atlas-q3')\"),\n source_doc_ids: z\n .array(z.string().regex(DOC_ID_PATTERN))\n .min(1)\n .max(50)\n .describe(\"DocIds the brief is compiled from; deduped, capped at 50 (D-03)\"),\n purpose: z\n .string()\n .min(1)\n .max(500)\n .describe(\"Free-form purpose; bounded so list_briefs stays scannable\"),\n max_tokens: z\n .number()\n .int()\n .positive()\n .optional()\n .default(2000)\n .describe(\"Hint for the LLM ladder; default 2000\"),\n prepared_text: z\n .string()\n .min(1)\n .optional()\n .describe(\"D-10 tier 3 fallback when no LLM is reachable — verbatim body to stitch in\"),\n sink: z.string().min(1).optional().describe(\"Override the default `_memory/_briefs` sink\"),\n },\n\n get_brief: {\n vault: z.string().min(1).describe(\"Vault name (registered in [vaults] config block)\"),\n target: z.string().min(1).describe(\"Stable cross-version handle for the brief\"),\n max_age_days: z\n .number()\n .int()\n .nonnegative()\n .optional()\n .describe(\"Reject briefs older than this many days unless allow_stale=true\"),\n allow_stale: z\n .boolean()\n .optional()\n .default(false)\n .describe(\n \"When true, return briefs flagged stale or too_old with annotation rather than null\",\n ),\n },\n\n // ── Phase 3 assembly tools (Plan 03-02 / ASM-02) ────────────────────────\n get_outline: {\n doc_id: z\n .string()\n .regex(DOC_ID_PATTERN)\n .describe(\"Opaque DocId (obsidian-fs:///) of the document\"),\n vaults: z\n .array(z.string().min(1))\n .optional()\n .describe(\"Optional vault filter; usually omitted (the DocId names a vault)\"),\n },\n\n // ── Phase 3 assembly tools (Plan 03-03) ─────────────────────────────────\n search_sections: {\n query: z.string().min(1),\n limit: z.number().int().positive().max(50).optional().default(10),\n vaults: z.array(z.string().min(1)).optional(),\n // Forward-compat with slice 03-05's authority/staleness rescore.\n // Accepted today; ignored by the controller until 03-05 wires the\n // forwarding inside hybridSearch. See 03-03-DEVIATIONS.md.\n recency_weight: z.number().min(0).optional().default(0),\n authority_weight: z.number().min(0).optional().default(0),\n include_superseded: z.boolean().optional().default(false),\n },\n\n // ── Phase 2 memory tools (Plan 02-05) ───────────────────────────────────\n recall: {\n query: z\n .string()\n .min(1)\n .describe(\"Natural-language query; routes through hybrid (semantic + BM25) search\"),\n min_confidence: z\n .enum([\"direct\", \"inferred\", \"uncertain\"])\n .optional()\n .describe(\n \"Exclude docs whose confidence ordinal is lower than this (direct=3, inferred=2, uncertain=1)\",\n ),\n types: z\n .array(z.string().min(1))\n .optional()\n .describe(\"Restrict to docs whose `type` property is in this set\"),\n max_age_days: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\"Exclude docs whose `observed_at` is older than this many days\"),\n sink: z\n .string()\n .min(1)\n .optional()\n .describe(\n \"Memory sink name OR full obsidian-fs://… handle. Defaults to all configured sinks.\",\n ),\n limit: z\n .number()\n .int()\n .positive()\n .max(200)\n .optional()\n .describe(\"Maximum results AFTER filter+sort; default 20\"),\n vaults: z\n .array(z.string().min(1))\n .optional()\n .describe(\"Restrict to these vault names; defaults to all configured\"),\n },\n\n // ── Phase 3 assembly tools (Plan 03-04 / ASM-01) ────────────────────────\n get_document_bundle: {\n doc_id: z\n .string()\n .regex(DOC_ID_PATTERN)\n .describe(\"Opaque DocId (obsidian-fs:///) of the anchor document\"),\n // v2.0.0 accepts only depth:1. The literal pin guarantees Zod\n // rejects any other value at the boundary so the controller does\n // not need to clamp. Phase 4 may widen additively (z.union of\n // literals, or `z.number().int().min(1).max(2)`).\n depth: z\n .literal(1)\n .optional()\n .default(1)\n .describe(\"Link-walk depth. v2.0.0: only 1 (one-hop). Phase 4 may widen.\"),\n vaults: z\n .array(z.string().min(1))\n .optional()\n .describe(\"Optional vault filter; usually omitted (the DocId names a vault)\"),\n },\n\n // ── Phase 4 graph tools (Plan 04-03 / GRA-01) ───────────────────────────\n expand: {\n seed_doc_ids: z\n .array(z.string().regex(DOC_ID_PATTERN))\n .min(1)\n .describe(\"1+ opaque DocIds (e.g. obsidian-fs:///) — seeds of the BFS.\"),\n // Hops hard-capped at 2 (D-05) via Zod literal union — `hops: 3`\n // is rejected at the boundary; the controller does not clamp.\n hops: z\n .union([z.literal(1), z.literal(2)])\n .describe(\"Hop cap (1 or 2). v2.0.0 hard-caps at 2.\"),\n direction: z\n .enum([\"forward\", \"backward\", \"both\"])\n .optional()\n .default(\"both\")\n .describe(\"Edge traversal direction; default 'both'.\"),\n edge_types: z\n .array(z.enum([\"wikilink\", \"mention\", \"frontmatter-ref\", \"hyperlink\"]))\n .optional()\n .describe(\"Optional filter on edge types; default = all four types.\"),\n filter_properties: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"Strict-equality predicate on document properties (e.g. {type: 'Project'}).\"),\n include_superseded: z\n .boolean()\n .optional()\n .default(false)\n .describe(\"When false (default), docs whose properties.status === 'superseded' are dropped.\"),\n },\n\n // ── Phase 4 graph tools (Plan 04-05 / GRA-02) ───────────────────────────\n //\n // The cluster tool's schema is unusual: it requires EXACTLY ONE of\n // `query` or `seed_doc_ids` (mutual exclusion per D-15a). Zod can't\n // model \"exactly one\" in a raw shape directly — we declare the union\n // of both shapes in `SCHEMA_BUILDERS` below; here we publish the raw\n // shape so the MCP SDK's `tools/list` JSON Schema projection still\n // works. The runtime path goes through `buildToolSchema(\"cluster\")`\n // which calls the SCHEMA_BUILDERS entry.\n cluster: {\n query: z.string().min(1).optional(),\n seed_doc_ids: z.array(z.string().regex(DOC_ID_PATTERN)).min(1).optional(),\n // CR-02: `vault` scopes the `query` path on multi-vault setups so\n // search_hybrid is not silently restricted to whichever vault\n // sorts first in VaultManager insertion order. Optional at the\n // schema layer; the runtime cluster() entry enforces the\n // multi-vault-without-vault error.\n vault: z.string().min(1).optional(),\n method: z.literal(\"edge-community\"),\n query_top_k: z.number().int().positive().max(200).optional().default(50),\n force: z.boolean().optional().default(false),\n },\n\n // ── Phase 3 assembly tools (Plan 03-06) ─────────────────────────────────\n assemble_dossier: {\n type: z\n .string()\n .min(1)\n .describe(\n \"Exact-match value for properties.type on the anchor document (D-03 — no fuzzy match)\",\n ),\n key: z\n .string()\n .min(1)\n .describe(\n \"Candidate key — matches the document's title OR any entry in properties.aliases (D-04)\",\n ),\n vaults: z\n .array(z.string().min(1))\n .optional()\n .describe(\"Restrict to these vault names; defaults to all configured\"),\n },\n\n // ── Phase 6 task-contract DSL (Plan 06-02 / D-A1 escape valve) ─────────\n register_contracts_as_tools: {\n vault: z.string().min(1).optional().describe(\"Vault name; omit to apply to all vaults\"),\n },\n\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-05, Q-DESCRIBE) ────────\n describe_contract: {\n name: z.string().min(1).describe(\"Registered contract name (see register_contracts_as_tools)\"),\n vault: z.string().min(1).optional().describe(\"Vault name; omit on single-vault setups\"),\n },\n\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-06) ────────────────────\n instantiate_contract: {\n name: z.string().min(1).describe(\"Registered contract name\"),\n inputs: z\n .record(z.string(), z.unknown())\n .optional()\n .default({})\n .describe(\"Contract inputs; validated against the contract's inputZodSchema\"),\n source_overrides: z\n .record(z.string(), z.string())\n .optional()\n .describe(\"Override declared source handles by handle name\"),\n sink_overrides: z\n .record(z.string(), z.string())\n .optional()\n .describe(\n \"Override declared sink handles by handle name. Targets MUST resolve through MemorySinkRegistry (D-A4c).\",\n ),\n vault: z.string().min(1).optional().describe(\"Vault name; omit on single-vault setups\"),\n },\n} as const satisfies Record;\n\n/**\n * Build a `z.object({...})` from a tool's raw shape. The\n * `suggest_frontmatter` tool layers an additional cross-field refinement\n * (path OR content required) — handled by the schema-builder map below.\n */\nconst SCHEMA_BUILDERS: Partial z.ZodTypeAny>> = {\n suggest_frontmatter: () =>\n z\n .object(TOOL_SCHEMAS.suggest_frontmatter)\n .refine((v) => v.path !== undefined || v.content !== undefined, {\n message: \"suggest_frontmatter requires either `path` or `content`\",\n }),\n // Plan 04-05 / D-15a — EXACTLY ONE of `query` or `seed_doc_ids` must\n // be present. The runtime path also returns a structured\n // {ok:false, reason:'both_seeds_and_query'} error when both are set,\n // so this Zod refinement is the early-rejection gate at the MCP\n // boundary (cluster's internal validator handles the same case for\n // direct callers that bypass Zod).\n cluster: () =>\n z\n .object(TOOL_SCHEMAS.cluster)\n .refine(\n (v) =>\n (v.query !== undefined && v.seed_doc_ids === undefined) ||\n (v.query === undefined && v.seed_doc_ids !== undefined),\n {\n message:\n \"cluster requires EXACTLY ONE of `query` or `seed_doc_ids` (D-15a mutual exclusion)\",\n },\n ),\n};\n\n/**\n * Materialize the full Zod schema for a tool — wraps the raw shape in\n * `z.object({...})` and layers any tool-specific refinements. Called at\n * handler time inside `server.registerTool` for input validation.\n */\nexport function buildToolSchema(name: ToolName): z.ZodTypeAny {\n const builder = SCHEMA_BUILDERS[name];\n if (builder) return builder();\n return z.object(TOOL_SCHEMAS[name] as ZodRawShape);\n}\n","/**\n * Foundation types for Phase 6 task contracts (ADR-006).\n *\n * Pure type module — zero runtime imports. Plan 06-02/03/04 build on\n * these types; the loader/instantiator/describer land in later slices.\n *\n * Naming convention (CLAUDE.md): PascalCase types, snake_case YAML keys\n * (stored verbatim), `step_alias` is the YAML form; `stepAlias` is the\n * camelCase form at row boundary in `ContractAuditRow`.\n */\n\nimport type { z } from \"zod\";\n\n/**\n * Closed assembly-verb set (ADR-006 §Decision 2 / D-A2a / C-1).\n *\n * 11 baseline verbs + `\"literal\"` escape + `mcp:///` peer.\n *\n * No write verbs in the set — writes happen exclusively via the\n * structurally-separate `write_back:` block. Promoting a peer-MCP verb\n * into the baseline enum is a v2.x decision driven by `aggregateVerbUsage`\n * data (D-A2b).\n */\nexport type AssemblyVerb =\n | \"search_hybrid\"\n | \"expand\"\n | \"cluster\"\n | \"recall\"\n | \"compile_brief\"\n | \"get_brief\"\n | \"query_frontmatter\"\n | \"list_backlinks\"\n | \"get_outline\"\n | \"search_sections\"\n | \"read_note\"\n | \"literal\"\n | `mcp://${string}/${string}`;\n\n/**\n * Path-matcher constant for the loader scan + ChangeFeed dispatch\n * (Pitfall F3 — non-recursive; `_contracts/memory/*.yaml` belongs to\n * the Phase 2 MemoryContract loader).\n */\nexport const CONTRACT_PATH_REGEX = /^_contracts\\/[^/]+\\.yaml$/;\n\n/**\n * A single step in an `assembly:` array. `as:` is the alias under which\n * the step's output is bound in the template environment.\n *\n * `value:` is populated ONLY when `verb === \"literal\"` (escape hatch\n * for hard-coded fixtures).\n */\nexport interface ContractStep {\n as: string;\n verb: AssemblyVerb;\n args?: Record;\n value?: unknown;\n}\n\n/**\n * Contract-declared source / sink handle entry.\n *\n * `handle` is the literal URI shown in the YAML (e.g.\n * `\"obsidian-fs://my-vault\"`). `required` defaults to true; when false,\n * a missing override + missing default is not an error.\n */\nexport interface ContractHandleDecl {\n handle: string;\n required: boolean;\n}\n\n/** Backwards-compat alias — sources and sinks share the same shape today. */\nexport type ContractSourceDecl = ContractHandleDecl;\nexport type ContractSinkDecl = ContractHandleDecl;\n\n/**\n * Write-back spec — the chokepoint that produces a real DocId via\n * DeliveryAdapter.write (Invariant C-3, Pitfall F6).\n */\nexport interface WriteBackSpec {\n /** Sink handle (template expression or literal). */\n sink: string;\n document_kind: \"brief\" | \"observation\" | \"custom\";\n properties: Record;\n /** Template expression that resolves to the body string. */\n body_from: string;\n}\n\n/** YAML inputs flat form: `{ : }`. */\nexport type ContractInputs = Record;\n\n/**\n * Parsed-and-validated contract — registry entry shape.\n *\n * Caches the built input schema so `describe_contract` and\n * `instantiate_contract` skip the buildInputSchema round-trip.\n */\nexport interface ParsedContract {\n version: 1;\n name: string;\n description: string;\n inputs: ContractInputs;\n required: string[];\n sources: Record;\n sinks: Record;\n assembly: ContractStep[];\n output_shape?: object;\n write_back?: WriteBackSpec;\n /** Built once at load time — `z.fromJSONSchema(inputJsonSchema)`. */\n inputZodSchema: z.ZodObject;\n /** Built once at load time — passed verbatim to MCP `tools/list`. */\n inputJsonSchema: object;\n}\n\n/** Caller-supplied override map keyed by handle name (not URI scheme). */\nexport type OverrideMap = Record;\n\n/**\n * Closed error envelope (ADR-006 §Decision 7 + Q-OUTPUT + WARNING-6).\n *\n * 12 reasons, sealed for v2.0.0. The first 11 are orchestrator-level;\n * `ambiguous_vault` is server-dispatch-level (caller omitted `vault` and\n * multiple vaults are configured — surfaced in the same closed union to\n * keep callers parsing one discriminated type).\n */\nexport type InstantiateError =\n | { ok: false; reason: \"unknown_contract\"; name: string }\n | { ok: false; reason: \"invalid_inputs\"; issues: unknown }\n | {\n ok: false;\n reason: \"unknown_override_handle\";\n handle: string;\n valid_handles: string[];\n }\n | { ok: false; reason: \"missing_required_source\"; handle: string; hint: string }\n | {\n ok: false;\n reason: \"sink_override_not_a_memory_sink\";\n target: string;\n hint: string;\n }\n | { ok: false; reason: \"unresolved_template\"; expression: string }\n | { ok: false; reason: \"verb_not_available\"; verb: string }\n | {\n ok: false;\n reason: \"mcp_client_unavailable\";\n verb: string;\n client_name: string;\n }\n | { ok: false; reason: \"assembly_step_failed\"; step_alias: string; cause: string }\n | { ok: false; reason: \"write_back_failed\"; cause: string }\n | { ok: false; reason: \"validation_failed_on_output_shape\"; issues: unknown }\n | { ok: false; reason: \"ambiguous_vault\"; available_vaults: string[] };\n\n/**\n * Re-export of the DB row type for convenience — Plan 06-02/03 import\n * this name (NOT from `src/db/queries/contract-audit.js`) so the\n * dependency graph stays cleanly directed (contracts → db).\n */\nexport interface ContractAuditRow {\n kind: \"contract_step\" | \"contract_load_error\";\n contract?: string;\n verb?: string;\n stepAlias?: string;\n vault?: string;\n ts: number;\n errorMessage?: string;\n}\n","/**\n * TYPES_CATALOG — Phase 6 / D-A3b, ADR-006 §Decision 6.\n *\n * Resolves `$ref: \"#/types/\"` in contract YAML input schemas.\n *\n * Additive evolution only:\n * - Phase 10 may extend `DocId.pattern` to also match `notion://...`\n * (additive). We MUST NEVER narrow.\n * - Adding a NEW type entry (e.g. `Workspace`) is allowed at minor\n * version bumps.\n *\n * `Object.freeze` enforces the additive-only contract structurally —\n * direct mutation throws in strict ESM.\n *\n * Adapter-seam discipline: zero `fs`/`path.join`/`gray-matter`/`chokidar`\n * imports. Pure data module.\n */\n\nexport const TYPES_CATALOG: Readonly> = Object.freeze({\n DocId: Object.freeze({\n type: \"string\",\n pattern: \"^[a-z][a-z0-9-]*://\",\n description: \"Opaque document identifier per ADR-001 (URI-style)\",\n }),\n Handle: Object.freeze({\n type: \"string\",\n pattern: \"^[a-z][a-z0-9-]*://\",\n description:\n \"Source or sink handle (currently identical to DocId; future-proofed for divergence)\",\n }),\n ChunkId: Object.freeze({\n type: \"string\",\n pattern: \"^[a-z][a-z0-9-]*://.+#chunk-[0-9a-f]{7}$\",\n description: \"Content-stable chunk identifier per Phase 5 ADR-005 H-5\",\n }),\n MemorySink: Object.freeze({\n type: \"string\",\n description: \"Registered MemorySink handle (see list_sinks)\",\n \"x-validator\": \"memory-sink\",\n }),\n});\n","/**\n * resolveRefs — Phase 6 / D-A3a, ADR-006 §Decision 6, T-06-01-01 gate.\n *\n * Resolves `$ref: \"#/types/\"` nodes against TYPES_CATALOG. Any\n * other `$ref` form (HTTP URL, file://, JSON-Pointer beyond `#/types/`)\n * throws synchronously — Security: no HTTP fetches, no FS reads from\n * contract YAML.\n *\n * Spread order (RESEARCH Example 3): catalog entry first, YAML-author\n * additions on the same node second — author additions WIN. This lets\n * a contract override the catalog description without weakening the\n * pattern/type constraints (those are spread first; redundant author\n * `type`/`pattern` simply re-state them).\n *\n * Adapter-seam discipline: zero `fs`/`path.join`/`gray-matter`/`chokidar`\n * imports. Pure function.\n */\n\nimport { TYPES_CATALOG } from \"./types-catalog.js\";\n\nconst TYPES_REF_RE = /^#\\/types\\/(\\w+)$/;\n\nexport function resolveRefs(schema: unknown): unknown {\n if (Array.isArray(schema)) return schema.map(resolveRefs);\n if (schema !== null && typeof schema === \"object\") {\n const obj = schema as Record;\n if (typeof obj[\"$ref\"] === \"string\") {\n const ref = obj[\"$ref\"];\n const match = ref.match(TYPES_REF_RE);\n if (!match) {\n throw new Error(`Unsupported $ref form (only '#/types/' accepted): ${ref}`);\n }\n const typeName = match[1]!;\n const catalogEntry = (TYPES_CATALOG as Record)[typeName];\n if (catalogEntry === undefined) {\n throw new Error(`Unknown $ref target: ${ref}`);\n }\n // Strip $ref before merging — author additions win (Example 3).\n // Use destructuring to keep TS strict-mode happy.\n const rest: Record = {};\n for (const [k, v] of Object.entries(obj)) {\n if (k === \"$ref\") continue;\n rest[k] = resolveRefs(v);\n }\n return { ...(catalogEntry as Record), ...rest };\n }\n const out: Record = {};\n for (const [k, v] of Object.entries(obj)) {\n out[k] = resolveRefs(v);\n }\n return out;\n }\n return schema;\n}\n","/**\n * buildInputSchema — Phase 6 / D-A3a, ADR-006 §Decision 6.\n *\n * Wraps the YAML author's flat `inputs:` form into the canonical\n * `{type:'object', properties, required, additionalProperties: false}`\n * envelope, resolves `$ref` against TYPES_CATALOG, then produces a\n * `ZodObject` via `z.fromJSONSchema`.\n *\n * Pitfall F1: SDK 1.29 `registerTool({inputSchema})` REJECTS raw JSON\n * Schema literals — the inputSchema must be a real Zod schema.\n * `z.fromJSONSchema` is the chokepoint that converts the JSON shape\n * into a Zod schema the SDK accepts.\n *\n * Pitfall F2: `z.fromJSONSchema` honors `additionalProperties` from the\n * input. WITHOUT explicit `additionalProperties: false`, typo'd input\n * keys are silently dropped at runtime. The wrapper sets this\n * explicitly — verified by Test 11.\n *\n * Assumption A3 (verified by Test 12): the `\"x-validator\": \"memory-sink\"`\n * extension keyword passes through `z.fromJSONSchema` unchanged. The\n * memory-sink validation happens at instantiation time (Plan 06-03)\n * by inspecting the `jsonSchema.properties.*[\"x-validator\"]` field —\n * NOT the Zod schema.\n *\n * Adapter-seam discipline: only `zod` is imported. Zero `fs`/`path.join`/\n * `gray-matter`/`chokidar`/`yaml`.\n */\n\nimport { z } from \"zod\";\nimport { resolveRefs } from \"./json-schema-ref.js\";\n\nexport interface BuiltInputSchema {\n zodSchema: z.ZodObject;\n jsonSchema: {\n type: \"object\";\n properties: Record;\n required: string[];\n additionalProperties: false;\n };\n}\n\nexport function buildInputSchema(\n yamlInputs: Record,\n required: string[] = [],\n): BuiltInputSchema {\n const resolvedProperties = resolveRefs(yamlInputs) as Record;\n const jsonSchema = {\n type: \"object\" as const,\n properties: resolvedProperties,\n required,\n additionalProperties: false as const,\n };\n // Pitfall F1: fromJSONSchema produces a ZodObject the SDK accepts.\n // Cast is safe — we always pass an `object`-typed JSON Schema in.\n // Zod's `fromJSONSchema` accepts `JSONSchema` whose property bag is\n // typed as `Record` — our `unknown` map is\n // structurally compatible at runtime (the contract YAML is JSON\n // Schema by construction), but TypeScript needs an explicit cast.\n const zodSchema = z.fromJSONSchema(\n jsonSchema as unknown as Parameters[0],\n ) as z.ZodObject;\n return { zodSchema, jsonSchema };\n}\n","/**\n * ContractRegistry — Phase 6 / D-A1c, ADR-006 §Decision 1, Invariant C-4.\n *\n * In-memory `Map` wrapper with first-wins\n * collision policy. A second `set(name, ...)` with the same name does\n * NOT replace the original; it returns a structured failure result so\n * the caller can record `contract_audit kind: 'contract_load_error'`.\n *\n * Caller writes the audit row (`src/contracts/audit.ts` —\n * `recordContractLoadError`); the registry stays free of DB imports.\n *\n * Adapter-seam discipline: zero `fs`/`path.join`/`gray-matter`/`chokidar`\n * imports. Pure in-memory data structure.\n */\n\nimport type { ParsedContract } from \"./types.js\";\n\nexport type RegistrySetResult = { ok: true } | { ok: false; reason: \"duplicate_name\" };\n\nexport class ContractRegistry {\n private readonly contracts = new Map();\n\n get size(): number {\n return this.contracts.size;\n }\n\n get(name: string): ParsedContract | undefined {\n return this.contracts.get(name);\n }\n\n /** D-A1c first-wins. Returns `{ok:false, reason:\"duplicate_name\"}` if `name` is already registered. */\n set(name: string, contract: ParsedContract): RegistrySetResult {\n if (this.contracts.has(name)) {\n return { ok: false, reason: \"duplicate_name\" };\n }\n this.contracts.set(name, contract);\n return { ok: true };\n }\n\n delete(name: string): boolean {\n return this.contracts.delete(name);\n }\n\n entries(): IterableIterator<[string, ParsedContract]> {\n return this.contracts.entries();\n }\n\n names(): string[] {\n return Array.from(this.contracts.keys());\n }\n}\n","/**\n * slugify — Phase 6 / D-A1c, ADR-006 §Decision 1.\n *\n * Converts a kebab-case contract name into a snake_case MCP tool name\n * with the configured `tool_prefix` prepended. Zero deps (RESEARCH\n * Anti-Patterns — no `lodash`, no `change-case`).\n *\n * Examples:\n * slugify(\"meeting-prep\", \"vm_\") → \"vm_meeting_prep\"\n * slugify(\"project-status\", \"\") → \"project_status\" (caller's\n * responsibility to enforce A7 .min(1))\n *\n * First-wins on collision is the registry's job (`ContractRegistry.set`).\n *\n * Adapter-seam discipline: pure function, no imports, no I/O.\n */\nexport function slugify(name: string, prefix: string): string {\n return prefix + name.replace(/-/g, \"_\");\n}\n","/**\n * Contract-audit writers — Phase 6 / Q-AUD, ADR-006 §Decision 4,\n * Invariant C-5.\n *\n * Security pattern (mitigates T-06-01-03 — Information Disclosure):\n * Function signatures explicitly EXCLUDE any `output` / `payload`\n * field. TypeScript strict-mode rejects a call site that attempts to\n * add one. Peer-MCP step outputs may contain sensitive data (private\n * PR text, customer records, secret tokens); we never capture them.\n *\n * `recordContractStep` / `recordContractLoadError` use ONLY the\n * `vault.db.contractAudit` namespace — they do NOT touch any other DB\n * table. `aggregateVerbUsage` re-exports the underlying query for\n * Plan 06-04's resource handler.\n *\n * Adapter-seam discipline: only `./types.js` + the typed query interface\n * are imported. Zero `fs`/`path.join`/`gray-matter`/`chokidar`.\n */\n\nimport type { ContractAuditQueries } from \"../db/queries/contract-audit.js\";\n\nexport interface ContractAuditDeps {\n contractAudit: ContractAuditQueries;\n}\n\nexport interface RecordContractStepArgs {\n contract: string;\n verb: string;\n step_alias: string;\n vault: string;\n}\n\nexport interface RecordContractLoadErrorArgs {\n file: string;\n error_message: string;\n vault: string;\n}\n\nexport interface VerbUsageRow {\n verb: string;\n invocation_count: number;\n last_seen: number;\n}\n\n/**\n * Write one `contract_audit kind: 'contract_step'` row. NEVER accepts an\n * output / payload field (C-5).\n */\nexport function recordContractStep(deps: ContractAuditDeps, args: RecordContractStepArgs): void {\n deps.contractAudit.insert({\n kind: \"contract_step\",\n contract: args.contract,\n verb: args.verb,\n stepAlias: args.step_alias,\n vault: args.vault,\n ts: Date.now(),\n });\n}\n\n/**\n * Write one `contract_audit kind: 'contract_load_error'` row. The `file`\n * is prefixed onto `error_message` for human-readable surfacing through\n * `list_contract_load_errors` (Plan 06-04 Resource).\n */\nexport function recordContractLoadError(\n deps: ContractAuditDeps,\n args: RecordContractLoadErrorArgs,\n): void {\n deps.contractAudit.insert({\n kind: \"contract_load_error\",\n vault: args.vault,\n ts: Date.now(),\n errorMessage: `${args.file}: ${args.error_message}`,\n });\n}\n\n/**\n * D-A2b promotion signal — verb usage histogram across `contract_step`\n * rows in this vault. Plan 06-04's resource handler pipes this through\n * `vault-memory://contract-verbs/{vault}`.\n */\nexport function aggregateVerbUsage(deps: ContractAuditDeps, vault: string): VerbUsageRow[] {\n return deps.contractAudit.aggregateVerbUsage(vault);\n}\n","/**\n * ContractFileSchema — Phase 6 / CON-01, ADR-006 §Decision 2.\n *\n * Zod schema for the YAML contract file shape. Plan 06-02's loader will\n * call `parseDocument(yamlText).toJS()` and feed the result here.\n *\n * Invariants enforced structurally:\n * - C-1: closed `assembly[].verb` set (11 baseline + literal + mcp://).\n * No write verbs in the enum — writes happen exclusively via\n * the structurally-separate `write_back:` block.\n * - Step aliases are unique across the assembly array (superRefine).\n * - `version: 1` is the only supported version in v2.0.0 (additive\n * evolution lands as `z.union([z.literal(1), z.literal(2)])` later).\n *\n * Authoring style mirrors `src/memory/contract/default-v1.ts`:\n * `.describe()` on every public field, `.superRefine` for cross-field\n * invariants.\n *\n * Adapter-seam discipline: only `zod`. Zero `fs`/`path.join`/`gray-matter`/\n * `chokidar`/`yaml`.\n */\n\nimport { z } from \"zod\";\n\nconst BASELINE_VERBS = [\n \"search_hybrid\",\n \"expand\",\n \"cluster\",\n \"recall\",\n \"compile_brief\",\n \"get_brief\",\n \"query_frontmatter\",\n \"list_backlinks\",\n \"get_outline\",\n \"search_sections\",\n \"read_note\",\n] as const;\n\nconst MCP_VERB_RE = /^mcp:\\/\\/[a-z][a-z0-9_-]*\\/[a-z][a-z0-9_-]*$/;\n\n/**\n * Verb schema = closed enum (baseline + literal) OR mcp:// peer pattern.\n * Anything else — including any v1 write tool name — fails validation.\n */\nconst VerbSchema = z.union([z.enum([...BASELINE_VERBS, \"literal\"]), z.string().regex(MCP_VERB_RE)]);\n\nconst StepSchema = z\n .object({\n as: z\n .string()\n .min(1)\n .regex(/^[a-z_][a-z0-9_]*$/, \"alias must be snake_case\")\n .describe(\"D-A2c — unique snake_case alias for this step's output\"),\n verb: VerbSchema.describe(\"Closed enum + literal + mcp:// extension (D-A2a / C-1)\"),\n args: z.record(z.string(), z.unknown()).optional(),\n value: z.unknown().optional(),\n })\n .describe(\"One step in an assembly: array\");\n\nconst HandleDeclSchema = z\n .object({\n handle: z.string().min(1),\n required: z.boolean().default(true),\n })\n .describe(\"Source or sink handle declaration (D-A4a)\");\n\nconst WriteBackSchema = z\n .object({\n sink: z.string().min(1).describe(\"Template expression OR literal sink handle\"),\n document_kind: z.enum([\"brief\", \"observation\", \"custom\"]),\n properties: z.record(z.string(), z.unknown()).default({}),\n body_from: z.string().min(1).describe(\"Template expression that resolves to the body string\"),\n })\n .describe(\"DeliveryAdapter.write chokepoint — only ground-truth DocId source (C-3)\");\n\nexport const ContractFileSchema = z\n .object({\n version: z.literal(1).describe(\"v2.0.0 supports version 1 only; v2.x may extend additively\"),\n name: z\n .string()\n .min(1)\n .regex(/^[a-z][a-z0-9-]*$/, \"name must be kebab-case\")\n .describe(\"Contract name — used by instantiate_contract and slugify\"),\n description: z.string().default(\"\"),\n inputs: z.record(z.string(), z.unknown()).default({}),\n required: z.array(z.string()).default([]),\n sources: z.record(z.string(), HandleDeclSchema).default({}),\n sinks: z.record(z.string(), HandleDeclSchema).default({}),\n assembly: z.array(StepSchema).min(1, \"assembly must contain at least one step\"),\n output_shape: z.unknown().optional(),\n write_back: WriteBackSchema.optional(),\n })\n .superRefine((data, ctx) => {\n // D-A2c: every step alias is unique across the assembly array.\n const aliases = new Set();\n for (const step of data.assembly) {\n if (aliases.has(step.as)) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"assembly\"],\n message: `duplicate step alias '${step.as}'`,\n });\n }\n aliases.add(step.as);\n }\n });\n\nexport type ContractFileShape = z.infer;\n","/**\n * startContractRegistry — Phase 6 / D-LOAD, ADR-006 §Decision 7.\n *\n * Boot scan + ChangeFeed subscriber for `_contracts/.yaml` files\n * (Pitfall F3 — non-recursive; `_contracts/memory/*.yaml` belongs to the\n * Phase 2 MemoryContract loader). On each event:\n * - parse via `yaml@2.9 parseDocument(text).toJS()` (preserves comments\n * on a later round-trip per CON-01);\n * - Zod-validate via `ContractFileSchema`;\n * - resolve `$ref` via `resolveRefs`;\n * - build the cached input schema via `buildInputSchema`;\n * - register via `ContractRegistry.set(name, parsed)` (first-wins per\n * D-A1c — duplicate-name writes a `contract_load_error` audit row).\n *\n * Parse failures during a hot-reload event do NOT mutate the registry\n * (graceful degradation per D-LOAD): the prior version stays in place\n * and a `contract_load_error` audit row records the diagnostic.\n *\n * # Adapter-seam discipline\n *\n * Zero `fs` / `path.join` / `gray-matter` / `chokidar` imports. The loader\n * reads vault content exclusively through `SourceConnector.readDocument`\n * and `SourceConnector.listDocuments`; ChangeEvents arrive through the\n * `ChangeFeed.subscribe` seam. `yaml`'s `parseDocument` operates on text\n * already read by the source, not the filesystem.\n *\n * # Production end-to-end coverage (forward note)\n *\n * The existing Phase-1 `ObsidianFsSource` + `ObsidianFsChangeFeed` only\n * enumerate / watch `.md` files (see `scanner.ts:47` and `change-feed.ts:191`).\n * Until those adapters are widened to also surface `_contracts/*.yaml`,\n * the loader's boot scan + hot-reload paths only fire under tests (which\n * supply YAML-aware stubs). Server bootstrap wires the loader through\n * the existing seams so the registry, the audit table, and the\n * `register_contracts_as_tools` tool surface land in v2.0.0; widening\n * obsidian-fs to enumerate contract YAML is a follow-up tracked under\n * Phase 6 wave-4 (Plan 06-04). This file is the seam, not the surface.\n */\n\nimport { parseDocument } from \"yaml\";\nimport {\n CONTRACT_PATH_REGEX,\n type ParsedContract,\n type ContractInputs,\n type ContractStep,\n type ContractSourceDecl,\n type ContractSinkDecl,\n type WriteBackSpec,\n} from \"./types.js\";\nimport { ContractFileSchema, type ContractFileShape } from \"./schema.js\";\nimport { buildInputSchema } from \"./input-schema.js\";\nimport { resolveRefs } from \"./json-schema-ref.js\";\nimport { ContractRegistry } from \"./registry.js\";\nimport { recordContractLoadError, type ContractAuditDeps } from \"./audit.js\";\nimport { decomposeDocId } from \"../adapters/registry.js\";\nimport { sha256 } from \"../adapters/source/obsidian-fs/hash.js\";\nimport type { SuppressionSet } from \"../adapters/change-feed/obsidian-fs/suppression.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport type { ChangeFeed, ChangeEvent, Disposable } from \"../adapters/change-feed/types.js\";\nimport type { DocId, Document } from \"../types.js\";\n\n/**\n * Discriminator for the `onRegistryChange` callback (test hook). Boot\n * scan fires `\"boot\"` once after the scan completes; ChangeFeed events\n * fire `\"create\"` | `\"update\"` | `\"delete\"` on successful registry\n * mutation. NOT fired on parse failures (graceful degradation).\n */\nexport type RegistryChangeKind = \"boot\" | \"create\" | \"update\" | \"delete\";\n\nexport interface StartContractRegistryOpts {\n vault: Vault;\n feed: ChangeFeed;\n source: SourceConnector;\n auditDeps: ContractAuditDeps;\n onRegistryChange?: (kind: RegistryChangeKind) => void;\n /**\n * Phase 7 / Plan 07-07 / CAN-08. Shared SuppressionSet from the server\n * bootstrap. When provided, `handleChangeEvent` calls\n * `suppression.consume(file, hash)` BEFORE re-validating; suppressed\n * events with a matching hash short-circuit (no reload, no audit row,\n * no `onExternalReload` fire). When omitted, behavior matches Phase 6\n * (every event re-validates).\n *\n * @see ../adapters/change-feed/obsidian-fs/suppression.ts — the\n * hash-keyed `consume(path, hash)` semantics.\n */\n suppression?: SuppressionSet;\n /**\n * Phase 7 / Plan 07-07 / CAN-08. Fires AFTER a non-suppressed\n * create/update reload successfully re-registers the contract. The\n * server bootstrap uses this to emit the\n * `vault-memory://contracts/reloaded` MCP Resource notification so\n * the plugin's `ReloadNotifier` can surface an \"External edit\n * detected — reload editor?\" prompt without polling.\n *\n * Receives the contract file path (vault-relative `_contracts/.yaml`).\n * NOT fired on parse failures, NOT fired on suppressed events, NOT\n * fired on delete (the plugin treats deletes as a separate concern).\n */\n onExternalReload?: (file: string) => void;\n}\n\nexport interface StartedContractRegistry {\n registry: ContractRegistry;\n dispose: () => void;\n}\n\n/**\n * Boot scan + ChangeFeed subscription. Returns a `Disposable` that\n * unsubscribes the feed handler. Idempotent boot scan: even when the\n * ChangeFeed emits an initial `create` for every existing file\n * (Pitfall F5), the registry's first-wins policy prevents duplicate\n * entries — the second attempt yields a `contract_load_error` audit row\n * (latest-error-visible is the desired behavior per D-LOAD).\n */\nexport async function startContractRegistry(\n opts: StartContractRegistryOpts,\n): Promise {\n const registry = new ContractRegistry();\n\n // Closure-local map: contract file relative-path → registered contract\n // name. Used by `delete` / `rename` events to look up the registered\n // name (the file path is the only identity the ChangeFeed carries).\n const fileToName = new Map();\n\n // ── Boot scan ─────────────────────────────────────────────────────────\n await bootScan(opts, registry, fileToName);\n opts.onRegistryChange?.(\"boot\");\n\n // ── ChangeFeed subscription ───────────────────────────────────────────\n const sub: Disposable = opts.feed.subscribe(async (event: ChangeEvent) => {\n await handleChangeEvent(event, opts, registry, fileToName);\n });\n\n return {\n registry,\n dispose: () => sub[Symbol.dispose](),\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Internal — boot scan\n// ─────────────────────────────────────────────────────────────────────────\n\nasync function bootScan(\n opts: StartContractRegistryOpts,\n registry: ContractRegistry,\n fileToName: Map,\n): Promise {\n for await (const ref of opts.source.listDocuments()) {\n const { resource } = decomposeDocId(ref.id);\n if (!CONTRACT_PATH_REGEX.test(resource)) continue;\n let text: string;\n try {\n const doc = await opts.source.readDocument(ref.id);\n text = extractText(doc);\n } catch (err) {\n recordContractLoadError(opts.auditDeps, {\n file: resource,\n error_message: messageOf(err),\n vault: opts.vault.config.name,\n });\n continue;\n }\n parseAndRegister(text, resource, opts, registry, fileToName);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Internal — ChangeFeed handler\n// ─────────────────────────────────────────────────────────────────────────\n\nasync function handleChangeEvent(\n event: ChangeEvent,\n opts: StartContractRegistryOpts,\n registry: ContractRegistry,\n fileToName: Map,\n): Promise {\n // Rename — adapter-native rename event. Handle as delete-old + create-new.\n // Renames are NOT suppression candidates (the plugin's YAML emit never\n // emits a rename event — only a create/update on the YAML path) so we\n // skip the suppression check here. `onExternalReload` does NOT fire on\n // rename; the plugin's open .contract view stays bound to its own\n // file path and the user's intent is unambiguous when they rename.\n if (event.kind === \"rename\") {\n const oldResource = decomposeDocId(event.old_id).resource;\n const newResource = decomposeDocId(event.new_id).resource;\n if (CONTRACT_PATH_REGEX.test(oldResource)) {\n deleteByFile(oldResource, registry, fileToName, opts);\n }\n if (CONTRACT_PATH_REGEX.test(newResource)) {\n await loadFromFeed(event.new_id, newResource, opts, registry, fileToName);\n opts.onRegistryChange?.(\"update\");\n } else if (CONTRACT_PATH_REGEX.test(oldResource)) {\n // Renamed OUT of `_contracts/` — pure delete.\n opts.onRegistryChange?.(\"delete\");\n }\n return;\n }\n\n const { resource } = decomposeDocId(event.id);\n if (!CONTRACT_PATH_REGEX.test(resource)) return;\n\n switch (event.kind) {\n case \"delete\": {\n if (deleteByFile(resource, registry, fileToName, opts)) {\n opts.onRegistryChange?.(\"delete\");\n }\n return;\n }\n case \"create\":\n case \"update\": {\n // Phase 7 / CAN-08 — hash-keyed echo suppression. Read the on-disk\n // body once, compute SHA-256, and ask the SuppressionSet whether\n // this event is the echo of a plugin-driven write. If yes, drop\n // silently (no audit row, no registry mutation, no callback fire).\n // If no, fall through to the existing re-validate path.\n //\n // We read the body here (rather than inside loadFromFeed) because\n // the suppression check needs the hash up-front. The body is\n // re-used downstream so the read isn't wasted.\n let text: string;\n try {\n const doc = await opts.source.readDocument(event.id);\n text = extractText(doc);\n } catch (err) {\n recordContractLoadError(opts.auditDeps, {\n file: resource,\n error_message: messageOf(err),\n vault: opts.vault.config.name,\n });\n return;\n }\n\n if (opts.suppression !== undefined) {\n const hash = sha256(text);\n if (opts.suppression.consume(resource, hash)) {\n // Echo of an own-write — drop silently per CAN-08 D-WATCH-PLUGIN-OUT.\n return;\n }\n }\n\n // For `update` semantics, drop the prior registration of this file\n // first so the new YAML can re-register (D-LOAD replace).\n if (event.kind === \"update\") {\n deleteByFile(resource, registry, fileToName, opts);\n }\n const ok = parseAndRegister(text, resource, opts, registry, fileToName);\n if (ok) {\n opts.onRegistryChange?.(event.kind);\n // CAN-08 D-WATCH-SERVER-NOTIFY — surface non-suppressed\n // external edits to subscribers (the plugin's ReloadNotifier).\n opts.onExternalReload?.(resource);\n }\n return;\n }\n }\n}\n\n/**\n * Delete the contract previously registered from `file` (if any).\n * Returns true iff something was removed.\n */\nfunction deleteByFile(\n file: string,\n registry: ContractRegistry,\n fileToName: Map,\n _opts: StartContractRegistryOpts,\n): boolean {\n const name = fileToName.get(file);\n if (name === undefined) return false;\n registry.delete(name);\n fileToName.delete(file);\n return true;\n}\n\n/**\n * Read + parse + register the YAML at `id` (a DocId whose resource is\n * `file`). On any failure, write `contract_load_error` and return false;\n * the registry stays unmutated (D-LOAD graceful degradation).\n */\nasync function loadFromFeed(\n id: DocId,\n file: string,\n opts: StartContractRegistryOpts,\n registry: ContractRegistry,\n fileToName: Map,\n): Promise {\n let text: string;\n try {\n const doc = await opts.source.readDocument(id);\n text = extractText(doc);\n } catch (err) {\n recordContractLoadError(opts.auditDeps, {\n file,\n error_message: messageOf(err),\n vault: opts.vault.config.name,\n });\n return false;\n }\n return parseAndRegister(text, file, opts, registry, fileToName);\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Internal — parse + register\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Parse `text` as a YAML contract; validate; register. Writes\n * `contract_load_error` on any failure path (parse error, Zod failure,\n * duplicate name). Returns true iff `registry.set` succeeded.\n */\nfunction parseAndRegister(\n text: string,\n file: string,\n opts: StartContractRegistryOpts,\n registry: ContractRegistry,\n fileToName: Map,\n): boolean {\n let parsed: ParsedContract;\n try {\n const docNode = parseDocument(text);\n const raw = docNode.toJS();\n const validated = ContractFileSchema.safeParse(raw);\n if (!validated.success) {\n throw new Error(`zod: ${JSON.stringify(validated.error.format())}`);\n }\n parsed = buildParsedContract(validated.data);\n } catch (err) {\n recordContractLoadError(opts.auditDeps, {\n file,\n error_message: messageOf(err),\n vault: opts.vault.config.name,\n });\n return false;\n }\n\n const result = registry.set(parsed.name, parsed);\n if (!result.ok) {\n recordContractLoadError(opts.auditDeps, {\n file,\n error_message: `duplicate_name: '${parsed.name}' already registered (first-wins per D-A1c)`,\n vault: opts.vault.config.name,\n });\n return false;\n }\n fileToName.set(file, parsed.name);\n return true;\n}\n\n/**\n * Compose a ParsedContract from validated YAML data. Builds the cached\n * Zod + JSON Schema (Pitfall F1/F2 chokepoint) and resolves $ref in\n * `output_shape` (D-A3a).\n */\nfunction buildParsedContract(data: ContractFileShape): ParsedContract {\n const inputs: ContractInputs = data.inputs as ContractInputs;\n const required = data.required;\n const built = buildInputSchema(inputs, required);\n const outputShape =\n data.output_shape !== undefined ? (resolveRefs(data.output_shape) as object) : undefined;\n\n // Narrow the optional shapes from the Zod-defaulted shape to the\n // ParsedContract surface. The Zod `HandleDeclSchema` fills `required`\n // with a boolean default; same shape on both sides.\n const sources = data.sources as Record;\n const sinks = data.sinks as Record;\n const assembly = data.assembly as ContractStep[];\n const writeBack = data.write_back as WriteBackSpec | undefined;\n\n const result: ParsedContract = {\n version: 1,\n name: data.name,\n description: data.description,\n inputs,\n required,\n sources,\n sinks,\n assembly,\n inputZodSchema: built.zodSchema,\n inputJsonSchema: built.jsonSchema,\n };\n if (outputShape !== undefined) result.output_shape = outputShape;\n if (writeBack !== undefined) result.write_back = writeBack;\n return result;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Internal — text extraction\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Extract the raw YAML text from a `Document`. The obsidian-fs source\n * publishes content as a single `paragraph` block (`blocks[0].text`);\n * future block-shaped adapters can populate the same field. Throws if\n * the Document has no block content — caller writes a load error.\n */\nfunction extractText(doc: Document): string {\n const block = doc.blocks[0];\n if (block === undefined) {\n throw new Error(\"Document has no blocks (cannot read contract YAML)\");\n }\n if (block.kind === \"paragraph\") return block.text;\n // For future block-shaped adapters, fall back to concatenating\n // paragraph blocks. Today no other adapter produces non-paragraph\n // contract documents.\n const paragraphs = doc.blocks.filter(\n (b): b is { kind: \"paragraph\"; text: string } => b.kind === \"paragraph\",\n );\n if (paragraphs.length === 0) {\n throw new Error(\"Document blocks contain no paragraph text\");\n }\n return paragraphs.map((b) => b.text).join(\"\\n\");\n}\n\nfunction messageOf(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n","/**\n * syncAutoRegistered — Phase 6 / D-A1, ADR-006 §Decision 1 (Pattern 4).\n *\n * Diff-based dynamic MCP Tool registration. Maintains a per-loader\n * `registered: Map` that survives across calls;\n * each invocation:\n * 1. computes the desired set from the registry (`` per\n * `slugify`);\n * 2. removes tools no longer desired via `RegisteredTool.remove()`;\n * 3. adds new tools via `server.registerTool(name, config, callback)`;\n * 4. calls `server.sendToolListChanged()` exactly ONCE per mutation\n * cycle (only when at least one add/remove occurred — idempotent\n * no-op when the diff is empty).\n *\n * No-op when `opts.enabled === false` (D-A1b default OFF). The\n * `register_contracts_as_tools` MCP Tool (Plan 06-02 Task 3) forces\n * `enabled: true` regardless of the per-vault config — that is the\n * explicit-control escape valve (D-A1).\n *\n * # Callback shim\n *\n * Each auto-registered tool's callback is a thin wrapper around\n * `opts.instantiateHandler(contractName, args)` (Plan 06-03 supplies the\n * real handler). The wrapper serializes the handler's return as a single\n * `text` content block — matching the v1 `ok()` shape used by\n * `src/server.ts`. Tool argument validation happens in the MCP SDK layer\n * BEFORE the wrapper fires, using the `parsed.inputZodSchema` (Pitfall\n * F1 — SDK 1.29 requires a Zod schema, not raw JSON Schema).\n *\n * # Adapter-seam discipline\n *\n * Imports only `@modelcontextprotocol/sdk` types + Plan 06-01 modules.\n * Zero `fs` / `path` / `yaml` / `chokidar`.\n */\n\nimport type { McpServer, RegisteredTool } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { ContractRegistry } from \"./registry.js\";\nimport type { ParsedContract } from \"./types.js\";\nimport { slugify } from \"./slug.js\";\n\nexport interface SyncAutoRegisteredOpts {\n /** D-A1b — per-vault gate. No-op when false. */\n enabled: boolean;\n /**\n * Plan 06-03 supplies the real handler; Plan 06-02 wires a stub\n * (`not_yet_implemented`) so auto-registration is observable today.\n * Invoked only when a registered `vm_` tool is CALLED — never\n * during registration itself.\n */\n instantiateHandler: (contractName: string, args: unknown) => Promise;\n}\n\n/**\n * Diff the registry against `registered`; perform adds/removes via the\n * SDK; fire `sendToolListChanged()` exactly once when at least one\n * change occurred.\n *\n * The `registered` map is OWNED by the caller (one per `startContractRegistry`\n * instance) — this function mutates it in place. That keeps each vault's\n * tool surface independently disposable: a server with two vaults has\n * two `registered` maps; removing vault A's tools does not touch B's\n * handles.\n */\nexport function syncAutoRegistered(\n server: McpServer,\n registry: ContractRegistry,\n prefix: string,\n registered: Map,\n opts: SyncAutoRegisteredOpts,\n): void {\n if (!opts.enabled) return;\n\n // Build the desired set: → ParsedContract.\n const desired = new Map();\n for (const [name, parsed] of registry.entries()) {\n desired.set(slugify(name, prefix), parsed);\n }\n\n let mutated = false;\n\n // Remove gone — snapshot first since we mutate `registered`.\n for (const [toolName, regd] of Array.from(registered)) {\n if (!desired.has(toolName)) {\n regd.remove();\n registered.delete(toolName);\n mutated = true;\n }\n }\n\n // Add new.\n for (const [toolName, parsed] of desired) {\n if (registered.has(toolName)) continue;\n const contractName = parsed.name;\n const regd = server.registerTool(\n toolName,\n {\n description: parsed.description,\n inputSchema: parsed.inputZodSchema,\n },\n // The callback runs AFTER the SDK validates args against the Zod\n // schema, so `args` is typed-narrowed to the contract's inputs.\n async (args: unknown) => {\n const result = await opts.instantiateHandler(contractName, args);\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(result) }],\n };\n },\n ) as RegisteredTool;\n registered.set(toolName, regd);\n mutated = true;\n }\n\n if (mutated) server.sendToolListChanged();\n}\n","/**\n * resolveTemplate — Phase 6 / D-A2c / ADR-006 §Decision 5 / Invariant C-7.\n *\n * Mustache-style template resolver over a `{inputs, steps}` bindings table.\n * Pure function, zero deps.\n *\n * # Resolution rules\n *\n * 1. Whole-string `^\\{\\{\\}\\}$` → returns the RAW typed value at\n * `` (number, array, object, etc.) — NEVER stringified.\n * 2. Embedded `{{...}}` substitutions inside a larger string → each\n * lookup result is converted to a string (JSON.stringify for\n * non-string values) and concatenated with the surrounding text.\n * 3. Recursion: arrays and objects are walked; each leaf string is\n * resolved independently. Non-string leaves (number, null, boolean)\n * pass through unchanged. The first unresolved leaf short-circuits\n * the whole result.\n * 4. `` syntax — `alias.field.nested[0]`. Split on `.` AND `[i]`\n * via the regex `/[.[\\]]/`; filter empty segments.\n *\n * # Security invariant (C-7, ADR-006 §Decision 5)\n *\n * `resolveTemplate` operates ONLY on contract YAML (read at boot time,\n * never user-supplied at call time). User inputs are looked UP from\n * the bindings table but the looked-up value is NEVER re-evaluated as\n * a template. Test 13 verifies this: if `inputs.x = \"{{inputs.y}}\"`,\n * then `resolveTemplate(\"{{inputs.x}}\", ...)` returns the raw string\n * `\"{{inputs.y}}\"`, not a recursive substitution.\n *\n * Mitigates threat T-06-03-01 (user-controlled template injection).\n *\n * # Adapter-seam discipline\n *\n * Zero `fs` / `path` / `gray-matter` / `chokidar` / `yaml` imports.\n * Pure function only.\n */\n\n/**\n * Binding table consumed by `resolveTemplate`. `inputs` carries the\n * caller-supplied values (resolves under `{{inputs.}}`); `steps`\n * accumulates named-binding outputs (resolves under `{{.}}`);\n * `handles` carries resolved source/sink handles (resolves directly\n * under `{{}}` without prefix, per RESEARCH Example 1).\n *\n * `handles` is internal to the orchestrator's binding step — it is\n * accessible from templates but NOT returned in the `bundle.steps`\n * field. The orchestrator merges it into the lookup root alongside\n * `steps` so contract YAML authors can write `{{default_sink}}`\n * without an `inputs.` prefix.\n */\nexport interface TemplateBindings {\n inputs: Record;\n steps: Record;\n /** Resolved source/sink handles. Accessible as bare `{{handle_name}}`. */\n handles?: Record;\n}\n\n/**\n * Result envelope. Discriminated union — branch on `.ok` before\n * destructuring. On `false`, `expression` carries the offending\n * `{{...}}` token verbatim (so the orchestrator can surface it in the\n * `InstantiateError.unresolved_template.expression` field).\n */\nexport type TemplateResolveResult =\n | { ok: true; value: T }\n | { ok: false; reason: \"unresolved_template\"; expression: string };\n\n/** Matches a single `{{}}` token. */\nconst TOKEN_RE = /\\{\\{([^}]+)\\}\\}/g;\n/** Matches a string that is JUST a single template — no surrounding chars. */\nconst WHOLE_STRING_RE = /^\\{\\{([^}]+)\\}\\}$/;\n\n/**\n * Look up `path` against the bindings table. Returns the raw value or\n * `undefined` when any segment is missing.\n *\n * Path syntax — alias.field.nested[0]. The leading segment is treated\n * as a key on `{inputs, steps}` (we merge them into a single root\n * lookup space so contracts can reference `{{inputs.foo}}` or\n * `{{step1.bar}}` without prefixing).\n */\nfunction lookup(path: string, bindings: TemplateBindings): unknown {\n const segments = path.split(/[.[\\]]/).filter(Boolean);\n if (segments.length === 0) return undefined;\n // Unified namespace per RESEARCH Example 2:\n // `{{inputs.}}` resolves through the `inputs` object;\n // `{{.}}` resolves through `steps[]`;\n // `{{}}` resolves through `handles[]`.\n // Build the root by exposing the `inputs` object directly AND\n // spreading both the steps map and the (optional) handles map so each\n // alias is a top-level key.\n const root: Record = {\n inputs: bindings.inputs,\n ...bindings.steps,\n ...(bindings.handles ?? {}),\n };\n let cur: unknown = root;\n for (const seg of segments) {\n if (cur === null || cur === undefined) return undefined;\n if (typeof cur !== \"object\") return undefined;\n // Numeric index handling (foo[0] → segments include \"0\").\n if (Array.isArray(cur)) {\n const idx = Number(seg);\n if (!Number.isInteger(idx)) return undefined;\n cur = cur[idx];\n continue;\n }\n cur = (cur as Record)[seg];\n }\n return cur;\n}\n\n/**\n * Resolve one string value against the bindings. Implements rules (1)\n * and (2) above.\n */\nfunction resolveString(s: string, bindings: TemplateBindings): TemplateResolveResult {\n // Rule 1: whole-string single template → raw typed value.\n const whole = WHOLE_STRING_RE.exec(s);\n if (whole !== null) {\n const path = whole[1]!.trim();\n const v = lookup(path, bindings);\n if (v === undefined) {\n return { ok: false, reason: \"unresolved_template\", expression: `{{${path}}}` };\n }\n return { ok: true, value: v };\n }\n // Rule 2: embedded substitutions — string-concat.\n if (!s.includes(\"{{\")) return { ok: true, value: s };\n let unresolved: string | null = null;\n // Reset regex state for repeated use.\n TOKEN_RE.lastIndex = 0;\n const replaced = s.replace(TOKEN_RE, (_match, rawPath: string) => {\n if (unresolved !== null) return \"\";\n const path = rawPath.trim();\n const v = lookup(path, bindings);\n if (v === undefined) {\n unresolved = `{{${path}}}`;\n return \"\";\n }\n return typeof v === \"string\" ? v : JSON.stringify(v);\n });\n if (unresolved !== null) {\n return { ok: false, reason: \"unresolved_template\", expression: unresolved };\n }\n return { ok: true, value: replaced };\n}\n\n/**\n * Recursive resolver. Walks objects + arrays; leaf strings go through\n * `resolveString`; non-string leaves pass through unchanged. First\n * unresolved leaf short-circuits the whole result (Test 12).\n */\nexport function resolveTemplate(\n value: unknown,\n bindings: TemplateBindings,\n): TemplateResolveResult {\n if (typeof value === \"string\") {\n return resolveString(value, bindings) as TemplateResolveResult;\n }\n if (Array.isArray(value)) {\n const out: unknown[] = [];\n for (const item of value) {\n const r = resolveTemplate(item, bindings);\n if (!r.ok) return r;\n out.push(r.value);\n }\n return { ok: true, value: out as T };\n }\n if (value !== null && typeof value === \"object\") {\n const out: Record = {};\n for (const [k, v] of Object.entries(value as Record)) {\n const r = resolveTemplate(v, bindings);\n if (!r.ok) return r;\n out[k] = r.value;\n }\n return { ok: true, value: out as T };\n }\n // Pass-through for numbers, booleans, null, undefined.\n return { ok: true, value: value as T };\n}\n","/**\n * PeerMcpRegistry — Phase 6 / D-A2a peer-MCP / RESEARCH §Pattern 3 /\n * Pitfall F4.\n *\n * Lifecycle:\n *\n * - At server boot: `new PeerMcpRegistry(); await reg.start(configs);`\n * Each `[contracts.mcp_clients.]` entry is spawned via\n * `StdioClientTransport(...)` and an MCP SDK `Client` is connected\n * over stdio. Connect failures DO NOT block boot — the registry\n * records the failed name as unavailable and writes a WARN line to\n * stderr (CONTEXT.md \"Claude's Discretion\": peer-MCP unreachable is\n * not a server-fatal condition).\n *\n * - At runtime: `verbDispatcher` consults the registry on every\n * `mcp:///` verb. The peer-MCP call is wrapped in\n * `Promise.race([call, timeout(step_timeout_seconds * 1000)])` at\n * `verbs/mcp-extension.ts` (Q-TIMEOUT — peer-MCP only).\n *\n * - At shutdown: `process.on('SIGTERM' | 'SIGINT')` handlers in\n * `src/server.ts` call `reg.shutdown()`, which iterates every\n * PeerMcpClient and invokes `[Symbol.dispose]()` →\n * `transport.close()` → child process killed. Mitigates Pitfall F4\n * (orphaned child processes after parent crash).\n *\n * # Envelope peeling\n *\n * MCP `tools/call` returns `{content: [{type:'text', text: '...'}]}`.\n * The wrapper peels one layer: when the first content block is a\n * `text` and the text parses as JSON, return the parsed object; when\n * the text is not JSON, return the raw string; otherwise return the\n * full envelope. Callers see ergonomic data, not raw protocol shapes.\n *\n * # Testability\n *\n * `ClientFactory` is an optional constructor parameter — tests inject\n * a stub factory that returns a mock Client without spawning a real\n * child process. Plan 06-04's CON-09 smoketest exercises the real\n * `defaultConnect` path end-to-end.\n *\n * # Adapter-seam discipline\n *\n * Imports only `@modelcontextprotocol/sdk/client/*`. The\n * `StdioClientTransport` spawns a child via the SDK's own\n * `child_process.spawn` — encapsulated, not leaked into `src/contracts/`.\n */\n\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\n/** Single `[contracts.mcp_clients.]` config entry. */\nexport interface PeerMcpClientConfig {\n command: string;\n args?: string[];\n env?: Record;\n}\n\n/**\n * One tool a peer exposes, as returned by MCP `tools/list`. Mirrors the\n * subset of the SDK's `ListToolsResult.tools[]` that the Sources\n * registry surfaces (SOURCES-REGISTRY.md §5.2). `inputSchema` is opaque\n * here — the inspector consumes it to type step args.\n */\nexport interface PeerMcpTool {\n name: string;\n description?: string;\n inputSchema?: Record;\n}\n\n/**\n * Connection state for a source (SOURCES-REGISTRY.md §5.1):\n * - \"connected\" — connect succeeded AND tools/list succeeded ≥ once.\n * - \"unavailable\" — the (re)connect attempt failed.\n * - \"unreachable\" — connected at some point but a later tools/list failed.\n */\nexport type PeerMcpStatus = \"connected\" | \"unavailable\" | \"unreachable\";\n\n/** Read-only projection of a source's cached state for resource handlers. */\nexport interface PeerMcpClientInfo {\n status: PeerMcpStatus;\n tools: readonly PeerMcpTool[];\n /** Epoch-seconds of the last successful tools/list; null if never. */\n lastRefreshed: number | null;\n /** Captured error message when status is \"unavailable\"/\"unreachable\". */\n error?: string;\n}\n\n/** A live peer-MCP client managed by the registry. */\nexport interface PeerMcpClient {\n /** Forward a `tools/call` to the peer, peeling the MCP envelope. */\n callTool(name: string, args: unknown): Promise;\n /** Fetch the peer's tools/list. Throws when the client is unavailable. */\n listTools(): Promise;\n /** False when the boot-time connect failed; calling `callTool` throws. */\n available: boolean;\n /** Kills the underlying child process. Idempotent (transport.close is). */\n [Symbol.dispose](): void;\n}\n\n/** Minimal client surface the registry depends on (subset of SDK `Client`). */\nexport interface PeerClientLike {\n callTool: Client[\"callTool\"];\n /** Present on the real SDK Client; optional so older stubs still satisfy the type. */\n listTools?: Client[\"listTools\"];\n}\n\n/**\n * Optional injection point for tests. Production code uses\n * `defaultConnect` which spawns a real child via `StdioClientTransport`.\n */\nexport type ClientFactory = (\n cfg: PeerMcpClientConfig,\n) => Promise<{ client: PeerClientLike; transport: { close(): void } }>;\n\n/** Internal per-source record: the wrapped client + its cached metadata. */\ninterface RegistryEntry {\n client: PeerMcpClient;\n status: PeerMcpStatus;\n tools: PeerMcpTool[];\n lastRefreshed: number | null;\n error?: string;\n}\n\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\nexport class PeerMcpRegistry {\n private entries = new Map();\n private readonly clientFactory: ClientFactory | undefined;\n\n constructor(clientFactory?: ClientFactory) {\n this.clientFactory = clientFactory;\n }\n\n get size(): number {\n return this.entries.size;\n }\n\n /**\n * Boot every `[contracts.mcp_clients.]` entry. Failures are\n * non-fatal: the name is recorded as unavailable and a WARN line is\n * written to stderr. Returns when all attempts have settled.\n *\n * On a successful connect we prime the tools cache via tools/list. A\n * tools/list failure does NOT mark the source unavailable — the\n * connection is live and callTool may still work — but the status\n * becomes \"unreachable\" so the UI can prompt a retry.\n */\n async start(configs: Record): Promise {\n for (const [name, cfg] of Object.entries(configs)) {\n await this.connectAndStore(name, cfg);\n }\n }\n\n get(name: string): PeerMcpClient | undefined {\n return this.entries.get(name)?.client;\n }\n\n /** All registered source names, in insertion order. */\n names(): string[] {\n return Array.from(this.entries.keys());\n }\n\n /** Cached metadata projection for one source; undefined if unknown. */\n getInfo(name: string): PeerMcpClientInfo | undefined {\n const e = this.entries.get(name);\n if (e === undefined) return undefined;\n return {\n status: e.status,\n tools: e.tools,\n lastRefreshed: e.lastRefreshed,\n ...(e.error !== undefined ? { error: e.error } : {}),\n };\n }\n\n /**\n * Register a new source at runtime: spawn + connect, then prime the\n * tools cache. Replaces any existing entry of the same name (the old\n * client is disposed first). Returns the resulting info projection.\n */\n async add(name: string, cfg: PeerMcpClientConfig): Promise {\n const existing = this.entries.get(name);\n if (existing !== undefined) {\n try {\n existing.client[Symbol.dispose]();\n } catch {\n // Best-effort — replacing the entry regardless.\n }\n }\n await this.connectAndStore(name, cfg);\n // connectAndStore always sets an entry, so getInfo is non-undefined.\n return this.getInfo(name)!;\n }\n\n /**\n * Dispose a source and drop it from the registry. Idempotent —\n * removing an unknown name is a no-op that returns false.\n */\n remove(name: string): boolean {\n const e = this.entries.get(name);\n if (e === undefined) return false;\n try {\n e.client[Symbol.dispose]();\n } catch (err) {\n const msg = errorMessage(err);\n process.stderr.write(`[contracts] peer-MCP dispose error: ${msg}\\n`);\n }\n this.entries.delete(name);\n return true;\n }\n\n /**\n * Re-issue tools/list against the live client and refresh the cache.\n * Returns the updated info, or undefined if the name is unknown.\n *\n * If the client is currently unavailable this only updates the error;\n * re-spawning a failed source requires `add(name, cfg)` with the\n * config (the registry does not retain configs).\n */\n async refresh(name: string): Promise {\n const e = this.entries.get(name);\n if (e === undefined) return undefined;\n if (!e.client.available) {\n e.status = \"unavailable\";\n return this.getInfo(name);\n }\n await this.primeTools(e);\n return this.getInfo(name);\n }\n\n /** Dispose every client and clear the internal map. Idempotent. */\n async shutdown(): Promise {\n for (const e of this.entries.values()) {\n try {\n e.client[Symbol.dispose]();\n } catch (err) {\n const msg = errorMessage(err);\n process.stderr.write(`[contracts] peer-MCP dispose error: ${msg}\\n`);\n }\n }\n this.entries.clear();\n }\n\n // ─── internals ─────────────────────────────────────────────────────────\n\n /** Connect (factory or default), store the entry, prime tools cache. */\n private async connectAndStore(name: string, cfg: PeerMcpClientConfig): Promise {\n try {\n const { client, transport } = this.clientFactory\n ? await this.clientFactory(cfg)\n : await this.defaultConnect(cfg);\n const entry: RegistryEntry = {\n client: wrapAvailable(client, transport),\n status: \"connected\",\n tools: [],\n lastRefreshed: null,\n };\n this.entries.set(name, entry);\n await this.primeTools(entry);\n } catch (err) {\n const msg = errorMessage(err);\n process.stderr.write(`[contracts] peer-MCP client '${name}' failed to start: ${msg}\\n`);\n this.entries.set(name, {\n client: wrapUnavailable(),\n status: \"unavailable\",\n tools: [],\n lastRefreshed: null,\n error: msg,\n });\n }\n }\n\n /**\n * Call tools/list and update the entry's cache + status. A failure\n * keeps the connection (status → \"unreachable\") rather than tearing it\n * down — the source is reachable for callTool even if discovery failed.\n */\n private async primeTools(entry: RegistryEntry): Promise {\n try {\n const tools = await entry.client.listTools();\n entry.tools = tools;\n entry.lastRefreshed = nowSeconds();\n entry.status = \"connected\";\n delete entry.error;\n } catch (err) {\n entry.status = \"unreachable\";\n entry.error = errorMessage(err);\n }\n }\n\n private async defaultConnect(\n cfg: PeerMcpClientConfig,\n ): Promise<{ client: Client; transport: StdioClientTransport }> {\n const transport = new StdioClientTransport({\n command: cfg.command,\n args: cfg.args ?? [],\n env: cfg.env,\n });\n const client = new Client({ name: \"vault-memory-peer\", version: \"2.0.0\" });\n await client.connect(transport);\n return { client, transport };\n }\n}\n\nfunction wrapAvailable(client: PeerClientLike, transport: { close(): void }): PeerMcpClient {\n return {\n available: true,\n async callTool(name: string, args: unknown): Promise {\n const res = await client.callTool({\n name,\n arguments: args as Record,\n });\n // Peel MCP envelope: result.content[0] is typically\n // {type:'text', text: '...'}. Return parsed JSON when applicable.\n const content = (res as { content?: unknown }).content;\n if (Array.isArray(content) && content.length > 0) {\n const first = content[0] as { type?: string; text?: string };\n if (first.type === \"text\" && typeof first.text === \"string\") {\n try {\n return JSON.parse(first.text);\n } catch {\n return first.text;\n }\n }\n }\n return res;\n },\n async listTools(): Promise {\n // A peer without listTools support (older stub or a server that\n // doesn't advertise the tools capability) yields an empty set\n // rather than throwing — an empty palette is fine; a crash is not.\n if (typeof client.listTools !== \"function\") return [];\n const res = await client.listTools();\n const tools = (res as { tools?: unknown }).tools;\n if (!Array.isArray(tools)) return [];\n const out: PeerMcpTool[] = [];\n for (const t of tools) {\n if (!t || typeof t !== \"object\") continue;\n const name = (t as { name?: unknown }).name;\n if (typeof name !== \"string\") continue;\n const tool: PeerMcpTool = { name };\n const description = (t as { description?: unknown }).description;\n if (typeof description === \"string\") tool.description = description;\n const inputSchema = (t as { inputSchema?: unknown }).inputSchema;\n if (inputSchema && typeof inputSchema === \"object\") {\n tool.inputSchema = inputSchema as Record;\n }\n out.push(tool);\n }\n return out;\n },\n [Symbol.dispose](): void {\n transport.close();\n },\n };\n}\n\nfunction wrapUnavailable(): PeerMcpClient {\n return {\n available: false,\n async callTool(): Promise {\n throw new Error(\"peer-MCP client unavailable\");\n },\n async listTools(): Promise {\n throw new Error(\"peer-MCP client unavailable\");\n },\n [Symbol.dispose](): void {\n /* no-op */\n },\n };\n}\n","/**\n * callMcpVerb — Phase 6 / D-A2a peer-MCP extension / Q-TIMEOUT.\n *\n * Parses `mcp:///` syntax, looks the client up in the\n * `PeerMcpRegistry`, forwards the args, and wraps the call in\n * `Promise.race([call, timeout(step_timeout_seconds * 1000)])` so a\n * hung peer cannot block contract instantiation indefinitely.\n *\n * # Q-TIMEOUT scope (ADR-006 §Decision 11)\n *\n * ONLY peer-MCP verbs are wrapped here. Baseline verbs route directly\n * through their handlers in `verbs/index.ts` without the race — they\n * use their own timeout discipline (SQLite query timeout, Ollama HTTP\n * timeout). Wrapping baseline verbs adds latency overhead for no\n * benefit.\n *\n * # Failure envelopes (ADR-006 §Decision 7, sealed for v2.0.0)\n *\n * - `{ok:false, reason:'verb_not_available', verb}` — regex rejected\n * the verb shape (defense in depth; Plan 06-01's Zod gate already\n * rejects malformed verbs at contract load time).\n * - `{ok:false, reason:'mcp_client_unavailable', verb, client_name}` —\n * the server name has no registered client OR the boot connect\n * failed.\n * - `{ok:false, reason:'assembly_step_failed', step_alias, cause}` —\n * either a timeout (`cause: 'timeout'`) or the underlying call\n * threw (`cause: `).\n *\n * # Adapter-seam discipline\n *\n * Zero `fs` / `path` / `gray-matter` / `chokidar` imports.\n */\n\nimport type { PeerMcpRegistry } from \"../mcp-clients.js\";\n\n/** Same shape as `verbDispatcher`'s `opts` so callers can pass through. */\nexport interface VerbDispatchOpts {\n stepAlias: string;\n timeoutSeconds: number;\n}\n\n/**\n * `mcp:///` — both segments must be `[a-z][a-z0-9_-]*`.\n * Mirrors the Zod regex used by the contract loader (Plan 06-01\n * `schema.ts`). Pinned here so defense-in-depth dispatch rejects the\n * same shapes the loader rejects.\n */\nconst MCP_VERB_RE = /^mcp:\\/\\/([a-z][a-z0-9_-]*)\\/([a-z][a-z0-9_-]*)$/;\n\nexport async function callMcpVerb(\n verb: string,\n args: Record,\n registry: PeerMcpRegistry,\n opts: VerbDispatchOpts,\n): Promise {\n const match = MCP_VERB_RE.exec(verb);\n if (!match) {\n return { ok: false, reason: \"verb_not_available\", verb };\n }\n const serverName = match[1]!;\n const toolName = match[2]!;\n const client = registry.get(serverName);\n if (!client || !client.available) {\n return {\n ok: false,\n reason: \"mcp_client_unavailable\",\n verb,\n client_name: serverName,\n };\n }\n // Q-TIMEOUT: wrap ONLY peer-MCP verbs.\n const timeoutMs = Math.max(1, Math.floor(opts.timeoutSeconds * 1000));\n let timer: NodeJS.Timeout | undefined;\n const timeoutPromise = new Promise((_, reject) => {\n timer = setTimeout(() => reject(new Error(\"timeout\")), timeoutMs);\n });\n try {\n return await Promise.race([client.callTool(toolName, args), timeoutPromise]);\n } catch (err) {\n const cause =\n err instanceof Error && err.message === \"timeout\"\n ? \"timeout\"\n : err instanceof Error\n ? err.message\n : String(err);\n return {\n ok: false,\n reason: \"assembly_step_failed\",\n step_alias: opts.stepAlias,\n cause,\n };\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n","/**\n * verbDispatcher — Phase 6 / D-A2a / ADR-006 §Decision 2 / Invariant C-1.\n *\n * Closed 11-verb baseline dispatcher + `\"literal\"` escape + `mcp://`\n * peer-MCP extension. Write verbs are NOT part of the assembly enum —\n * writes happen exclusively via the structurally-separate `write_back:`\n * block (Invariant C-1).\n *\n * # Baseline verb signatures (verified against existing implementations per RESEARCH §A9)\n *\n * - search_hybrid: ({query, vaults?, top_k?, recency_weight?, authority_weight?, include_superseded?, expand?}) → {hits}\n * - expand: ({seed_doc_ids, hops, direction?, edge_types?, filter_properties?, include_superseded?}) → {doc_ids, edges}\n * - cluster: ({seed_doc_ids?, query?, vault?, method, query_top_k?, force?}) → {clusters}\n * - recall: ({query, min_confidence?, types?, max_age_days?, sink?, vaults?, limit?}) → {hits}\n * - compile_brief: ({vault, target, source_doc_ids, purpose, max_tokens?, prepared_text?, sink?}) → {ok, doc_id, body?}\n * - get_brief: ({vault, target, max_age_days?, allow_stale?}) → Brief | {stale: true, ...} | null\n * - query_frontmatter: ({vault, where, limit?}) → {doc_ids, rows}\n * - list_backlinks: ({vault, path}) → {backlinks}\n * - get_outline: ({doc_id, vaults?}) → {nodes}\n * - search_sections: ({query, vaults?, limit?, recency_weight?, authority_weight?, include_superseded?}) → {hits}\n * - read_note: ({vault, path}) → {body, properties, ...}\n *\n * Each adapter passes contract YAML args (post-template-resolution)\n * verbatim to the verb handler — no reshaping. The contract author is\n * responsible for matching the verb's documented signature; Zod\n * validation at `instantiate_contract` time catches type mismatches.\n *\n * # Q-TIMEOUT (ADR-006 §Decision 11)\n *\n * `opts.timeoutSeconds` applies ONLY to `mcp://*` verbs (peer-MCP).\n * Baseline verbs are NOT wrapped — they use their own timeout\n * discipline. Test 11 verifies that an absurdly small\n * `timeoutSeconds` does not affect baseline dispatch.\n *\n * # Adapter-seam discipline\n *\n * Imports `../mcp-clients.js` (registry type), `../types.js`\n * (AssemblyVerb type), and `./mcp-extension.js`. Zero `fs` / `path` /\n * `gray-matter` / `chokidar` imports.\n */\n\nimport type { AssemblyVerb } from \"../types.js\";\nimport type { PeerMcpRegistry } from \"../mcp-clients.js\";\nimport { callMcpVerb, type VerbDispatchOpts } from \"./mcp-extension.js\";\n\nexport type { VerbDispatchOpts } from \"./mcp-extension.js\";\n\n/**\n * Dependencies injected into `verbDispatcher`. Each handler is a thin\n * thunk over the existing Phase 1-5 implementation — `instantiate.ts`\n * binds these against a specific Vault at call site.\n */\nexport interface VerbDeps {\n hybridSearch: (args: unknown) => Promise;\n handleExpand: (args: unknown) => Promise;\n handleCluster: (args: unknown) => Promise;\n handleRecall: (args: unknown) => Promise;\n handleCompileBrief: (args: unknown) => Promise;\n handleGetBrief: (args: unknown) => Promise;\n handleQueryFrontmatter: (args: unknown) => Promise;\n handleListBacklinks: (args: unknown) => Promise;\n handleGetOutline: (args: unknown) => Promise;\n handleSearchSections: (args: unknown) => Promise;\n handleReadNote: (args: unknown) => Promise;\n peerMcpRegistry: PeerMcpRegistry;\n}\n\n/**\n * Dispatch one assembly step. Returns the verb's output OR a structured\n * error envelope; the orchestrator (`instantiate.ts`) inspects the\n * shape and either binds the output under the step's `as:` alias or\n * short-circuits with an `InstantiateError`.\n *\n * `step` carries the original step record so `literal` can peel\n * `step.value` (not `args`).\n */\nexport async function verbDispatcher(\n verb: AssemblyVerb,\n args: Record | undefined,\n step: { value?: unknown } | undefined,\n deps: VerbDeps,\n opts: VerbDispatchOpts,\n): Promise {\n // The `literal` escape hatch — emits `step.value` verbatim.\n if (verb === \"literal\") {\n return step?.value;\n }\n // Peer-MCP extension — wrapped in Q-TIMEOUT.\n if (typeof verb === \"string\" && verb.startsWith(\"mcp://\")) {\n return callMcpVerb(verb, args ?? {}, deps.peerMcpRegistry, opts);\n }\n // Closed baseline enum.\n switch (verb) {\n case \"search_hybrid\":\n return deps.hybridSearch(args);\n case \"expand\":\n return deps.handleExpand(args);\n case \"cluster\":\n return deps.handleCluster(args);\n case \"recall\":\n return deps.handleRecall(args);\n case \"compile_brief\":\n return deps.handleCompileBrief(args);\n case \"get_brief\":\n return deps.handleGetBrief(args);\n case \"query_frontmatter\":\n return deps.handleQueryFrontmatter(args);\n case \"list_backlinks\":\n return deps.handleListBacklinks(args);\n case \"get_outline\":\n return deps.handleGetOutline(args);\n case \"search_sections\":\n return deps.handleSearchSections(args);\n case \"read_note\":\n return deps.handleReadNote(args);\n default:\n // Defense-in-depth — the Zod schema at contract load rejects any\n // verb outside the closed enum; this is the runtime backstop.\n return { ok: false, reason: \"verb_not_available\", verb };\n }\n}\n","/**\n * instantiateContract — Phase 6 / CON-06 / D-A4a/b/c / Q-OUTPUT.\n *\n * The L4 orchestrator: takes a contract name + inputs (+ optional\n * source/sink overrides), executes the full 7-step pipeline from\n * RESEARCH §Architecture, and returns either the shaped bundle or a\n * structured `InstantiateError` envelope.\n *\n * # Pipeline (RESEARCH §Architecture (1)-(7))\n *\n * (1) Lookup contract → `unknown_contract` if missing.\n * (2) Zod-validate inputs against `parsed.inputZodSchema` (Pitfall F2:\n * additionalProperties:false rejects typos) → `invalid_inputs`.\n * (3) Resolve override handles. Reject unknown handles (validated\n * against `parsed.sources`/`parsed.sinks` keys). Sinks ADDITIONALLY\n * validate through `MemorySinkRegistry.resolveMemorySink` (D-A4c\n * — MEM-05 invariant un-bypassable). Default chain per D-A4b:\n * explicit override → config default → contract YAML literal →\n * error if required.\n * (4) Build template bindings: `inputs` carries the caller's data PLUS\n * resolved source/sink handles (so `{{default_source}}` works);\n * `steps` starts empty and accumulates as the loop runs.\n * (5) For each assembly step:\n * a. Resolve `{{templates}}` in step.args + step.value via\n * `resolveTemplate`. Unresolved → `unresolved_template`.\n * b. Dispatch via `verbDispatcher`. Thrown errors caught and\n * surfaced as `assembly_step_failed`. Structured-error\n * envelopes (`verb_not_available`, `mcp_client_unavailable`)\n * from the dispatcher pass through directly.\n * c. Write a `contract_audit kind:'contract_step'` row REGARDLESS\n * of success/failure (payload-free per C-5).\n * d. Bind output under `step.as` in `bindings.steps`.\n * (6) If `parsed.write_back` exists: resolve templates on `sink`,\n * `body_from`, `properties` and route through\n * `DeliveryAdapter.write` (MEM-05 chokepoint). Thrown → `write_back_failed`.\n * (7) If `parsed.output_shape` exists: build a Zod schema via\n * `z.fromJSONSchema(parsed.output_shape)` and `safeParse` the\n * `{steps, write_back}` bundle (Q-OUTPUT). Mismatch →\n * `validation_failed_on_output_shape`. Parse failure inside the\n * Zod build → stderr WARN + skip (graceful degradation).\n *\n * # Invariants (ADR-006)\n *\n * - C-1: The verb enum has NO write verbs. The dispatcher's `default`\n * branch rejects unknown verbs (defense-in-depth).\n * - C-2: All sinks pass through `MemorySinkRegistry.resolveMemorySink`\n * before the write_back path runs. Tested in Test 7.\n * - C-3: Only `DeliveryAdapter.write()`'s return value populates\n * `bundle.write_back.doc_id`. Peer-MCP outputs are advisory step\n * bindings — they cannot fabricate a DocId.\n * - C-5: `recordContractStep` is payload-free; its TypeScript\n * signature excludes any output/payload field.\n * - C-7: User-supplied input values are NEVER re-evaluated as\n * templates (verified in templates.test.ts Test 13).\n *\n * # Adapter-seam discipline\n *\n * Imports zod + sibling contracts modules + MemorySinkRegistry type +\n * DeliveryAdapter type. Zero `fs` / `path` / `gray-matter` /\n * `chokidar` / `yaml` imports.\n */\n\nimport { z } from \"zod\";\nimport type { ContractRegistry } from \"./registry.js\";\nimport type { InstantiateError, OverrideMap, ContractStep } from \"./types.js\";\nimport { resolveTemplate, type TemplateBindings } from \"./templates.js\";\nimport { verbDispatcher, type VerbDeps } from \"./verbs/index.js\";\nimport { recordContractStep, type ContractAuditDeps } from \"./audit.js\";\nimport type { MemorySinkRegistry } from \"../memory/registry.js\";\nimport type { DeliveryAdapter } from \"../adapters/delivery/types.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { Document, DocId } from \"../types.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\n// ─────────────────────────────────────────────────────────────────────────\n// Public surface\n// ─────────────────────────────────────────────────────────────────────────\n\nexport interface InstantiateDeps extends VerbDeps, ContractAuditDeps {\n vault: Vault;\n registry: ContractRegistry;\n memorySinks: MemorySinkRegistry;\n delivery: DeliveryAdapter;\n /** From `[contracts.defaults]` — overrides contract YAML literals. */\n configDefaults: Record;\n /** Q-TIMEOUT — applied ONLY to peer-MCP verbs. */\n stepTimeoutSeconds: number;\n}\n\nexport interface InstantiateArgs {\n name: string;\n inputs: Record;\n source_overrides?: OverrideMap;\n sink_overrides?: OverrideMap;\n}\n\n/** Q-OUTPUT — the bundle shape returned to callers on success. */\nexport interface InstantiateBundle {\n steps: Record;\n write_back: { doc_id: string; sink: string } | null;\n}\n\nexport type InstantiateResult = ({ ok: true } & InstantiateBundle) | InstantiateError;\n\n// ─────────────────────────────────────────────────────────────────────────\n// Orchestrator\n// ─────────────────────────────────────────────────────────────────────────\n\nexport async function instantiateContract(\n deps: InstantiateDeps,\n args: InstantiateArgs,\n): Promise {\n // (1) Lookup.\n const parsed = deps.registry.get(args.name);\n if (!parsed) return { ok: false, reason: \"unknown_contract\", name: args.name };\n\n // (2) Zod-validate inputs (Pitfall F2: additionalProperties:false).\n const inputCheck = parsed.inputZodSchema.safeParse(args.inputs);\n if (!inputCheck.success) {\n return { ok: false, reason: \"invalid_inputs\", issues: inputCheck.error.format() };\n }\n\n // (3a) Reject unknown override handles for sources.\n const validSourceHandles = Object.keys(parsed.sources);\n for (const handle of Object.keys(args.source_overrides ?? {})) {\n if (!validSourceHandles.includes(handle)) {\n return {\n ok: false,\n reason: \"unknown_override_handle\",\n handle,\n valid_handles: validSourceHandles,\n };\n }\n }\n // (3b) Reject unknown override handles for sinks.\n const validSinkHandles = Object.keys(parsed.sinks);\n for (const handle of Object.keys(args.sink_overrides ?? {})) {\n if (!validSinkHandles.includes(handle)) {\n return {\n ok: false,\n reason: \"unknown_override_handle\",\n handle,\n valid_handles: validSinkHandles,\n };\n }\n }\n\n // (3c) Default chain per D-A4b: explicit → config → contract literal → error if required.\n const resolvedSources: Record = {};\n for (const [handle, decl] of Object.entries(parsed.sources)) {\n const v =\n args.source_overrides?.[handle] ??\n deps.configDefaults[handle] ??\n (decl.handle === \"\" ? undefined : decl.handle);\n if (v === undefined && decl.required) {\n return {\n ok: false,\n reason: \"missing_required_source\",\n handle,\n hint: `pass via source_overrides or set [contracts.defaults.${handle}] in config.toml`,\n };\n }\n if (v !== undefined) resolvedSources[handle] = v;\n }\n const resolvedSinks: Record = {};\n for (const [handle, decl] of Object.entries(parsed.sinks)) {\n const v =\n args.sink_overrides?.[handle] ??\n deps.configDefaults[handle] ??\n (decl.handle === \"\" ? undefined : decl.handle);\n if (v === undefined && decl.required) {\n return {\n ok: false,\n reason: \"missing_required_source\",\n handle,\n hint: `pass via sink_overrides or set [contracts.defaults.${handle}] in config.toml`,\n };\n }\n if (v !== undefined) {\n // (4) D-A4c MEM-05 invariant — must resolve through MemorySinkRegistry.\n try {\n deps.memorySinks.resolveMemorySink(v);\n } catch {\n return {\n ok: false,\n reason: \"sink_override_not_a_memory_sink\",\n target: v,\n hint: \"sinks must be a registered MemorySink handle (see list_sinks)\",\n };\n }\n resolvedSinks[handle] = v;\n }\n }\n\n // (5) Build template bindings. The three namespaces are kept separate\n // so the returned `bundle.steps` carries ONLY step outputs (not the\n // resolved source/sink handles). Both access patterns are supported:\n // - `{{default_sink}}` resolves via the `handles` map (bare name);\n // - `{{inputs.default_sink}}` resolves via `inputs.` (a\n // mirror copy is placed under `inputs` so contract authors who\n // prefer the explicit path notation are not blocked).\n // - `{{inputs.x}}` resolves caller-supplied data via `inputs`;\n // - `{{step1.y}}` resolves accumulated step outputs via `steps`.\n // Caller inputs cannot collide with declared handles (Zod\n // additionalProperties:false rejects unknown keys at input validation).\n const bindings: TemplateBindings = {\n inputs: { ...inputCheck.data, ...resolvedSources, ...resolvedSinks },\n steps: {},\n handles: { ...resolvedSources, ...resolvedSinks },\n };\n\n // (6) Execute steps.\n for (const step of parsed.assembly) {\n const stepResult = await runStep(deps, parsed.name, step, bindings);\n if (\"error\" in stepResult) {\n return stepResult.error;\n }\n bindings.steps[step.as] = stepResult.value;\n }\n\n // (7) Run write_back via DeliveryAdapter.write (MEM-05 chokepoint).\n let writeBackResult: { doc_id: string; sink: string } | null = null;\n if (parsed.write_back) {\n const wb = parsed.write_back;\n const sinkResolved = resolveTemplate(wb.sink, bindings);\n if (!sinkResolved.ok) {\n return { ok: false, reason: \"unresolved_template\", expression: sinkResolved.expression };\n }\n const bodyResolved = resolveTemplate(wb.body_from, bindings);\n if (!bodyResolved.ok) {\n return { ok: false, reason: \"unresolved_template\", expression: bodyResolved.expression };\n }\n const propsResolved = resolveTemplate(wb.properties, bindings);\n if (!propsResolved.ok) {\n return { ok: false, reason: \"unresolved_template\", expression: propsResolved.expression };\n }\n if (typeof bodyResolved.value !== \"string\") {\n return {\n ok: false,\n reason: \"write_back_failed\",\n cause: `body_from must resolve to a string, got ${typeof bodyResolved.value}`,\n };\n }\n const sinkResolvedString =\n typeof sinkResolved.value === \"string\" ? sinkResolved.value : String(sinkResolved.value);\n // Resolve the sink name/handle to its canonical full handle (e.g.\n // `obsidian-fs://test-vault/_memory/`). MemorySinkRegistry accepts\n // either form via resolveMemorySink.\n let sinkObj: { handle: unknown; vault: string; resolveToRelativePath: string };\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n sinkObj = deps.memorySinks.resolveMemorySink(sinkResolvedString) as any;\n } catch {\n return {\n ok: false,\n reason: \"write_back_failed\",\n cause: `sink \"${sinkResolvedString}\" did not resolve to a registered MemorySink`,\n };\n }\n const sinkHandle = sinkObj.handle as unknown as string;\n try {\n // Compose a Document patch — body lives in a single paragraph\n // block; the DeliveryAdapter assigns the final filename via the\n // contract's `naming` strategy.\n const doc: Partial = {\n blocks: [{ kind: \"paragraph\", text: bodyResolved.value }],\n properties: propsResolved.value as Record,\n };\n // Synthesize a real DocId rooted in the sink folder. The\n // obsidian-fs delivery adapter's NAMING-AUTO logic rewrites the\n // last path segment per the bound MemoryContract's naming\n // strategy (date-slug for default-memory-v1, caller-provided for\n // default-brief-v1). We pick a placeholder slug from the\n // contract name + step alias namespace so the DocId is a valid\n // path even before the rewrite. Plan 06-04 may swap this for an\n // adapter-side allocator that returns the final DocId without a\n // placeholder round-trip.\n // Placeholder filename — the obsidian-fs adapter's NAMING-AUTO\n // logic rewrites this per the bound MemoryContract's naming\n // strategy. The extension is adapter-specific (markdown for\n // obsidian-fs) but we never hard-code it here per ADR-002 I-5;\n // the adapter appends the extension when it rewrites the path.\n const placeholderName = String(parsed.name).replace(/[^a-z0-9-]/gi, \"_\");\n const placeholderResource = sinkObj.resolveToRelativePath + placeholderName;\n const placeholderId =\n `obsidian-fs://${sinkObj.vault}/${placeholderResource}` as unknown as DocId;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const writeRes: any = await deps.delivery.write(placeholderId, doc, {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n sink: sinkHandle as any,\n });\n if (writeRes && writeRes.ok === false) {\n return {\n ok: false,\n reason: \"write_back_failed\",\n cause: String(writeRes.reason ?? writeRes.message ?? \"unknown write failure\"),\n };\n }\n writeBackResult = {\n doc_id: String(writeRes.doc_id),\n sink: sinkHandle,\n };\n } catch (err) {\n const cause = errorMessage(err);\n return { ok: false, reason: \"write_back_failed\", cause };\n }\n }\n\n // (8) Validate bundle against output_shape (Q-OUTPUT).\n const bundle: InstantiateBundle = {\n steps: bindings.steps,\n write_back: writeBackResult,\n };\n if (parsed.output_shape) {\n try {\n const outputSchema = z.fromJSONSchema(\n parsed.output_shape as unknown as Parameters[0],\n );\n const check = outputSchema.safeParse(bundle);\n if (!check.success) {\n return {\n ok: false,\n reason: \"validation_failed_on_output_shape\",\n issues: check.error.format(),\n };\n }\n } catch (err) {\n // The contract YAML's output_shape is not a Zod-parseable JSON\n // Schema. Log + skip (graceful degradation) — the contract\n // author can iterate without breaking the slice.\n const msg = errorMessage(err);\n process.stderr.write(`[contracts] output_shape validation skipped: ${msg}\\n`);\n }\n }\n\n return { ok: true, ...bundle };\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Internals\n// ─────────────────────────────────────────────────────────────────────────\n\ninterface StepValue {\n value: unknown;\n}\ninterface StepError {\n error: InstantiateError;\n}\n\n/**\n * Run one assembly step: resolve templates → dispatch → record audit\n * (always) → return the bound output OR a structured error.\n */\nasync function runStep(\n deps: InstantiateDeps,\n contractName: string,\n step: ContractStep,\n bindings: TemplateBindings,\n): Promise {\n // (a) Resolve templates on args + value.\n const resolvedArgs = step.args\n ? resolveTemplate(step.args, bindings)\n : { ok: true as const, value: undefined };\n if (!resolvedArgs.ok) {\n writeAuditRow(deps, contractName, step);\n return {\n error: {\n ok: false,\n reason: \"unresolved_template\",\n expression: resolvedArgs.expression,\n },\n };\n }\n const resolvedValue =\n step.value !== undefined\n ? resolveTemplate(step.value, bindings)\n : { ok: true as const, value: undefined };\n if (!resolvedValue.ok) {\n writeAuditRow(deps, contractName, step);\n return {\n error: {\n ok: false,\n reason: \"unresolved_template\",\n expression: resolvedValue.expression,\n },\n };\n }\n\n // (b) Dispatch verb.\n let output: unknown;\n try {\n output = await verbDispatcher(\n step.verb,\n resolvedArgs.value as Record | undefined,\n { value: resolvedValue.value },\n deps,\n { stepAlias: step.as, timeoutSeconds: deps.stepTimeoutSeconds },\n );\n } catch (err) {\n writeAuditRow(deps, contractName, step);\n const cause = errorMessage(err);\n return {\n error: {\n ok: false,\n reason: \"assembly_step_failed\",\n step_alias: step.as,\n cause,\n },\n };\n }\n\n // (c) Write audit row.\n writeAuditRow(deps, contractName, step);\n\n // (d) If the dispatcher returned a structured error envelope, surface it.\n if (\n output !== null &&\n typeof output === \"object\" &&\n \"ok\" in (output as Record) &&\n (output as { ok: boolean }).ok === false\n ) {\n // The dispatcher emits one of:\n // - {ok:false, reason:\"verb_not_available\", verb}\n // - {ok:false, reason:\"mcp_client_unavailable\", verb, client_name}\n // - {ok:false, reason:\"assembly_step_failed\", step_alias, cause}\n // All three are valid InstantiateError reasons.\n return { error: output as InstantiateError };\n }\n\n return { value: output };\n}\n\nfunction writeAuditRow(deps: InstantiateDeps, contractName: string, step: ContractStep): void {\n recordContractStep(deps, {\n contract: contractName,\n verb: step.verb,\n step_alias: step.as,\n vault: deps.vault.config.name,\n });\n}\n","/**\n * describeContract — Phase 6 / CON-05 / Q-DESCRIBE.\n *\n * Pure function over `ParsedContract` returning the contract's input\n * JSON Schema + an auto-generated markdown summary. Used by the\n * `describe_contract` MCP tool so agents can discover what a contract\n * does before instantiating it.\n *\n * # Output\n *\n * { ok: true,\n * json_schema: ,\n * summary: }\n * | { ok: false, reason: \"unknown_contract\", name }\n *\n * The summary contains the headings in RESEARCH §Q-DESCRIBE order:\n * `## Inputs`, `## Sources`, `## Sinks`, `## Assembly`, `## write_back`,\n * `## Output Shape`. Sections with no content are omitted. The\n * `Assembly` section renders steps as a numbered list — agents (and\n * humans) consume this directly without parsing the YAML.\n *\n * # Adapter-seam discipline\n *\n * Zero `fs` / `path` / `gray-matter` / `chokidar` / `yaml` imports.\n * Pure function over an in-memory ParsedContract.\n */\n\nimport type { ContractRegistry } from \"./registry.js\";\nimport type { ParsedContract } from \"./types.js\";\n\n/**\n * Plain-language gloss for each baseline assembly verb, so the rendered\n * `## Assembly` section reads as steps a non-technical user can follow —\n * not bare function names. Keyed by the 11 baseline verbs (src/contracts/\n * schema.ts BASELINE_VERBS). `literal` and `mcp://…` peer verbs fall back\n * to a generic gloss.\n */\nconst VERB_GLOSS: Record = {\n read_note: \"Read a note's content\",\n search_hybrid: \"Search the vault (semantic + keyword)\",\n search_sections: \"Search for matching sections within notes\",\n query_frontmatter: \"Find notes by their properties (frontmatter)\",\n expand: \"Gather notes linked to the starting note (follow the graph)\",\n cluster: \"Group the gathered notes into related communities\",\n recall: \"Recall earlier agent observations from memory\",\n compile_brief: \"Compile the gathered notes into a brief\",\n get_brief: \"Fetch an already-compiled brief\",\n list_backlinks: \"List notes that link back to this one\",\n get_outline: \"Read a note's heading outline\",\n};\n\nfunction glossFor(verb: string): string {\n if (VERB_GLOSS[verb]) return VERB_GLOSS[verb]!;\n if (verb === \"literal\") return \"Use a fixed inline value\";\n if (verb.startsWith(\"mcp://\")) return `Call an external tool (${verb})`;\n return verb;\n}\n\nexport interface DescribeDeps {\n registry: ContractRegistry;\n}\n\nexport interface DescribeArgs {\n name: string;\n}\n\nexport type DescribeResult =\n | { ok: true; json_schema: object; summary: string }\n | { ok: false; reason: \"unknown_contract\"; name: string };\n\nexport function describeContract(deps: DescribeDeps, args: DescribeArgs): DescribeResult {\n const parsed = deps.registry.get(args.name);\n if (!parsed) return { ok: false, reason: \"unknown_contract\", name: args.name };\n return {\n ok: true,\n json_schema: parsed.inputJsonSchema,\n summary: renderSummary(parsed),\n };\n}\n\nfunction renderSummary(parsed: ParsedContract): string {\n const lines: string[] = [];\n lines.push(`# ${parsed.name}`);\n lines.push(\"\");\n if (parsed.description) {\n lines.push(parsed.description);\n lines.push(\"\");\n }\n\n // ## Inputs\n if (Object.keys(parsed.inputs).length > 0) {\n lines.push(\"## Inputs\");\n for (const [name, spec] of Object.entries(parsed.inputs)) {\n const s = (spec ?? {}) as Record;\n const type =\n typeof s.type === \"string\"\n ? s.type\n : typeof s[\"$ref\"] === \"string\"\n ? `\\`${String(s[\"$ref\"])}\\``\n : \"any\";\n const required = parsed.required.includes(name) ? \"required\" : \"optional\";\n const desc = typeof s.description === \"string\" ? s.description : \"\";\n const descSuffix = desc ? `: ${desc}` : \"\";\n lines.push(`- **${name}** (${type}, ${required})${descSuffix}`);\n }\n lines.push(\"\");\n }\n\n // ## Sources\n if (Object.keys(parsed.sources).length > 0) {\n lines.push(\"## Sources\");\n for (const [handle, decl] of Object.entries(parsed.sources)) {\n const req = decl.required ? \"required\" : \"optional\";\n lines.push(`- **${handle}** → \\`${decl.handle}\\` (${req})`);\n }\n lines.push(\"\");\n }\n\n // ## Sinks\n if (Object.keys(parsed.sinks).length > 0) {\n lines.push(\"## Sinks\");\n for (const [handle, decl] of Object.entries(parsed.sinks)) {\n const req = decl.required ? \"required\" : \"optional\";\n lines.push(`- **${handle}** → \\`${decl.handle}\\` (${req} MemorySink)`);\n }\n lines.push(\"\");\n }\n\n // ## Assembly — rendered as plain-language steps so a non-technical user\n // can follow what the contract does, with the verb + arg keys kept inline\n // for agents/authors who want the precise call.\n if (parsed.assembly.length > 0) {\n lines.push(\"## Assembly\");\n parsed.assembly.forEach((step, i) => {\n const argsRender = step.args ? `(${Object.keys(step.args).join(\", \")})` : \"()\";\n lines.push(\n `${i + 1}. **${step.as}** — ${glossFor(step.verb)} _(\\`${step.verb}${argsRender}\\`)_`,\n );\n });\n lines.push(\"\");\n }\n\n // ## write_back\n if (parsed.write_back) {\n lines.push(\"## write_back\");\n lines.push(\n `Writes a ${parsed.write_back.document_kind} document to \\`${parsed.write_back.sink}\\` ` +\n `with body from \\`${parsed.write_back.body_from}\\`.`,\n );\n lines.push(\"\");\n }\n\n // ## Output Shape\n if (parsed.output_shape) {\n lines.push(\"## Output Shape\");\n const props = ((parsed.output_shape as { properties?: Record }).properties ??\n {}) as Record;\n const compact = Object.entries(props)\n .map(([k, v]) => {\n const o = (v ?? {}) as { type?: string; $ref?: string };\n const t = typeof o.type === \"string\" ? o.type : (o.$ref ?? \"any\");\n return `${k}: ${t}`;\n })\n .join(\", \");\n lines.push(`\\`{${compact}}\\``);\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\").trim() + \"\\n\";\n}\n","/**\n * Contract MCP Resources — Plan 06-04 / CON-04 + D-A2b.\n *\n * Two pure read-only Resource handlers; both registered in\n * `src/server.ts` via `server.registerResource(...)`. Resources do NOT\n * count toward the REL-08 tool budget per Phase 5 BRF-09 precedent.\n *\n * - `readListContracts(deps, opts?)` — projects the per-vault\n * `ContractRegistry` into `{total, contracts: [{name, description,\n * vault, source_count, sink_count, write_back: boolean}]}`. Optional\n * `opts.source` filters to contracts whose ANY declared source's\n * handle starts with the given prefix.\n *\n * - `readListContractVerbs(deps)` — returns\n * `{baseline: [<11 verbs>], custom: [{verb, declared_in,\n * used_by_contracts, invocation_count, last_seen}]}`. The baseline\n * set is constant (ADR-006 §Decision 3). The `custom` entries are\n * computed from `contract_audit.aggregateVerbUsage(vault)` filtered\n * to `mcp://` verbs. `used_by_contracts` is derived by scanning\n * `contract_audit.listByKind('contract_step', {vault})` for distinct\n * contract names per verb (no schema change needed).\n *\n * # Adapter-seam discipline\n *\n * Zero `fs`/`path.join`/`gray-matter`/`chokidar`/`yaml` imports — pure\n * data projection over the registry + DB query interface.\n */\n\nimport type { ContractRegistry } from \"./registry.js\";\nimport type { ContractAuditQueries } from \"../db/queries/contract-audit.js\";\n\n// ─────────────────────────────────────────────────────────────────────────\n// list_contracts (CON-04)\n// ─────────────────────────────────────────────────────────────────────────\n\nexport interface ListContractsDeps {\n registry: ContractRegistry;\n vaultName: string;\n}\n\nexport interface ListContractsOpts {\n /** Filter to contracts whose ANY source handle starts with this prefix. */\n source?: string;\n}\n\nexport interface ListContractsEntry {\n name: string;\n description: string;\n vault: string;\n source_count: number;\n sink_count: number;\n write_back: boolean;\n}\n\nexport interface ListContractsResource {\n total: number;\n contracts: ListContractsEntry[];\n}\n\nexport function readListContracts(\n deps: ListContractsDeps,\n opts: ListContractsOpts = {},\n): ListContractsResource {\n const out: ListContractsEntry[] = [];\n for (const [name, parsed] of deps.registry.entries()) {\n if (opts.source !== undefined) {\n const anyMatch = Object.values(parsed.sources).some((s) => s.handle.startsWith(opts.source!));\n if (!anyMatch) continue;\n }\n out.push({\n name,\n description: parsed.description,\n vault: deps.vaultName,\n source_count: Object.keys(parsed.sources).length,\n sink_count: Object.keys(parsed.sinks).length,\n write_back: parsed.write_back !== undefined,\n });\n }\n return { total: out.length, contracts: out };\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// list_contract_verbs (D-A2b)\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * The 11 baseline verbs per ADR-006 §Decision 3. `literal` is\n * intentionally NOT in this list — it's an escape-hatch, not a callable\n * verb usable by promotion signal aggregation.\n */\nexport const BASELINE_VERBS: readonly string[] = Object.freeze([\n \"search_hybrid\",\n \"expand\",\n \"cluster\",\n \"recall\",\n \"compile_brief\",\n \"get_brief\",\n \"query_frontmatter\",\n \"list_backlinks\",\n \"get_outline\",\n \"search_sections\",\n \"read_note\",\n]);\n\nexport interface ListContractVerbsDeps {\n contractAudit: ContractAuditQueries;\n vaultName: string;\n}\n\nexport interface ListContractVerbsEntry {\n verb: string;\n declared_in: string;\n used_by_contracts: string[];\n invocation_count: number;\n last_seen: number;\n}\n\nexport interface ListContractVerbsResource {\n baseline: readonly string[];\n custom: ListContractVerbsEntry[];\n}\n\nexport function readListContractVerbs(deps: ListContractVerbsDeps): ListContractVerbsResource {\n const usage = deps.contractAudit.aggregateVerbUsage(deps.vaultName);\n // List ALL `contract_step` rows once and reduce in-process so the\n // `used_by_contracts` join is O(N) without adding a SQL helper.\n // Larger budget than aggregateVerbUsage covers — verbs with high\n // invocation_count will appear repeatedly in the rows but we group\n // them via a Map.\n const rows = deps.contractAudit.listByKind(\"contract_step\", {\n vault: deps.vaultName,\n limit: 10_000,\n });\n const verbToContracts = new Map>();\n for (const r of rows) {\n if (r.verb === undefined || r.contract === undefined) continue;\n if (!verbToContracts.has(r.verb)) verbToContracts.set(r.verb, new Set());\n verbToContracts.get(r.verb)!.add(r.contract);\n }\n\n const custom = usage\n .filter((u) => u.verb.startsWith(\"mcp://\"))\n .map(\n (u): ListContractVerbsEntry => ({\n verb: u.verb,\n declared_in: extractDeclaredIn(u.verb),\n used_by_contracts: Array.from(verbToContracts.get(u.verb) ?? []).sort(),\n invocation_count: u.invocation_count,\n last_seen: u.last_seen,\n }),\n );\n\n return { baseline: BASELINE_VERBS, custom };\n}\n\nfunction extractDeclaredIn(verb: string): string {\n const m = verb.match(/^mcp:\\/\\/([a-z][a-z0-9_-]*)\\//);\n return m ? `[contracts.mcp_clients.${m[1]}]` : \"[contracts.mcp_clients]\";\n}\n","/**\n * Sources MCP Resources — SOURCES-REGISTRY.md §5 (Stage 2).\n *\n * Three pure read-only projections over the live `PeerMcpRegistry`.\n * Registered in `src/server.ts` via `server.registerResource(...)`.\n * Resources do NOT count toward the REL-08 tool budget (Phase 5 BRF-09\n * precedent, same as the contracts/contract-verbs resources).\n *\n * - `readListSources(reg)` — `{sources: [{name, transport, command,\n * args, status, tool_count, last_refreshed, error?}]}`. The host\n * (vault-memory itself) is NOT included — the plugin prepends it as\n * a synthetic entry. `env` is intentionally omitted (may hold\n * secrets; SOURCES-REGISTRY.md §5.1).\n *\n * - `readSourceTools(reg, name)` — `{name, status, last_refreshed,\n * tools: [...]}`. `tools` is the cached tools/list payload; `[]` when\n * the source is not connected.\n *\n * - `readSourceTool(reg, name, tool)` — a single tool's schema, inlined\n * from the cached list (no extra peer call). `{found:false}` when the\n * source or tool is unknown.\n *\n * The registry does not retain per-source config beyond what it was\n * started/added with, so `command`/`args`/`transport` are accepted as a\n * lookup map passed alongside the registry (server threads the live\n * `config.contracts.mcp_clients` plus any runtime-added entries).\n *\n * # Adapter-seam discipline\n *\n * Zero fs/path/yaml/chokidar imports — pure data projection over the\n * registry interface + a plain config map.\n */\n\nimport type { PeerMcpRegistry, PeerMcpStatus, PeerMcpTool } from \"./mcp-clients.js\";\n\n/** Connection/transport metadata for one source (config-derived). */\nexport interface SourceConfigMeta {\n command: string;\n args: readonly string[];\n}\n\nexport interface ListSourcesEntry {\n name: string;\n transport: \"stdio\";\n command: string;\n args: readonly string[];\n status: PeerMcpStatus;\n tool_count: number;\n last_refreshed: number | null;\n error?: string;\n}\n\nexport interface ListSourcesResource {\n sources: ListSourcesEntry[];\n}\n\n/**\n * Project every registered source into the list shape. `configMeta`\n * supplies command/args per source name; sources missing from the map\n * fall back to empty command/args (still listed — the registry is\n * authoritative for existence).\n */\nexport function readListSources(\n reg: PeerMcpRegistry,\n configMeta: Record,\n): ListSourcesResource {\n const sources: ListSourcesEntry[] = [];\n for (const name of reg.names()) {\n const info = reg.getInfo(name);\n if (info === undefined) continue;\n const meta = configMeta[name];\n const entry: ListSourcesEntry = {\n name,\n transport: \"stdio\",\n command: meta?.command ?? \"\",\n args: meta?.args ?? [],\n status: info.status,\n tool_count: info.tools.length,\n last_refreshed: info.lastRefreshed,\n };\n if (info.error !== undefined) entry.error = info.error;\n sources.push(entry);\n }\n return { sources };\n}\n\nexport interface SourceToolsResource {\n name: string;\n status: PeerMcpStatus;\n last_refreshed: number | null;\n tools: readonly PeerMcpTool[];\n error?: string;\n}\n\n/** Per-source cached tools/list. `{error}` carries the unknown-source case. */\nexport function readSourceTools(\n reg: PeerMcpRegistry,\n name: string,\n): SourceToolsResource | { error: string } {\n const info = reg.getInfo(name);\n if (info === undefined) {\n return { error: `unknown source: ${name}` };\n }\n const out: SourceToolsResource = {\n name,\n status: info.status,\n last_refreshed: info.lastRefreshed,\n tools: info.tools,\n };\n if (info.error !== undefined) out.error = info.error;\n return out;\n}\n\nexport interface SourceToolResource {\n found: true;\n name: string;\n tool: PeerMcpTool;\n}\n\n/** A single tool's schema, inlined from the cache. */\nexport function readSourceTool(\n reg: PeerMcpRegistry,\n name: string,\n toolName: string,\n): SourceToolResource | { found: false; error: string } {\n const info = reg.getInfo(name);\n if (info === undefined) {\n return { found: false, error: `unknown source: ${name}` };\n }\n const tool = info.tools.find((t) => t.name === toolName);\n if (tool === undefined) {\n return { found: false, error: `unknown tool: ${name}/${toolName}` };\n }\n return { found: true, name, tool };\n}\n","/**\n * src/contracts barrel — Plans 06-01 / 06-02 / 06-03 surface.\n *\n * Plan 06-04 adds: resources (vault-memory://contract-verbs/{vault})\n * and the reference-contracts test fixtures.\n */\n\nexport type {\n AssemblyVerb,\n ContractStep,\n ContractHandleDecl,\n ContractSourceDecl,\n ContractSinkDecl,\n WriteBackSpec,\n ContractInputs,\n ParsedContract,\n OverrideMap,\n InstantiateError,\n ContractAuditRow,\n} from \"./types.js\";\nexport { CONTRACT_PATH_REGEX } from \"./types.js\";\n\nexport { TYPES_CATALOG } from \"./types-catalog.js\";\nexport { resolveRefs } from \"./json-schema-ref.js\";\nexport { buildInputSchema, type BuiltInputSchema } from \"./input-schema.js\";\nexport { ContractRegistry, type RegistrySetResult } from \"./registry.js\";\nexport { slugify } from \"./slug.js\";\nexport {\n recordContractStep,\n recordContractLoadError,\n aggregateVerbUsage,\n type ContractAuditDeps,\n type RecordContractStepArgs,\n type RecordContractLoadErrorArgs,\n type VerbUsageRow,\n} from \"./audit.js\";\nexport { ContractFileSchema, type ContractFileShape } from \"./schema.js\";\nexport {\n startContractRegistry,\n type StartContractRegistryOpts,\n type StartedContractRegistry,\n type RegistryChangeKind,\n} from \"./loader.js\";\nexport { syncAutoRegistered, type SyncAutoRegisteredOpts } from \"./auto-register.js\";\nexport { resolveTemplate, type TemplateBindings, type TemplateResolveResult } from \"./templates.js\";\nexport {\n PeerMcpRegistry,\n type PeerMcpClient,\n type PeerMcpClientConfig,\n type ClientFactory,\n} from \"./mcp-clients.js\";\nexport { verbDispatcher, type VerbDeps, type VerbDispatchOpts } from \"./verbs/index.js\";\nexport { callMcpVerb } from \"./verbs/mcp-extension.js\";\nexport {\n instantiateContract,\n type InstantiateDeps,\n type InstantiateArgs,\n type InstantiateBundle,\n type InstantiateResult,\n} from \"./instantiate.js\";\nexport {\n describeContract,\n type DescribeDeps,\n type DescribeArgs,\n type DescribeResult,\n} from \"./describe.js\";\nexport {\n readListContracts,\n readListContractVerbs,\n BASELINE_VERBS,\n type ListContractsDeps,\n type ListContractsOpts,\n type ListContractsEntry,\n type ListContractsResource,\n type ListContractVerbsDeps,\n type ListContractVerbsEntry,\n type ListContractVerbsResource,\n} from \"./resources.js\";\n\nexport {\n readListSources,\n readSourceTools,\n readSourceTool,\n type SourceConfigMeta,\n type ListSourcesEntry,\n type ListSourcesResource,\n type SourceToolsResource,\n type SourceToolResource,\n} from \"./sources-resources.js\";\n\nexport type { PeerMcpTool, PeerMcpStatus, PeerMcpClientInfo } from \"./mcp-clients.js\";\n","/**\n * Audit log + index-run reporting — user-facing layer.\n *\n * Thin wrapper over `AuditQueries` that enriches raw audit rows with\n * note-path / note-title context (best effort — null if the note has\n * been hard-deleted from the `notes` table).\n *\n * See `./README.md` for the audit + permission semantics.\n */\n\nimport type { Vault } from \"../vault/index.js\";\nimport type { ListWritesFilter } from \"../db/queries/audit.js\";\n\nconst DEFAULT_AUDIT_LIMIT = 50;\nconst MAX_AUDIT_LIMIT = 1000;\nconst DEFAULT_RUNS_LIMIT = 20;\nconst MAX_RUNS_LIMIT = 200;\n\nexport interface AuditLogEntry {\n /** Write event id (sortable, monotonically increasing). */\n id: number;\n /** Note path (relative to vault root), or null if note was hard-deleted. */\n notePath: string | null;\n /** Note title at time of write — best-effort, may be null if deleted. */\n noteTitle: string | null;\n op: \"create\" | \"update\" | \"delete\";\n previousHash: string | null;\n newHash: string | null;\n /** Hash the writer expected on disk; mismatch = conflict prevention triggered. */\n expectedHash: string | null;\n clientId: string | null;\n diffSummary: string | null;\n /** Epoch ms. */\n at: number;\n /**\n * Plan 02-06 (MEM-08): true iff this write was routed under a configured\n * MemorySink (agent observation, supersede). False for regular user writes\n * and for any audit row predating migration 009 (those rows surface as\n * `false` per the column default). Filter via the `is_memory_sink_write`\n * filter on `getAuditLog` / the `audit_log` MCP tool.\n */\n is_memory_sink_write: boolean;\n}\n\nexport interface IndexRunEntry {\n runId: string;\n vaultName: string;\n modelName: string | null;\n trigger: string;\n startedAt: number;\n finishedAt: number | null;\n durationMs: number | null;\n notesIndexed: number;\n notesUpdated: number;\n notesDeleted: number;\n chunksCreated: number;\n error: string | null;\n}\n\nexport interface GetAuditLogInput {\n vault: Vault;\n notePath?: string;\n op?: \"create\" | \"update\" | \"delete\";\n /** Epoch ms — only entries at or after this timestamp. */\n since?: number;\n limit?: number;\n /**\n * Plan 02-06 (MEM-08): when set, restricts the result to memory-sink\n * writes (`true`) or non-memory writes (`false`). When omitted, both\n * kinds are included — preserves the v1 audit_log default behavior.\n */\n is_memory_sink_write?: boolean;\n}\n\nexport interface GetIndexRunsInput {\n vault: Vault;\n limit?: number;\n}\n\nfunction clampLimit(value: number | undefined, fallback: number, max: number): number {\n if (value === undefined) return fallback;\n if (!Number.isFinite(value) || value <= 0) return fallback;\n const n = Math.floor(value);\n return n > max ? max : n;\n}\n\nexport function getAuditLog(input: GetAuditLogInput): AuditLogEntry[] {\n const { vault } = input;\n const limit = clampLimit(input.limit, DEFAULT_AUDIT_LIMIT, MAX_AUDIT_LIMIT);\n\n const filter: ListWritesFilter = { limit };\n\n if (input.notePath !== undefined) {\n const note = vault.db.notes.getByPath(input.notePath);\n if (!note) return [];\n filter.noteId = note.id;\n }\n if (input.op !== undefined) filter.op = input.op;\n if (input.since !== undefined) filter.since = input.since;\n if (input.is_memory_sink_write !== undefined) {\n filter.isMemorySinkWrite = input.is_memory_sink_write;\n }\n\n const rows = vault.db.audit.listWrites(filter);\n\n return rows.map((row): AuditLogEntry => {\n const note = vault.db.notes.getById(row.note_id);\n return {\n id: row.id,\n notePath: note?.path ?? null,\n noteTitle: note?.title ?? null,\n op: row.op,\n previousHash: row.previous_hash,\n newHash: row.new_hash,\n expectedHash: row.expected_hash,\n clientId: row.client_id,\n diffSummary: row.diff_summary,\n at: row.at,\n // SQLite returns the column as 0 | 1; convert to JS boolean at the\n // audit-layer boundary so callers (MCP audit_log + tests) see the\n // documented `is_memory_sink_write: boolean` shape.\n is_memory_sink_write: row.is_memory_sink_write === 1,\n };\n });\n}\n\nexport function getIndexRuns(input: GetIndexRunsInput): IndexRunEntry[] {\n const { vault } = input;\n const limit = clampLimit(input.limit, DEFAULT_RUNS_LIMIT, MAX_RUNS_LIMIT);\n\n const rows = vault.db.audit.listRuns(limit);\n\n return rows.map((row): IndexRunEntry => {\n let modelName: string | null = null;\n if (row.model_id !== null) {\n const all = vault.db.models.listAll();\n const found = all.find((m) => m.id === row.model_id);\n modelName = found?.name ?? null;\n }\n const durationMs = row.finished_at !== null ? row.finished_at - row.started_at : null;\n return {\n runId: row.run_id,\n vaultName: row.vault_name,\n modelName,\n trigger: row.trigger,\n startedAt: row.started_at,\n finishedAt: row.finished_at,\n durationMs,\n notesIndexed: row.notes_indexed,\n notesUpdated: row.notes_updated,\n notesDeleted: row.notes_deleted,\n chunksCreated: row.chunks_created,\n error: row.error,\n };\n });\n}\n","export { getAuditLog, getIndexRuns } from \"./audit.js\";\nexport type { AuditLogEntry, IndexRunEntry, GetAuditLogInput, GetIndexRunsInput } from \"./audit.js\";\n","/**\n * Vault-domain MCP handler factory.\n *\n * Tools: list_vaults, vault_stats, recent_notes, audit_log, list_models,\n * start_shadow_index, switch_active_model, vacuum_embeddings, index_runs.\n *\n * Extracted verbatim from the inline `handlers` literal + standalone\n * `handle*` functions in `src/server.ts`. Behavior-neutral: each arrow\n * maps the same args to the same domain call, now closing over `deps.*`\n * instead of `serve()` locals.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. SQLite access\n * is via the `vault.db` query namespaces (L0 substrate), not raw fs.\n */\n\nimport type { VaultManager } from \"../../vault/index.js\";\nimport { aggregateTopTags, aggregateTopFrontmatterKeys } from \"../utils.js\";\nimport {\n listModels,\n startShadowIndex,\n switchActiveModel,\n vacuumEmbeddings,\n} from \"../../indexer/index.js\";\nimport { getAuditLog, getIndexRuns } from \"../../audit/index.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nexport function handleListVaults(manager: VaultManager): object {\n const vaults = manager.list().map((v) => {\n const noteCount = v.db.notes.countAll();\n const runs = v.db.audit.listRuns(1);\n const lastRun = runs[0];\n return {\n name: v.config.name,\n path: v.config.path,\n embedding_model: v.config.embedding_model ?? null,\n note_count: noteCount,\n write_enabled: v.config.write_enabled ?? false,\n last_run: lastRun\n ? {\n run_id: lastRun.run_id,\n started_at: lastRun.started_at,\n finished_at: lastRun.finished_at,\n error: lastRun.error,\n }\n : null,\n };\n });\n return { vaults, count: vaults.length };\n}\n\ninterface VaultStatsRow {\n vault: string;\n vault_path: string;\n total_notes: number;\n total_words: number;\n embedding_model: string | null;\n indexed_at: number | null;\n top_tags: Array<{ tag: string; count: number }>;\n top_frontmatter_keys: Array<{ key: string; count: number }>;\n}\n\nexport function handleVaultStats(manager: VaultManager, vaultFilter: string | undefined): object {\n const targets = vaultFilter ? [manager.require(vaultFilter)] : manager.list();\n\n const stats: VaultStatsRow[] = targets.map((v) => {\n const total_notes = v.db.notes.countAll();\n const wordRow = v.db.handle\n .prepare<[], { total: number | null }>(\"SELECT SUM(word_count) AS total FROM notes\")\n .get();\n const lastRun = v.db.audit.listRuns(1)[0];\n const activeModel = v.db.models.getActive();\n\n return {\n vault: v.config.name,\n vault_path: v.config.path,\n total_notes,\n total_words: wordRow?.total ?? 0,\n embedding_model: activeModel?.name ?? v.config.embedding_model ?? null,\n indexed_at: lastRun?.finished_at ?? null,\n top_tags: aggregateTopTags(v.db.handle, 10),\n top_frontmatter_keys: aggregateTopFrontmatterKeys(v.db.handle, 10),\n };\n });\n\n if (vaultFilter) {\n // `targets` is non-empty when vaultFilter is set, because manager.require\n // throws on miss — so stats[0] is guaranteed. The assertion narrows the\n // type for the caller.\n return stats[0] as VaultStatsRow;\n }\n return { vaults: stats, count: stats.length };\n}\n\ninterface RecentNoteRow {\n vault: string;\n path: string;\n title: string | null;\n mtime: number;\n word_count: number | null;\n tags: string[] | null;\n}\n\nexport function handleRecentNotes(\n manager: VaultManager,\n vaultFilter: string | undefined,\n limit: number,\n since: number | undefined,\n): object {\n const targets = vaultFilter ? [manager.require(vaultFilter)] : manager.list();\n\n const all: RecentNoteRow[] = [];\n for (const v of targets) {\n const rows =\n since !== undefined\n ? v.db.handle\n .prepare<\n [number, number],\n {\n path: string;\n title: string | null;\n mtime: number;\n word_count: number | null;\n frontmatter: string | null;\n }\n >(\n \"SELECT path, title, mtime, word_count, frontmatter FROM notes WHERE mtime > ? ORDER BY mtime DESC LIMIT ?\",\n )\n .all(since, limit)\n : v.db.handle\n .prepare<\n [number],\n {\n path: string;\n title: string | null;\n mtime: number;\n word_count: number | null;\n frontmatter: string | null;\n }\n >(\n \"SELECT path, title, mtime, word_count, frontmatter FROM notes ORDER BY mtime DESC LIMIT ?\",\n )\n .all(limit);\n\n for (const r of rows) {\n let tags: string[] | null = null;\n if (r.frontmatter) {\n try {\n const fm = JSON.parse(r.frontmatter) as { tags?: unknown };\n if (Array.isArray(fm.tags)) {\n tags = fm.tags.filter((t): t is string => typeof t === \"string\");\n }\n } catch {\n // ignore\n }\n }\n all.push({\n vault: v.config.name,\n path: r.path,\n title: r.title,\n mtime: r.mtime,\n word_count: r.word_count,\n tags,\n });\n }\n }\n\n // Cross-vault merge: re-sort by mtime and trim.\n all.sort((a, b) => b.mtime - a.mtime);\n return { notes: all.slice(0, limit), count: Math.min(all.length, limit) };\n}\n\nexport function makeVaultHandlers(deps: HandlerDeps): Partial> {\n const { manager, ollama } = deps;\n return {\n list_vaults: async () => handleListVaults(manager),\n vault_stats: async (a) => {\n const p = a as { vault?: string };\n return handleVaultStats(manager, p.vault);\n },\n recent_notes: async (a) => {\n const p = a as { vault?: string; limit: number; since?: number };\n return handleRecentNotes(manager, p.vault, p.limit, p.since);\n },\n audit_log: async (a) => {\n const p = a as {\n vault: string;\n note_path?: string;\n op?: \"create\" | \"update\" | \"delete\";\n since?: number;\n limit: number;\n is_memory_sink_write?: boolean;\n };\n const vault = manager.require(p.vault);\n // Plan 02-06 (MEM-08): the new optional filter is purely additive.\n // Omitting it preserves Phase 1 behavior (include all rows).\n const entries = getAuditLog({\n vault,\n notePath: p.note_path,\n op: p.op,\n since: p.since,\n limit: p.limit,\n ...(p.is_memory_sink_write !== undefined\n ? { is_memory_sink_write: p.is_memory_sink_write }\n : {}),\n });\n return { entries, count: entries.length };\n },\n list_models: async (a) => {\n const p = a as { vault: string };\n const vault = manager.require(p.vault);\n const models = listModels(vault);\n return { models, count: models.length };\n },\n start_shadow_index: async (a) => {\n const p = a as { vault: string; model: string; batch_size?: number };\n const vault = manager.require(p.vault);\n return startShadowIndex({\n vault,\n model: p.model,\n ollama,\n batchSize: p.batch_size,\n log: (m) => process.stderr.write(`[shadow:${vault.config.name}] ${m}\\n`),\n });\n },\n switch_active_model: async (a) => {\n const p = a as { vault: string; model_name: string };\n const vault = manager.require(p.vault);\n return switchActiveModel(vault, p.model_name);\n },\n vacuum_embeddings: async (a) => {\n const p = a as { vault: string };\n const vault = manager.require(p.vault);\n return vacuumEmbeddings(vault);\n },\n index_runs: async (a) => {\n const p = a as { vault: string; limit: number };\n const vault = manager.require(p.vault);\n const runs = getIndexRuns({ vault, limit: p.limit });\n return { runs, count: runs.length };\n },\n };\n}\n","/**\n * Folder-convention learner.\n *\n * For a given vault-relative path, gather frontmatter conventions from\n * sibling notes (same folder prefix). The learner emits per-key:\n * - presence prevalence (how many sibling notes have the key)\n * - dominant value (if any single value covers >50% of populated notes)\n *\n * SQL-only — no embeddings, no LLM. Fast.\n *\n * Fallback: when the immediate folder has <3 sibling notes, the learner\n * walks UP one path segment (e.g. `Intelligence Impact/INIM-BDEV/Meetings/`\n * falls back to `Intelligence Impact/INIM-BDEV/`) until it finds enough\n * siblings OR reaches the vault root. This prevents the \"single note in a\n * new folder gets no suggestions\" failure mode.\n */\n\nimport type { Vault } from \"../vault/index.js\";\n\n/**\n * A single per-key inference result from the folder layer.\n */\nexport interface FolderConventionEntry {\n /** Frontmatter key name (e.g. \"class\", \"status\", \"tags\"). */\n key: string;\n /** Number of sibling notes (in the resolved folder) that have this key. */\n presenceCount: number;\n /** Total sibling notes in the resolved folder (denominator). */\n siblingCount: number;\n /** Presence ratio: presenceCount / siblingCount. */\n prevalence: number;\n /**\n * If a single value covers >50% of notes-with-this-key, the dominant\n * value. Otherwise null (split inference — no value, just the key).\n * Stored as JSON-typed: string, number, boolean, or array of strings\n * for the `tags` case.\n */\n dominantValue: unknown | null;\n /** Coverage of the dominant value among notes-with-this-key. */\n dominantValueRatio: number;\n}\n\n/**\n * The resolved folder used for the inference, plus the entries.\n * `resolvedFolder` may not be the original note's immediate folder —\n * see fallback rules above.\n */\nexport interface FolderConventionResult {\n resolvedFolder: string;\n siblingCount: number;\n fellBackFrom: string | null;\n entries: FolderConventionEntry[];\n}\n\n/** Minimum sibling notes required before we trust folder inference. */\nconst MIN_SIBLINGS = 3;\n\n/** Maximum levels to walk up before giving up. */\nconst MAX_FALLBACK_LEVELS = 4;\n\n/**\n * Resolve the folder for a given vault-relative note path.\n *\n * - `Personen/Joerg.md` → `Personen/`\n * - `Intelligence Impact/INIM-BDEV/Meetings/2026-05-12.md`\n * → `Intelligence Impact/INIM-BDEV/Meetings/`\n * - `note-at-root.md` → `\"\"` (the vault root)\n */\nexport function folderOf(notePath: string): string {\n const idx = notePath.lastIndexOf(\"/\");\n return idx === -1 ? \"\" : notePath.slice(0, idx + 1);\n}\n\n/**\n * Walk up one folder level. `Foo/Bar/` → `Foo/`. `Foo/` → `\"\"`. `\"\"` → null.\n */\nfunction parentFolder(folder: string): string | null {\n if (folder === \"\") return null;\n const trimmed = folder.endsWith(\"/\") ? folder.slice(0, -1) : folder;\n const idx = trimmed.lastIndexOf(\"/\");\n if (idx === -1) return \"\";\n return trimmed.slice(0, idx + 1);\n}\n\ninterface SiblingRow {\n path: string;\n frontmatter: string | null;\n}\n\n/**\n * Count sibling notes (any path starting with `folder`, excluding the\n * input note itself when applicable). Empty folder string means vault root.\n */\nfunction countSiblings(vault: Vault, folder: string, excludePath: string | null): number {\n const handle = vault.db.handle;\n if (folder === \"\") {\n // Vault root: notes with no `/` in path. The simplest reliable filter.\n const row = handle\n .prepare<\n [string | null],\n { c: number }\n >(\"SELECT COUNT(*) AS c FROM notes WHERE instr(path, '/') = 0 AND path != COALESCE(?, '')\")\n .get(excludePath);\n return row?.c ?? 0;\n }\n const row = handle\n .prepare<\n [string, string | null],\n { c: number }\n >(\"SELECT COUNT(*) AS c FROM notes WHERE path LIKE ? || '%' AND path != COALESCE(?, '')\")\n .get(folder, excludePath);\n return row?.c ?? 0;\n}\n\nfunction fetchSiblings(vault: Vault, folder: string, excludePath: string | null): SiblingRow[] {\n const handle = vault.db.handle;\n if (folder === \"\") {\n return handle\n .prepare<\n [string | null],\n SiblingRow\n >(\"SELECT path, frontmatter FROM notes WHERE instr(path, '/') = 0 AND path != COALESCE(?, '')\")\n .all(excludePath);\n }\n return handle\n .prepare<\n [string, string | null],\n SiblingRow\n >(\"SELECT path, frontmatter FROM notes WHERE path LIKE ? || '%' AND path != COALESCE(?, '')\")\n .all(folder, excludePath);\n}\n\n/**\n * Resolve the folder for inference, walking up if too few siblings.\n * Returns the chosen folder and the original folder (if different).\n */\nexport function resolveInferenceFolder(\n vault: Vault,\n notePath: string,\n excludePath: string | null = notePath,\n): { folder: string; fellBackFrom: string | null; siblingCount: number } {\n const start = folderOf(notePath);\n let current: string | null = start;\n let levels = 0;\n while (current !== null && levels < MAX_FALLBACK_LEVELS) {\n const count = countSiblings(vault, current, excludePath);\n if (count >= MIN_SIBLINGS || current === \"\") {\n return {\n folder: current,\n fellBackFrom: current === start ? null : start,\n siblingCount: count,\n };\n }\n current = parentFolder(current);\n levels++;\n }\n return { folder: \"\", fellBackFrom: start, siblingCount: 0 };\n}\n\n/**\n * Aggregate frontmatter keys + dominant values across a set of sibling rows.\n *\n * We tolerate dirty frontmatter (parse failures, primitives, nulls) without\n * crashing — same defensive posture as the v0.9.0 vault_stats aggregates.\n */\nfunction aggregateEntries(siblings: SiblingRow[]): FolderConventionEntry[] {\n const total = siblings.length;\n if (total === 0) return [];\n\n // For each key: count occurrences + collect values seen.\n const keyPresence = new Map();\n const keyValues = new Map>();\n\n for (const row of siblings) {\n if (!row.frontmatter) continue;\n let fm: unknown;\n try {\n fm = JSON.parse(row.frontmatter);\n } catch {\n continue;\n }\n if (!fm || typeof fm !== \"object\" || Array.isArray(fm)) continue;\n\n const obj = fm as Record;\n for (const [key, value] of Object.entries(obj)) {\n keyPresence.set(key, (keyPresence.get(key) ?? 0) + 1);\n // Normalize the value to a comparable string for the dominant-value\n // bucket. Arrays and objects get a deterministic JSON form so e.g.\n // `tags: [\"a\",\"b\"]` collides only with itself.\n const valKey = stableStringify(value);\n if (!keyValues.has(key)) keyValues.set(key, new Map());\n const bucket = keyValues.get(key)!;\n bucket.set(valKey, (bucket.get(valKey) ?? 0) + 1);\n }\n }\n\n const entries: FolderConventionEntry[] = [];\n for (const [key, presenceCount] of keyPresence) {\n const valueBucket = keyValues.get(key)!;\n const [domValStr, domCount] = pickDominant(valueBucket);\n const dominantValue = domCount / presenceCount > 0.5 ? safeParse(domValStr) : null;\n entries.push({\n key,\n presenceCount,\n siblingCount: total,\n prevalence: presenceCount / total,\n dominantValue,\n dominantValueRatio: domCount / presenceCount,\n });\n }\n\n // Sort by prevalence DESC, then key ASC for stable output.\n entries.sort((a, b) => {\n if (b.prevalence !== a.prevalence) return b.prevalence - a.prevalence;\n return a.key.localeCompare(b.key);\n });\n return entries;\n}\n\nfunction pickDominant(bucket: Map): [string, number] {\n let bestKey = \"\";\n let bestCount = 0;\n for (const [k, c] of bucket) {\n if (c > bestCount) {\n bestKey = k;\n bestCount = c;\n }\n }\n return [bestKey, bestCount];\n}\n\nfunction stableStringify(v: unknown): string {\n if (v === undefined) return \"null\";\n return JSON.stringify(v, Object.keys((v as object) ?? {}).sort());\n}\n\nfunction safeParse(s: string): unknown {\n try {\n return JSON.parse(s);\n } catch {\n return null;\n }\n}\n\n/**\n * Primary entry point. Returns folder-based frontmatter convention for\n * the input note (which may or may not yet exist in the DB — the path is\n * what matters).\n *\n * Pass `excludePath: null` when inferring for a brand-new note that isn't\n * indexed yet (so no sibling is wrongly skipped).\n */\nexport function inferFromFolder(\n vault: Vault,\n notePath: string,\n options: { excludePath?: string | null } = {},\n): FolderConventionResult {\n const excludePath = options.excludePath ?? notePath;\n const { folder, fellBackFrom, siblingCount } = resolveInferenceFolder(\n vault,\n notePath,\n excludePath,\n );\n const siblings = fetchSiblings(vault, folder, excludePath);\n return {\n resolvedFolder: folder,\n siblingCount,\n fellBackFrom,\n entries: aggregateEntries(siblings),\n };\n}\n","/**\n * Neighbor-based frontmatter inference.\n *\n * For a given note path, gather frontmatter conventions from the notes\n * directly linked to it — forward (notes this one points TO) and\n * backward (notes that link TO this one).\n *\n * Why this works: in a curated vault, a note's wikilink-neighborhood\n * carries semantic context that the folder may not. Example: a meeting\n * note `2026-05-12 Sondierung.md` links to `[[Jörg]]` (Person) and\n * `[[INIM-BDEV]]` (Project) — the link-cluster of typical \"meeting\"\n * notes will look the same.\n *\n * The neighbor learner is weaker than folder-conventions (more indirect)\n * but rescues cases where folder structure is shallow or unconvention'd.\n */\n\nimport type { Vault } from \"../vault/index.js\";\n\nexport interface NeighborInferenceEntry {\n /** Frontmatter key seen in neighbors. */\n key: string;\n /** Number of neighbors that have the key. */\n neighborCount: number;\n /** Total neighbors considered (denominator). */\n totalNeighbors: number;\n /** Presence ratio. */\n prevalence: number;\n /** Dominant value across neighbors-with-this-key, if any. */\n dominantValue: unknown | null;\n /** Coverage of the dominant value. */\n dominantValueRatio: number;\n}\n\nexport interface NeighborInferenceResult {\n /** Number of forward links resolved to existing notes. */\n forwardCount: number;\n /** Number of backlinks. */\n backwardCount: number;\n /** Combined unique neighbor count (denominator for prevalence). */\n totalNeighbors: number;\n entries: NeighborInferenceEntry[];\n}\n\ninterface NeighborRow {\n path: string;\n frontmatter: string | null;\n}\n\n/**\n * Gather all neighbor notes (forward + backward links), deduplicated by\n * note id.\n *\n * For a note that does not yet exist in the DB (brand-new), backlinks\n * cannot be computed (nothing links to it yet). Only forward-links from\n * the parsed content can contribute — but parsing happens upstream.\n * In that case the caller passes the parsed wikilinks directly via\n * `additionalForwardTargets`.\n */\nfunction gatherNeighbors(\n vault: Vault,\n notePath: string,\n additionalForwardTargets: string[] = [],\n): NeighborRow[] {\n const seenIds = new Set();\n const out: NeighborRow[] = [];\n\n const note = vault.db.notes.getByPath(notePath);\n\n // Backward: who links to this note's path (only meaningful if the note\n // exists in DB; backlinks reference target_note id OR a target_path\n // for unresolved links).\n if (note) {\n const back = vault.db.wikilinks.getBacklinks(note.id);\n for (const row of back) {\n if (seenIds.has(row.sourceNoteId)) continue;\n const src = vault.db.notes.getById(row.sourceNoteId);\n if (!src) continue;\n seenIds.add(src.id);\n out.push({ path: src.path, frontmatter: src.frontmatter });\n }\n\n // Forward: links this note has (already in DB).\n const forward = vault.db.wikilinks.getForwardLinks(note.id);\n for (const row of forward) {\n if (row.targetNoteId === null) continue;\n if (seenIds.has(row.targetNoteId)) continue;\n const target = vault.db.notes.getById(row.targetNoteId);\n if (!target) continue;\n seenIds.add(target.id);\n out.push({ path: target.path, frontmatter: target.frontmatter });\n }\n }\n\n // Fallback / new-note path: caller-supplied wikilink targets resolved\n // via path lookup. These are unresolved-link strings from parser\n // (e.g. \"Personen/Jörg\" — no .md).\n for (const target of additionalForwardTargets) {\n const candidate = vault.db.notes.getByPath(`${target}.md`) ?? vault.db.notes.getByPath(target);\n if (!candidate) continue;\n if (seenIds.has(candidate.id)) continue;\n seenIds.add(candidate.id);\n out.push({ path: candidate.path, frontmatter: candidate.frontmatter });\n }\n\n return out;\n}\n\nfunction aggregateEntries(neighbors: NeighborRow[]): NeighborInferenceEntry[] {\n const total = neighbors.length;\n if (total === 0) return [];\n\n const keyPresence = new Map();\n const keyValues = new Map>();\n\n for (const row of neighbors) {\n if (!row.frontmatter) continue;\n let fm: unknown;\n try {\n fm = JSON.parse(row.frontmatter);\n } catch {\n continue;\n }\n if (!fm || typeof fm !== \"object\" || Array.isArray(fm)) continue;\n\n const obj = fm as Record;\n for (const [key, value] of Object.entries(obj)) {\n keyPresence.set(key, (keyPresence.get(key) ?? 0) + 1);\n const valKey = JSON.stringify(value, Object.keys((value as object) ?? {}).sort());\n if (!keyValues.has(key)) keyValues.set(key, new Map());\n const bucket = keyValues.get(key)!;\n bucket.set(valKey, (bucket.get(valKey) ?? 0) + 1);\n }\n }\n\n const entries: NeighborInferenceEntry[] = [];\n for (const [key, presenceCount] of keyPresence) {\n const valueBucket = keyValues.get(key)!;\n let bestKey = \"\";\n let bestCount = 0;\n for (const [k, c] of valueBucket) {\n if (c > bestCount) {\n bestKey = k;\n bestCount = c;\n }\n }\n const dominantValue = bestCount / presenceCount > 0.5 ? safeParse(bestKey) : null;\n entries.push({\n key,\n neighborCount: presenceCount,\n totalNeighbors: total,\n prevalence: presenceCount / total,\n dominantValue,\n dominantValueRatio: bestCount / presenceCount,\n });\n }\n\n entries.sort((a, b) => {\n if (b.prevalence !== a.prevalence) return b.prevalence - a.prevalence;\n return a.key.localeCompare(b.key);\n });\n return entries;\n}\n\nfunction safeParse(s: string): unknown {\n try {\n return JSON.parse(s);\n } catch {\n return null;\n }\n}\n\n/**\n * Primary entry point. For a note path, returns the frontmatter\n * conventions visible across its linked neighbors.\n *\n * `additionalForwardTargets`: vault-relative paths (without `.md`) for\n * wikilinks that haven't been indexed yet — typically passed by the\n * tool handler when the input is a draft content blob rather than an\n * indexed note.\n */\nexport function inferFromNeighbors(\n vault: Vault,\n notePath: string,\n additionalForwardTargets: string[] = [],\n): NeighborInferenceResult {\n const neighbors = gatherNeighbors(vault, notePath, additionalForwardTargets);\n\n // Approximate forward/backward split — not strictly needed for the\n // aggregate, but useful in the tool response so the agent can see\n // where the signal came from.\n const note = vault.db.notes.getByPath(notePath);\n let forwardCount = 0;\n let backwardCount = 0;\n if (note) {\n forwardCount = vault.db.wikilinks\n .getForwardLinks(note.id)\n .filter((r) => r.targetNoteId !== null).length;\n backwardCount = vault.db.wikilinks.getBacklinks(note.id).length;\n }\n\n return {\n forwardCount,\n backwardCount,\n totalNeighbors: neighbors.length,\n entries: aggregateEntries(neighbors),\n };\n}\n","/**\n * Content-based heuristic inference.\n *\n * A set of vault-agnostic Title/Body pattern matchers. Each rule emits\n * suggested frontmatter when the input note matches its pattern. Rules\n * are intentionally narrow and self-explanatory — the user (or agent)\n * should be able to read the rule list and predict what will be inferred.\n *\n * Confidence is fixed per rule. Multiple rules CAN match (e.g. a meeting\n * note that mentions a person) — the resolver upstream combines them.\n *\n * No LLM, no embeddings. Pure deterministic RegEx + string scanning.\n */\n\nexport interface ContentHeuristicEntry {\n /** Frontmatter key the rule contributes (e.g. \"class\", \"type\"). */\n key: string;\n /** Suggested value. */\n value: unknown;\n /** Fixed confidence per rule (0..1). */\n confidence: number;\n /** Which rule fired, for transparency in the tool response. */\n rule: string;\n}\n\nexport interface ContentHeuristicResult {\n entries: ContentHeuristicEntry[];\n /** Rule names that matched (for the agent's debugging). */\n matchedRules: string[];\n}\n\ninterface HeuristicRule {\n name: string;\n /**\n * Returns the suggested entries when this rule matches; empty array\n * means the rule did not fire.\n */\n match: (input: HeuristicInput) => Omit[];\n}\n\ninterface HeuristicInput {\n title: string;\n bodyHead: string; // first ~2000 chars of body\n fullBody: string;\n}\n\nconst DEFAULT_CONFIDENCE = 0.7;\nconst STRONG_CONFIDENCE = 0.85;\nconst WEAK_CONFIDENCE = 0.5;\n\n/**\n * Email — matches Title-like \"E-Mail von X\", \"Mail von X\", \"Email from X\",\n * OR a body starting with \"From:\" / \"Von:\" header (forwarded mail style).\n */\nconst emailRule: HeuristicRule = {\n name: \"email-title-or-header\",\n match: ({ title, bodyHead }) => {\n const titleMatch =\n /^(E-?Mail|Email|Mail)\\s+(von|from)\\s+\\S+/i.test(title) || /^(Re|Fwd|AW|WG):\\s/i.test(title);\n const headerMatch = /^(From|Von):\\s+\\S+/im.test(bodyHead) && /^(To|An):\\s+\\S+/im.test(bodyHead);\n if (!titleMatch && !headerMatch) return [];\n return [\n { key: \"class\", value: \"Email\", confidence: STRONG_CONFIDENCE },\n { key: \"type\", value: \"email\", confidence: STRONG_CONFIDENCE },\n ];\n },\n};\n\n/**\n * Meeting — multi-language: Meeting, Treffen, Call, Sondierung, Termin,\n * Standup, Sync. Title-leading keyword OR a YYYY-MM-DD prefix + such a\n * keyword.\n */\nconst meetingRule: HeuristicRule = {\n name: \"meeting-title-keyword\",\n match: ({ title, bodyHead }) => {\n const keywords =\n /\\b(Meeting|Treffen|Call|Sondierung|Termin|Standup|Sync|Kickoff|Kick-off|Jour\\s*fixe|Workshop)\\b/i;\n const isMeeting =\n keywords.test(title) ||\n /^\\d{4}-\\d{2}-\\d{2}.*\\b(Meeting|Treffen|Call|Sondierung)/i.test(title);\n if (!isMeeting) return [];\n // Many meeting notes have an \"Attendees:\" / \"Teilnehmer:\" line — bump\n // confidence when we see one.\n const attendeesPresent = /^(Attendees|Teilnehmer|Participants):/im.test(bodyHead);\n const conf = attendeesPresent ? STRONG_CONFIDENCE : DEFAULT_CONFIDENCE;\n return [\n { key: \"class\", value: \"Meeting\", confidence: conf },\n { key: \"type\", value: \"meeting\", confidence: conf },\n ];\n },\n};\n\n/**\n * Person — short title that looks like a personal name (1-4 capitalized\n * tokens), AND body mentions LinkedIn URL, an email address with the\n * person's name, or a phone-number pattern.\n *\n * Deliberately narrow: many notes have person names in titles (e.g.\n * meeting notes) — we require corroborating signals from the body.\n */\nconst personRule: HeuristicRule = {\n name: \"person-name-title-with-corroboration\",\n match: ({ title, bodyHead }) => {\n const nameLike = /^[A-ZÄÖÜ][a-zäöüß'\\-]+( [A-ZÄÖÜ][a-zäöüß'\\-]+){0,3}$/.test(title.trim());\n if (!nameLike) return [];\n const corroborating =\n /linkedin\\.com\\/in\\//i.test(bodyHead) ||\n /\\b[\\w._-]+@[\\w.-]+\\.[a-z]{2,}\\b/i.test(bodyHead) ||\n /\\+?\\d[\\d\\s\\-./()]{6,}/.test(bodyHead);\n if (!corroborating) return [];\n return [\n { key: \"class\", value: \"Person\", confidence: STRONG_CONFIDENCE },\n { key: \"type\", value: \"person\", confidence: STRONG_CONFIDENCE },\n { key: \"participation\", value: [], confidence: WEAK_CONFIDENCE },\n ];\n },\n};\n\n/**\n * Reading note / clipping — body starts with a markdown link to a URL\n * (common Obsidian Web Clipper format), or has a `source:` URL in the\n * first ~500 chars.\n */\nconst clippingRule: HeuristicRule = {\n name: \"clipping-source-url\",\n match: ({ bodyHead }) => {\n const headSnippet = bodyHead.slice(0, 500);\n const hasMdLink = /^\\s*\\[.+\\]\\(https?:\\/\\/[^\\s)]+\\)/m.test(headSnippet);\n const hasSourceField = /^source:\\s*https?:\\/\\//im.test(headSnippet);\n if (!hasMdLink && !hasSourceField) return [];\n return [\n { key: \"class\", value: \"Clipping\", confidence: DEFAULT_CONFIDENCE },\n { key: \"tags\", value: [\"clippings\"], confidence: DEFAULT_CONFIDENCE },\n ];\n },\n};\n\n/**\n * Fact / short-status — very short body (<150 chars), one-line subject,\n * looks like a captured fact or status update.\n *\n * Confidence intentionally low — many short notes are not facts but\n * fragments, drafts, etc.\n */\nconst factRule: HeuristicRule = {\n name: \"short-fact\",\n match: ({ fullBody }) => {\n const trimmed = fullBody.trim();\n if (trimmed.length === 0 || trimmed.length > 150) return [];\n // Reject if it contains multiple paragraphs (likely fragment, not fact).\n if (/\\n\\s*\\n/.test(trimmed)) return [];\n return [{ key: \"class\", value: \"Fact\", confidence: WEAK_CONFIDENCE }];\n },\n};\n\n/**\n * Date prefix → `created` and (for date-prefixed names) `meeting_date`.\n * Common Obsidian convention.\n */\nconst dateInTitleRule: HeuristicRule = {\n name: \"date-prefix-in-title\",\n match: ({ title }) => {\n const m = title.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (!m) return [];\n const iso = `${m[1]}-${m[2]}-${m[3]}`;\n return [{ key: \"created\", value: iso, confidence: STRONG_CONFIDENCE }];\n },\n};\n\nconst RULES: readonly HeuristicRule[] = [\n emailRule,\n meetingRule,\n personRule,\n clippingRule,\n factRule,\n dateInTitleRule,\n];\n\n/**\n * Run all rules against the input note. Multiple rules CAN fire (e.g.\n * a date-prefix meeting note matches both `dateInTitleRule` and\n * `meetingRule`). The combiner handles cross-rule conflicts upstream;\n * here we just emit every match.\n */\nexport function inferFromContent(input: { title: string; body: string }): ContentHeuristicResult {\n const heuristicInput: HeuristicInput = {\n title: input.title,\n bodyHead: input.body.slice(0, 2000),\n fullBody: input.body,\n };\n\n const entries: ContentHeuristicEntry[] = [];\n const matchedRules: string[] = [];\n\n for (const rule of RULES) {\n const matches = rule.match(heuristicInput);\n if (matches.length > 0) {\n matchedRules.push(rule.name);\n for (const m of matches) {\n entries.push({ ...m, rule: rule.name });\n }\n }\n }\n\n return { entries, matchedRules };\n}\n","/**\n * Combiner — fuses folder-conventions, neighbor-inference, and content-\n * heuristics into a single structured suggestion bundle.\n *\n * Output shape (per the v0.10.0 contract):\n *\n * {\n * existing: [...], // keys already present in the note's frontmatter\n * suggestions: [...], // new keys with one agreed value (highest confidence)\n * conflicts: [...] // keys where sources disagree (existing or new)\n * }\n *\n * Confidence calibration per source:\n * - folder: raw prevalence (already in [0, 1])\n * - neighbor: prevalence × 0.6 (dampened — indirect signal)\n * - content: fixed per rule (0.5 / 0.7 / 0.85 → see content-heuristics)\n */\n\nimport type { Vault } from \"../vault/index.js\";\nimport { inferFromFolder, type FolderConventionResult } from \"./folder-conventions.js\";\nimport { inferFromNeighbors, type NeighborInferenceResult } from \"./neighbor-inference.js\";\nimport { inferFromContent, type ContentHeuristicResult } from \"./content-heuristics.js\";\n\nconst NEIGHBOR_DAMPING = 0.6;\nconst MIN_PRESENTATION_CONFIDENCE = 0.2;\n\nexport type SourceTag = \"folder\" | \"neighbor\" | \"content\";\n\nexport interface FrontmatterExisting {\n key: string;\n value: unknown;\n}\n\nexport interface FrontmatterSuggestion {\n key: string;\n /** Suggested value. `null` means \"key only, no concrete value\" — agent\n * should ask the user to fill it in. */\n suggestedValue: unknown | null;\n /** Combined confidence (max across sources that agreed). */\n confidence: number;\n /** Which sources contributed (in order of confidence DESC). */\n sources: SourceTag[];\n /**\n * Optional rule name for content-heuristics matches (helps the user\n * understand why something was suggested).\n */\n rule?: string;\n}\n\nexport interface FrontmatterConflict {\n key: string;\n /**\n * Each candidate value, with its source and confidence. The agent (or\n * user) picks one explicitly.\n */\n candidates: Array<{\n value: unknown;\n source: SourceTag | \"existing\";\n confidence: number;\n rule?: string;\n }>;\n}\n\nexport interface SuggestFrontmatterInput {\n vault: Vault;\n /**\n * The note's vault-relative path. May NOT yet exist in the DB — the\n * folder learner uses the path prefix, the neighbor learner uses\n * additionalForwardTargets (parsed from content).\n */\n path: string;\n /** Optional existing frontmatter on the note. Used for the `existing`\n * classification and conflict detection. */\n existingFrontmatter?: Record | null;\n /** Optional content for the heuristics layer. If omitted, the layer\n * is skipped (only folder + neighbor remain). */\n content?: string;\n /** Title (for content-heuristics). Falls back to the basename. */\n title?: string;\n /** Wikilink targets parsed from the (possibly draft) content. Used by\n * neighbor-inference when the note isn't indexed yet. */\n draftWikilinkTargets?: string[];\n /** Optional path to exclude from folder inference. Defaults to `path`. */\n excludePath?: string | null;\n}\n\nexport interface SuggestFrontmatterResult {\n /** Keys already present in the note's frontmatter (no conflict). */\n existing: FrontmatterExisting[];\n /** New (or value-clarifying) suggestions, sorted by confidence DESC. */\n suggestions: FrontmatterSuggestion[];\n /** Disagreements between sources, or existing-vs-suggestion mismatches. */\n conflicts: FrontmatterConflict[];\n /** Diagnostic info — useful when the agent wants to explain the result. */\n diagnostics: {\n folder: FolderConventionResult;\n neighbor: NeighborInferenceResult;\n content: ContentHeuristicResult;\n };\n}\n\ninterface Candidate {\n source: SourceTag;\n value: unknown | null;\n confidence: number;\n rule?: string;\n}\n\n/**\n * Stable canonical string for value comparison. Arrays preserved in order;\n * objects key-sorted. Mirrors the canonical-JSON convention used elsewhere\n * in the codebase (reader/hash.ts) for the same reason: equality must be\n * robust to JS object-property order quirks.\n */\nfunction valueKey(v: unknown): string {\n if (v === null || v === undefined) return \"null\";\n if (Array.isArray(v)) {\n return \"[\" + v.map(valueKey).join(\",\") + \"]\";\n }\n if (typeof v === \"object\") {\n const obj = v as Record;\n const keys = Object.keys(obj).sort();\n return \"{\" + keys.map((k) => JSON.stringify(k) + \":\" + valueKey(obj[k])).join(\",\") + \"}\";\n }\n return JSON.stringify(v);\n}\n\n/**\n * Core orchestration entrypoint. Runs all three learners and combines\n * their output into the structured response.\n */\nexport function suggestFrontmatter(input: SuggestFrontmatterInput): SuggestFrontmatterResult {\n const title = input.title ?? defaultTitleFromPath(input.path);\n\n const folder = inferFromFolder(input.vault, input.path, {\n excludePath: input.excludePath ?? input.path,\n });\n const neighbor = inferFromNeighbors(input.vault, input.path, input.draftWikilinkTargets ?? []);\n const content =\n input.content !== undefined\n ? inferFromContent({ title, body: input.content })\n : { entries: [], matchedRules: [] };\n\n return combineSuggestions({\n existingFrontmatter: input.existingFrontmatter ?? null,\n folder,\n neighbor,\n content,\n });\n}\n\nfunction defaultTitleFromPath(path: string): string {\n const base = path.split(\"/\").pop() ?? path;\n return base.replace(/\\.md$/i, \"\");\n}\n\n/**\n * Pure combiner — exposed separately so unit tests can construct\n * synthetic inputs without spinning up a vault.\n */\nexport function combineSuggestions(args: {\n existingFrontmatter: Record | null;\n folder: FolderConventionResult;\n neighbor: NeighborInferenceResult;\n content: ContentHeuristicResult;\n}): SuggestFrontmatterResult {\n const { existingFrontmatter, folder, neighbor, content } = args;\n\n // Build a `key -> candidates[]` map from the three sources.\n const candidates = new Map();\n\n const push = (key: string, c: Candidate): void => {\n if (!candidates.has(key)) candidates.set(key, []);\n candidates.get(key)!.push(c);\n };\n\n // Folder layer.\n for (const e of folder.entries) {\n if (e.prevalence < MIN_PRESENTATION_CONFIDENCE) continue;\n push(e.key, {\n source: \"folder\",\n value: e.dominantValue,\n confidence: e.prevalence,\n });\n }\n\n // Neighbor layer (dampened).\n for (const e of neighbor.entries) {\n const conf = e.prevalence * NEIGHBOR_DAMPING;\n if (conf < MIN_PRESENTATION_CONFIDENCE) continue;\n push(e.key, {\n source: \"neighbor\",\n value: e.dominantValue,\n confidence: conf,\n });\n }\n\n // Content layer.\n for (const e of content.entries) {\n push(e.key, {\n source: \"content\",\n value: e.value,\n confidence: e.confidence,\n rule: e.rule,\n });\n }\n\n const existing: FrontmatterExisting[] = [];\n const suggestions: FrontmatterSuggestion[] = [];\n const conflicts: FrontmatterConflict[] = [];\n\n const fm = existingFrontmatter ?? {};\n const existingKeys = new Set(Object.keys(fm));\n\n // Process each key from candidates + every existing key (so existing\n // keys that no source touched still land in `existing`).\n const allKeys = new Set([...candidates.keys(), ...existingKeys]);\n\n for (const key of allKeys) {\n const cands = candidates.get(key) ?? [];\n const existingValue = existingKeys.has(key) ? fm[key] : undefined;\n const hasExisting = existingValue !== undefined;\n const existingValueKey = hasExisting ? valueKey(existingValue) : null;\n\n // Group candidates by value-key to find disagreement and combine\n // confidence within an agreed value.\n const byValue = new Map();\n for (const c of cands) {\n if (c.value === null) {\n // Null value means \"key only\". Bucket separately so it doesn't\n // collide with a concrete-value candidate.\n const k = \"__keyonly__\";\n if (!byValue.has(k)) byValue.set(k, []);\n byValue.get(k)!.push(c);\n } else {\n const k = valueKey(c.value);\n if (!byValue.has(k)) byValue.set(k, []);\n byValue.get(k)!.push(c);\n }\n }\n\n const distinctValueCount = Array.from(byValue.keys()).filter((k) => k !== \"__keyonly__\").length;\n\n if (hasExisting) {\n // Anything in candidates that disagrees with the existing value is\n // a conflict; anything that agrees is silently absorbed.\n const agreeingBucket = byValue.get(existingValueKey!);\n if (agreeingBucket) {\n // Existing is corroborated. Drop the agreeing candidate, treat as\n // pure existing.\n byValue.delete(existingValueKey!);\n }\n const disagreeingValues = Array.from(byValue.entries()).filter(([k]) => k !== \"__keyonly__\");\n if (disagreeingValues.length === 0) {\n // No conflict — existing stays as-is.\n existing.push({ key, value: existingValue });\n } else {\n // Conflict between existing and one or more inferred values.\n const candidatesList: FrontmatterConflict[\"candidates\"] = [\n {\n value: existingValue,\n source: \"existing\",\n confidence: 1.0,\n },\n ];\n for (const [, group] of disagreeingValues) {\n const best = pickBestCandidate(group);\n candidatesList.push({\n value: best.value,\n source: best.source,\n confidence: best.confidence,\n ...(best.rule ? { rule: best.rule } : {}),\n });\n }\n conflicts.push({ key, candidates: candidatesList });\n }\n } else {\n // No existing value — emit a suggestion or a conflict between\n // disagreeing inference sources.\n if (distinctValueCount > 1) {\n // Sources disagree on the value. Emit a conflict.\n const candidatesList: FrontmatterConflict[\"candidates\"] = [];\n for (const [k, group] of byValue) {\n if (k === \"__keyonly__\") continue;\n const best = pickBestCandidate(group);\n candidatesList.push({\n value: best.value,\n source: best.source,\n confidence: best.confidence,\n ...(best.rule ? { rule: best.rule } : {}),\n });\n }\n // Sort candidates by confidence DESC for stable agent UX.\n candidatesList.sort((a, b) => b.confidence - a.confidence);\n conflicts.push({ key, candidates: candidatesList });\n } else if (distinctValueCount === 1) {\n // All sources that suggest a value agree. Pick the best one,\n // combine confidence by max.\n const [valueKeyStr, group] = Array.from(byValue.entries()).find(\n ([k]) => k !== \"__keyonly__\",\n )!;\n const best = pickBestCandidate(group);\n const sources = uniqueSources(group);\n suggestions.push({\n key,\n suggestedValue: best.value,\n confidence: best.confidence,\n sources,\n ...(best.rule ? { rule: best.rule } : {}),\n });\n void valueKeyStr;\n } else {\n // Only key-only candidates (no concrete value). Suggest the key\n // with `suggestedValue: null` — agent should ask user to fill in.\n const group = byValue.get(\"__keyonly__\")!;\n const best = pickBestCandidate(group);\n suggestions.push({\n key,\n suggestedValue: null,\n confidence: best.confidence,\n sources: uniqueSources(group),\n });\n }\n }\n }\n\n // Stable sorting for the response: suggestions DESC by confidence,\n // conflicts ASC by key (no clear order signal there).\n suggestions.sort((a, b) => {\n if (b.confidence !== a.confidence) return b.confidence - a.confidence;\n return a.key.localeCompare(b.key);\n });\n conflicts.sort((a, b) => a.key.localeCompare(b.key));\n existing.sort((a, b) => a.key.localeCompare(b.key));\n\n return {\n existing,\n suggestions,\n conflicts,\n diagnostics: { folder, neighbor, content },\n };\n}\n\nfunction pickBestCandidate(group: Candidate[]): Candidate {\n // Callers always pass at least one candidate — the `if (group)` check\n // above gates this. Defensive throw rather than non-null-assertion\n // keeps the failure mode loud if the invariant ever breaks.\n if (group.length === 0) {\n throw new Error(\"pickBestCandidate called with empty group\");\n }\n let best: Candidate = group[0]!;\n for (const c of group) {\n if (c.confidence > best.confidence) best = c;\n }\n return best;\n}\n\nfunction uniqueSources(group: Candidate[]): SourceTag[] {\n const seen = new Set();\n const out: SourceTag[] = [];\n // Order: by confidence DESC.\n const sorted = [...group].sort((a, b) => b.confidence - a.confidence);\n for (const c of sorted) {\n if (seen.has(c.source)) continue;\n seen.add(c.source);\n out.push(c.source);\n }\n return out;\n}\n","/**\n * Schema-inference module — public API for the `suggest_frontmatter`\n * MCP tool.\n *\n * Pipeline:\n * inferFromFolder — folder-convention prevalence + dominant values\n * inferFromNeighbors — wikilink-neighborhood prevalence + dominant values\n * inferFromContent — title/body pattern matchers (deterministic rules)\n * combineSuggestions — merge the three, resolve conflicts, classify as\n * existing / suggestions / conflicts\n *\n * Each layer returns a confidence in [0, 1]; the combiner uses the MAX\n * across sources when more than one agrees, and emits a conflict entry\n * when sources disagree on a value for the same key.\n */\n\nexport { inferFromFolder, folderOf } from \"./folder-conventions.js\";\nexport type { FolderConventionEntry, FolderConventionResult } from \"./folder-conventions.js\";\n\nexport { inferFromNeighbors } from \"./neighbor-inference.js\";\nexport type { NeighborInferenceEntry, NeighborInferenceResult } from \"./neighbor-inference.js\";\n\nexport { inferFromContent } from \"./content-heuristics.js\";\nexport type { ContentHeuristicEntry, ContentHeuristicResult } from \"./content-heuristics.js\";\n\nexport { suggestFrontmatter, combineSuggestions } from \"./combiner.js\";\nexport type {\n FrontmatterSuggestion,\n FrontmatterConflict,\n FrontmatterExisting,\n SuggestFrontmatterResult,\n SuggestFrontmatterInput,\n} from \"./combiner.js\";\n","/**\n * Notes-domain MCP handler factory.\n *\n * Tools: read_note, write_note, update_frontmatter, delete_note,\n * query_frontmatter, suggest_frontmatter.\n *\n * Extracted verbatim from the inline `handlers` literal + standalone\n * `handle*` functions in `src/server.ts`. Behavior-neutral.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. All file IO\n * goes through the adapter registry (`resolveSource` / `resolveDelivery`)\n * and the `vault.db` query namespaces.\n */\n\nimport type { VaultManager, Vault } from \"../../vault/index.js\";\nimport type { AdapterRegistry } from \"../../adapters/registry.js\";\nimport { formatDocId, parseSourceHandle } from \"../../adapters/registry.js\";\nimport type { Document, WikilinkRef } from \"../../types.js\";\nimport { queryFrontmatter, updateFrontmatter } from \"../../frontmatter/index.js\";\nimport { suggestFrontmatter } from \"../../schema/index.js\";\nimport {\n countWords,\n safeParseFrontmatter,\n defaultBasename,\n normalizeFolderHint,\n} from \"../utils.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\n/**\n * Read a note via the v2 SourceConnector seam (Plan 01-03 Task 06).\n *\n * The v1 wire shape `{path, title, content, frontmatter, hash, mtime,\n * word_count}` is preserved byte-for-byte; only the INTERNAL data path\n * changed: the handler now resolves the source by handle, mints a DocId,\n * and reads a Document via `source.readDocument(id)`. The mapping back\n * to the v1 shape happens at this boundary.\n *\n * Side effect: reads the file fresh from disk on every call (where v1\n * served the DB-cached row). In a normally-running server the catch-up\n * scan + watcher keep DB ≈ disk, so behavior is observationally\n * identical; the path goes through the seam either way.\n */\nexport async function handleReadNote(\n registry: AdapterRegistry,\n vaultName: string,\n path: string,\n): Promise {\n const handle = parseSourceHandle(`obsidian-fs://${vaultName}`);\n let source;\n try {\n source = registry.resolveSource(handle);\n } catch {\n // Preserve the v1 error message shape for unknown-vault cases.\n throw new Error(`Note not found: ${vaultName}/${path}`);\n }\n const id = formatDocId(\"obsidian-fs\", vaultName, path);\n let doc: Document;\n try {\n doc = await source.readDocument(id);\n } catch {\n throw new Error(`Note not found: ${vaultName}/${path}`);\n }\n\n // Map Document → v1 read_note response shape.\n // - `frontmatter` is `doc.properties` minus the adapter-injected\n // `wikilinks: WikilinkRef[]` (D-05). The v1 shape never carried the\n // wikilinks key; preserve that.\n const { wikilinks: _wikilinks, ...frontmatterOnly } = doc.properties as Record<\n string,\n unknown\n > & {\n wikilinks?: WikilinkRef[];\n };\n const hasFrontmatter = Object.keys(frontmatterOnly).length > 0;\n // Single-paragraph BodyShape=\"flat-text\" — body lives in blocks[0].text.\n const content = doc.blocks[0]?.kind === \"paragraph\" ? doc.blocks[0].text : \"\";\n\n return {\n path,\n title: doc.title,\n content,\n frontmatter: hasFrontmatter ? frontmatterOnly : null,\n hash: doc.hash,\n mtime: doc.mtime,\n word_count: countWords(content),\n };\n}\n\n/**\n * write_note handler. Routes through `registry.resolveDelivery(handle).write`\n * (plan 01-04 task 06) while preserving the v1 wire shape: caller sees\n * `{ok, noteId, newHash, created, reason?, ...}`. The DocId mapping happens\n * at the seam — v2 returns doc_id: DocId; we derive v1 noteId from the DB\n * row after a successful write.\n *\n * The v1 `client_id` arg, when supplied, overrides the constructor-injected\n * default per D-02. When omitted, the delivery's lazy clientId getter reads\n * `server.getClientVersion()?.name` at call time.\n */\nasync function handleWriteNote(\n registry: AdapterRegistry,\n vault: Vault,\n parsed: {\n vault: string;\n path: string;\n content: string;\n frontmatter?: Record | null;\n expected_hash?: string;\n client_id?: string;\n },\n): Promise {\n const handle = parseSourceHandle(`obsidian-fs://${parsed.vault}`);\n const delivery = registry.resolveDelivery(handle);\n const docId = formatDocId(\"obsidian-fs\", parsed.vault, parsed.path);\n\n const partial: Partial = {\n blocks: [{ kind: \"paragraph\", text: parsed.content }],\n properties: parsed.frontmatter ?? {},\n };\n const opts: { expectedHash?: string; clientId?: string } = {};\n if (parsed.expected_hash !== undefined) opts.expectedHash = parsed.expected_hash;\n if (parsed.client_id !== undefined) opts.clientId = parsed.client_id;\n\n const res = await delivery.write(docId, partial, opts);\n if (!res.ok) {\n // Preserve v1 conflict shape — handlers used to forward writeNote's\n // v1 WriteConflict directly; reshape to match. Phase 2 envelope fields\n // (sinkName / suggestion) are propagated unchanged when present so\n // callers receive actionable diagnostics on `sink_write_blocked` and\n // the other Phase 2 refusal codes.\n const out: Record = {\n ok: false,\n reason: res.reason === \"not_found\" ? \"hash_mismatch\" : res.reason,\n };\n if (res.currentHash !== undefined) out.currentHash = res.currentHash;\n if (res.message !== undefined) out.message = res.message;\n if (res.sinkName !== undefined) out.sinkName = res.sinkName;\n if (res.suggestion !== undefined) out.suggestion = res.suggestion;\n if (res.key !== undefined) out.key = res.key;\n if (res.observedValue !== undefined) out.observedValue = res.observedValue;\n return out;\n }\n\n // ADR-008: a write to a ContextFit vault must refresh its search KB so the\n // new/edited content is retrievable. The SQLite note row is already updated\n // inline by writeNote; here we rebuild the ContextFit KB (full re-ingest —\n // CPU-only, fast). Best-effort: a KB-refresh failure does not fail the write\n // (the note is on disk + in SQLite; the next index/catchup reconciles).\n if (vault.config.backend === \"contextfit\") {\n try {\n const { indexVaultWithContextFit } =\n await import(\"../../adapters/retrieval/contextfit/index.js\");\n await indexVaultWithContextFit(vault.config, {});\n } catch {\n // swallow — write succeeded; KB will catch up on next index/restart\n }\n }\n\n // Derive v1 noteId from the DB. The write went through writeNote\n // internally which upserts the note; getByPath returns the row.\n const noteRow = vault.db.notes.getByPath(parsed.path);\n return {\n ok: true,\n newHash: res.newHash,\n noteId: noteRow?.id ?? 0,\n created: res.created,\n };\n}\n\n/**\n * delete_note handler. Routes through `registry.resolveDelivery(handle).delete`\n * (plan 01-04 task 06). Preserves the v1 wire shape `{ok, newHash, noteId,\n * created}` (created=false for delete; newHash echoes the now-gone file's\n * pre-delete hash, matching v1 deleteNote semantics).\n */\nasync function handleDeleteNote(\n registry: AdapterRegistry,\n vault: Vault,\n parsed: {\n vault: string;\n path: string;\n expected_hash: string;\n client_id?: string;\n },\n): Promise {\n // Capture the v1 noteId + existing hash BEFORE we ask the delivery to\n // delete (after success, getByPath returns null).\n const noteRow = vault.db.notes.getByPath(parsed.path);\n const preDeleteHash = noteRow?.hash ?? parsed.expected_hash;\n\n const handle = parseSourceHandle(`obsidian-fs://${parsed.vault}`);\n const delivery = registry.resolveDelivery(handle);\n const docId = formatDocId(\"obsidian-fs\", parsed.vault, parsed.path);\n\n const opts: { expectedHash?: string; clientId?: string } = {\n expectedHash: parsed.expected_hash,\n };\n if (parsed.client_id !== undefined) opts.clientId = parsed.client_id;\n\n const res = await delivery.delete(docId, opts);\n if (!res.ok) {\n const out: Record = {\n ok: false,\n reason: res.reason === \"not_found\" ? \"hash_mismatch\" : res.reason,\n };\n if (res.currentHash !== undefined) out.currentHash = res.currentHash;\n if (res.message !== undefined) out.message = res.message;\n if (res.sinkName !== undefined) out.sinkName = res.sinkName;\n if (res.suggestion !== undefined) out.suggestion = res.suggestion;\n return out;\n }\n return {\n ok: true,\n newHash: preDeleteHash,\n noteId: noteRow?.id ?? 0,\n created: false,\n };\n}\n\n/**\n * Handler for the v0.10.0 `suggest_frontmatter` tool.\n *\n * Two-mode dispatch:\n * - `path` provided → existing-note inference. Reads stored content +\n * frontmatter + wikilinks from DB. Folder-conventions use the note's\n * own folder.\n * - `content` provided (no path) → draft inference. Folder-conventions\n * use `folder_hint` (or vault root). No backlinks. Forward-link\n * extraction would require a lightweight markdown parse — for v0.10.0\n * we skip it to keep the tool dependency-free and document the\n * limitation in the response.\n */\nfunction handleSuggestFrontmatter(\n manager: VaultManager,\n parsed: {\n vault: string;\n path?: string;\n content?: string;\n title?: string;\n folder_hint?: string;\n },\n): object {\n const vault = manager.require(parsed.vault);\n\n // Mode 1: existing-note path.\n if (parsed.path) {\n const note = vault.db.notes.getByPath(parsed.path);\n if (!note) {\n throw new Error(\n `Note not found: ${parsed.vault}/${parsed.path}. ` +\n `Use draft mode ({content, folder_hint}) for unindexed notes.`,\n );\n }\n const existingFm: Record | null = note.frontmatter\n ? safeParseFrontmatter(note.frontmatter)\n : null;\n const result = suggestFrontmatter({\n vault,\n path: note.path,\n existingFrontmatter: existingFm,\n content: parsed.content ?? note.content,\n title: parsed.title ?? note.title ?? defaultBasename(note.path),\n excludePath: note.path,\n });\n return {\n mode: \"existing\",\n path: note.path,\n ...result,\n };\n }\n\n // Mode 2: draft.\n const folderHint = normalizeFolderHint(parsed.folder_hint);\n // Synthesize a path under the folder hint so folder-conventions can\n // resolve. The path itself never gets written; it's a probe.\n const probePath = `${folderHint}__draft__${Date.now()}.md`;\n const result = suggestFrontmatter({\n vault,\n path: probePath,\n existingFrontmatter: null,\n content: parsed.content!,\n title: parsed.title ?? \"Draft\",\n // Exclude the synthetic path explicitly — though it won't match any\n // existing note, this future-proofs against collisions.\n excludePath: probePath,\n });\n return {\n mode: \"draft\",\n folder_hint: folderHint,\n note: \"Draft mode: no backlinks contributed. Provide `path` (and index the note first) for richer neighbor-inference.\",\n ...result,\n };\n}\n\nexport function makeNotesHandlers(deps: HandlerDeps): Partial> {\n const { manager, adapterRegistry, suppression, memorySinkRegistry } = deps;\n return {\n read_note: async (a) => {\n const p = a as { vault: string; path: string };\n return handleReadNote(adapterRegistry, p.vault, p.path);\n },\n query_frontmatter: async (a) => {\n const p = a as { vault: string; where: Record; limit: number };\n const vault = manager.require(p.vault);\n const hits = queryFrontmatter(vault, {\n where: p.where as Record,\n limit: p.limit,\n });\n return {\n notes: hits.map((n) => ({\n path: n.path,\n title: n.title,\n frontmatter: n.frontmatter ? JSON.parse(n.frontmatter) : null,\n mtime: n.mtime,\n })),\n count: hits.length,\n };\n },\n write_note: async (a) => {\n const p = a as {\n vault: string;\n path: string;\n content: string;\n frontmatter?: Record | null;\n expected_hash?: string;\n client_id?: string;\n };\n const vault = manager.require(p.vault);\n // Suppress the watcher event triggered by our own atomic rename.\n // We call suppression BEFORE delivery.write() so the event is\n // pre-filtered. Worst case (permission_denied / hash_mismatch):\n // we suppress an event that never fires — harmless beyond the\n // ~2s TTL.\n suppression.add(p.path);\n return handleWriteNote(adapterRegistry, vault, p);\n },\n update_frontmatter: async (a) => {\n const p = a as {\n vault: string;\n path: string;\n merge: Record;\n expected_hash?: string;\n client_id?: string;\n };\n const vault = manager.require(p.vault);\n return updateFrontmatter({\n vault,\n registry: adapterRegistry,\n memorySinkRegistry,\n relativePath: p.path,\n merge: p.merge,\n ...(p.expected_hash !== undefined ? { expectedHash: p.expected_hash } : {}),\n ...(p.client_id !== undefined ? { clientId: p.client_id } : {}),\n onBeforeFsWrite: () => suppression.add(p.path),\n });\n },\n delete_note: async (a) => {\n const p = a as {\n vault: string;\n path: string;\n expected_hash: string;\n client_id?: string;\n };\n const vault = manager.require(p.vault);\n suppression.add(p.path);\n return handleDeleteNote(adapterRegistry, vault, p);\n },\n suggest_frontmatter: async (a) => {\n const p = a as {\n vault: string;\n path?: string;\n content?: string;\n title?: string;\n folder_hint?: string;\n };\n return handleSuggestFrontmatter(manager, p);\n },\n };\n}\n","/**\n * Search-domain MCP handler factory.\n *\n * Tools: search_semantic, search_text, search_hybrid, search (compat),\n * fetch (compat).\n *\n * Extracted verbatim from the inline `handlers` literal + standalone\n * `handle*` functions in `src/server.ts`. Behavior-neutral.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. Display-URL\n * minting flows through the adapter registry seam (`displayUrl`); the\n * search pipeline reads via `vault.db` query namespaces.\n */\n\nimport type { VaultManager } from \"../../vault/index.js\";\nimport type { OllamaClient } from \"../../ollama/index.js\";\nimport type { Reranker } from \"../../rerank/index.js\";\nimport type { AdapterRegistry } from \"../../adapters/registry.js\";\nimport { parseSourceHandle } from \"../../adapters/registry.js\";\nimport { FtsQueries } from \"../../db/index.js\";\nimport { hybridSearch, searchVaults, matchesAnyGlob } from \"../../search/index.js\";\nimport type { ExpandDeps, ExpandDirection } from \"../../graph/index.js\";\nimport type { EdgeType } from \"../../db/queries/edges.js\";\nimport type { SearchHit } from \"../../types.js\";\nimport {\n resolveVaultTargets,\n encodeNoteId,\n decodeNoteId,\n displayUrl,\n truncateSnippet,\n} from \"../utils.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nasync function handleSearchSemantic(\n manager: VaultManager,\n ollama: OllamaClient,\n defaultModel: string,\n activeVault: string | undefined,\n query: string,\n vaultFilter: string[] | undefined,\n topK: number,\n excludePaths: string[] | undefined,\n): Promise {\n const { targets, skipped } = resolveVaultTargets(manager, vaultFilter, activeVault);\n\n if (targets.length === 0) {\n return {\n hits: [],\n note:\n skipped.length > 0\n ? `All eligible vaults are indexing; skipped: ${skipped.join(\", \")}.`\n : \"No vaults configured.\",\n };\n }\n\n // When excluding paths, fan out wider so the filtered topK is well-stocked.\n const hasExclude = excludePaths !== undefined && excludePaths.length > 0;\n const fanK = hasExclude ? topK * 3 : topK;\n\n // Cache query embedding by model name across vaults.\n const embedCache = new Map();\n const allHits: SearchHit[] = [];\n\n for (const vault of targets) {\n // Phase 7c follow-up (v0.7.2): the active model in the DB is the source\n // of truth — switch_active_model may have promoted a shadow model\n // that doesn't match config.embedding_model. Fall back to the config\n // only when no active model is registered yet.\n const model = vault.db.models.getActive();\n if (!model) continue;\n const modelName = model.name;\n\n let queryVec = embedCache.get(modelName);\n if (!queryVec) {\n const embedResp = await ollama.embed({ model: modelName, texts: [query] });\n queryVec = embedResp.vectors[0];\n if (!queryVec) continue;\n embedCache.set(modelName, queryVec);\n }\n\n const semanticHits = vault.db.embeddings.searchSemantic(model.id, queryVec, fanK);\n\n for (const hit of semanticHits) {\n const chunk = vault.db.chunks.getById(hit.chunkId);\n if (!chunk) continue;\n const note = vault.db.notes.getById(chunk.note_id);\n if (!note) continue;\n if (hasExclude && matchesAnyGlob(note.path, excludePaths!)) continue;\n const score = 1 / (1 + hit.distance);\n\n allHits.push({\n vault: vault.config.name,\n notePath: note.path,\n noteTitle: note.title,\n chunkText: chunk.text,\n chunkIdx: chunk.idx,\n headingPath: chunk.heading_path,\n score,\n scoreBreakdown: { semantic: score },\n });\n }\n }\n\n allHits.sort((a, b) => b.score - a.score);\n const out: Record = {\n hits: allHits.slice(0, topK),\n count: allHits.length,\n };\n if (skipped.length > 0) {\n out.note = `Skipped vault(s) currently indexing: ${skipped.join(\", \")}.`;\n }\n return out;\n}\n\nfunction handleSearchText(\n manager: VaultManager,\n activeVault: string | undefined,\n query: string,\n vaultFilter: string[] | undefined,\n topK: number,\n excludePaths: string[] | undefined,\n): object {\n const { targets, skipped } = resolveVaultTargets(manager, vaultFilter, activeVault);\n\n if (targets.length === 0) {\n return {\n hits: [],\n note:\n skipped.length > 0\n ? `All eligible vaults are indexing; skipped: ${skipped.join(\", \")}.`\n : \"No vaults configured.\",\n };\n }\n\n const hasExclude = excludePaths !== undefined && excludePaths.length > 0;\n const fanK = hasExclude ? topK * 3 : topK;\n\n const sanitized = FtsQueries.sanitize(query);\n const allHits: SearchHit[] = [];\n const skippedContextFit: string[] = [];\n\n for (const vault of targets) {\n // ADR-008: contextfit-backed vaults have no SQLite FTS table. `search_text`\n // is an Ollama-path BM25 surface; ContextFit users should use search_hybrid\n // / search_semantic (which dispatch to the ContextFit engine). Skip + note.\n if (vault.config.backend === \"contextfit\") {\n skippedContextFit.push(vault.config.name);\n continue;\n }\n const ftsHits = vault.db.fts.search(sanitized, fanK, true);\n for (const hit of ftsHits) {\n const chunk = vault.db.chunks.getById(hit.chunkId);\n if (!chunk) continue;\n const note = vault.db.notes.getById(chunk.note_id);\n if (!note) continue;\n if (hasExclude && matchesAnyGlob(note.path, excludePaths!)) continue;\n\n allHits.push({\n vault: vault.config.name,\n notePath: note.path,\n noteTitle: note.title,\n chunkText: hit.snippet ?? chunk.text,\n chunkIdx: chunk.idx,\n headingPath: chunk.heading_path,\n score: hit.score,\n scoreBreakdown: { text: hit.score },\n });\n }\n }\n\n allHits.sort((a, b) => b.score - a.score);\n const out: Record = {\n hits: allHits.slice(0, topK),\n count: allHits.length,\n };\n const notes: string[] = [];\n if (skipped.length > 0) notes.push(`Skipped vault(s) currently indexing: ${skipped.join(\", \")}.`);\n if (skippedContextFit.length > 0) {\n notes.push(\n `search_text is not supported for ContextFit vault(s): ${skippedContextFit.join(\", \")} — ` +\n `use search_hybrid or search_semantic instead.`,\n );\n }\n if (notes.length > 0) out.note = notes.join(\" \");\n return out;\n}\n\nexport async function handleSearchHybrid(\n manager: VaultManager,\n ollama: OllamaClient,\n defaultModel: string,\n activeVault: string | undefined,\n query: string,\n vaultFilter: string[] | undefined,\n topK: number,\n rrfK: number,\n excludePaths: string[] | undefined,\n reranker: Reranker | undefined,\n // Phase 3 / 03-05 additive params — D-07/D-08/ASM-07/ASM-08.\n recencyWeight: number = 0,\n authorityWeight: number = 0,\n halfLifeDays: number = 30,\n includeSuperseded: boolean = false,\n // Phase 3 / 03-05: optional display-URL resolver (ADR-002 §I-5b\n // seam-preserving — the URL literal lives in the adapter, not here).\n displayUrlFor?: (vaultName: string, notePath: string) => string,\n // Phase 4 / 04-04 (D-15): optional auto-expansion + its injected deps.\n // When `expand` is undefined, hybridSearch's guard short-circuits;\n // `expandDeps` is forwarded unconditionally so future per-call wiring\n // stays trivial.\n expandOpts?: {\n hops: 1 | 2;\n direction?: ExpandDirection;\n edge_types?: EdgeType[];\n },\n expandDeps?: ExpandDeps,\n): Promise {\n const { targets, skipped } = resolveVaultTargets(manager, vaultFilter, activeVault);\n\n if (targets.length === 0) {\n return {\n hits: [],\n note:\n skipped.length > 0\n ? `All eligible vaults are indexing; skipped: ${skipped.join(\", \")}.`\n : \"No vaults configured.\",\n };\n }\n\n const hasExclude = excludePaths !== undefined && excludePaths.length > 0;\n // Request 3× the final topK when filtering so the post-filter list is\n // well-stocked. hybridSearch internally fans 3× again per ranking, so\n // semantic/BM25 each retrieve ~9×topK chunks — plenty of headroom.\n const innerTopK = hasExclude ? topK * 3 : topK;\n\n // ADR-008: searchVaults routes contextfit-backed vaults to the CPU-only\n // engine and ollama vaults to the embeddings hybrid, then merges. For an\n // all-ollama target set (the common case) it delegates straight to\n // hybridSearch with no behavior change.\n const hits = await searchVaults({\n query,\n embeddingModel: defaultModel,\n ollama,\n vaults: targets,\n topK: innerTopK,\n rrfK,\n includeBreakdown: true,\n reranker,\n recencyWeight,\n authorityWeight,\n halfLifeDays,\n includeSuperseded,\n ...(displayUrlFor ? { displayUrlFor } : {}),\n // Phase 4 / 04-04 (D-15): forward optional expand + deps. When\n // `expandOpts` is undefined, hybridSearch short-circuits the\n // expand block (zero new DB reads — v1-baseline byte-identical).\n ...(expandOpts ? { expand: expandOpts } : {}),\n ...(expandDeps ? { expandDeps } : {}),\n });\n\n const filtered = hasExclude\n ? hits.filter((h) => !matchesAnyGlob(h.notePath, excludePaths!))\n : hits;\n\n const out: Record = {\n hits: filtered.slice(0, topK),\n count: filtered.length,\n };\n if (skipped.length > 0) {\n out.note = `Skipped vault(s) currently indexing: ${skipped.join(\", \")}.`;\n }\n return out;\n}\n\n// ─── v0.9.0 handlers — Agent-Compatibility & Self-Orientation ───────────────\n\n/**\n * Encode an opaque id for the OB1-compatible `search`/`fetch` API.\n *\n * Format: `:`\n *\n * Vault names cannot contain `:` per config schema, and Obsidian paths use\n * forward slashes — so the first `:` is an unambiguous separator. We pick\n * this over a base64-encoded blob because the id stays human-readable in\n * connector UIs (ChatGPT shows search results inline) and trivially\n * round-trips through copy/paste.\n */\nasync function handleSearchCompat(\n manager: VaultManager,\n registry: AdapterRegistry,\n ollama: OllamaClient,\n defaultModel: string,\n activeVault: string | undefined,\n query: string,\n limit: number,\n reranker: Reranker | undefined,\n): Promise {\n const { targets, skipped } = resolveVaultTargets(manager, undefined, activeVault);\n\n if (targets.length === 0) {\n return {\n results: [],\n note:\n skipped.length > 0\n ? `All eligible vaults are indexing; skipped: ${skipped.join(\", \")}.`\n : \"No vaults configured.\",\n };\n }\n\n // We delegate to the hybrid pipeline so OB1-style search benefits from\n // both BM25 and vector retrieval — this is the differentiator vs. OB1's\n // pure-embedding implementation. searchVaults additionally routes\n // contextfit-backed vaults to the CPU-only engine (ADR-008).\n const hits = await searchVaults({\n query,\n embeddingModel: defaultModel,\n ollama,\n vaults: targets,\n topK: limit,\n rrfK: 60,\n includeBreakdown: false,\n reranker,\n });\n\n // De-duplicate to one result per note (OB1 spec: one entry per\n // document). Chunks of the same note collapse to the first/best chunk\n // and contribute their snippet.\n const seen = new Set();\n const results: Array<{\n id: string;\n title: string;\n url: string;\n snippet: string;\n }> = [];\n for (const h of hits) {\n const noteKey = `${h.vault}:${h.notePath}`;\n if (seen.has(noteKey)) continue;\n seen.add(noteKey);\n results.push({\n id: encodeNoteId(h.vault, h.notePath),\n title: h.noteTitle ?? h.notePath,\n url: displayUrl(registry, h.vault, h.notePath),\n snippet: truncateSnippet(h.chunkText, 280),\n });\n if (results.length >= limit) break;\n }\n\n const out: Record = { results };\n if (skipped.length > 0) {\n out.note = `Skipped vault(s) currently indexing: ${skipped.join(\", \")}.`;\n }\n return out;\n}\n\nfunction handleFetchCompat(manager: VaultManager, registry: AdapterRegistry, id: string): object {\n const { vault: vaultName, path } = decodeNoteId(id);\n const vault = manager.require(vaultName);\n const note = vault.db.notes.getByPath(path);\n if (!note) {\n throw new Error(`Note not found: ${vaultName}/${path}`);\n }\n const metadata: Record = {\n vault: vaultName,\n path: note.path,\n mtime: note.mtime,\n hash: note.hash,\n word_count: note.word_count,\n };\n if (note.frontmatter) {\n try {\n metadata.frontmatter = JSON.parse(note.frontmatter);\n } catch {\n // Stored frontmatter should always be valid JSON; if it isn't, treat\n // as missing rather than failing the fetch.\n }\n }\n return {\n id,\n title: note.title ?? note.path,\n text: note.content,\n url: displayUrl(registry, vaultName, note.path),\n metadata,\n };\n}\n\nexport function makeSearchHandlers(deps: HandlerDeps): Partial> {\n const { manager, ollama, defaultModel, activeVault, reranker, adapterRegistry } = deps;\n return {\n search_semantic: async (a) => {\n const p = a as {\n query: string;\n vaults?: string[];\n top_k: number;\n exclude_paths?: string[];\n };\n return handleSearchSemantic(\n manager,\n ollama,\n defaultModel,\n activeVault,\n p.query,\n p.vaults,\n p.top_k,\n p.exclude_paths,\n );\n },\n search_text: async (a) => {\n const p = a as {\n query: string;\n vaults?: string[];\n top_k: number;\n exclude_paths?: string[];\n };\n return handleSearchText(manager, activeVault, p.query, p.vaults, p.top_k, p.exclude_paths);\n },\n search_hybrid: async (a) => {\n const p = a as {\n query: string;\n vaults?: string[];\n top_k: number;\n rrf_k: number;\n exclude_paths?: string[];\n rerank: boolean;\n // Phase 3 / 03-05 additive params — Zod fills defaults so these\n // are always present after validation. v1 callers omit them and\n // get the v1-identical default behavior.\n recency_weight: number;\n authority_weight: number;\n half_life_days: number;\n include_superseded: boolean;\n // Phase 4 / 04-04 (D-15): additive optional auto-expansion.\n // When omitted, the downstream hybridSearch guard short-circuits.\n expand?: {\n hops: 1 | 2;\n direction?: ExpandDirection;\n edge_types?: EdgeType[];\n };\n };\n return handleSearchHybrid(\n manager,\n ollama,\n defaultModel,\n activeVault,\n p.query,\n p.vaults,\n p.top_k,\n p.rrf_k,\n p.exclude_paths,\n p.rerank ? reranker : undefined,\n p.recency_weight,\n p.authority_weight,\n p.half_life_days,\n p.include_superseded,\n // 03-05: display-URL resolver — delegates to the obsidian-fs source\n // adapter (or whichever adapter owns the vault) so hybrid.ts never\n // mints adapter URL strings (ADR-002 §I-5b).\n (vaultName, notePath) => displayUrl(adapterRegistry, vaultName, notePath),\n // Phase 4 / 04-04 (D-15): pass the optional expand object + its\n // injected deps (manager + sourceConnectorFor) so hybridSearch\n // can compose Plan 04-03's `expand()` over the rescored top-K.\n p.expand,\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n );\n },\n search: async (a) => {\n const p = a as { query: string; limit: number };\n return handleSearchCompat(\n manager,\n adapterRegistry,\n ollama,\n defaultModel,\n activeVault,\n p.query,\n p.limit,\n reranker,\n );\n },\n fetch: async (a) => {\n const p = a as { id: string };\n return handleFetchCompat(manager, adapterRegistry, p.id);\n },\n };\n}\n","/**\n * Graph-domain MCP handler factory.\n *\n * Tools: list_backlinks, list_forward_links, find_broken_links, expand,\n * cluster.\n *\n * Extracted verbatim from the inline `handlers` literal in `src/server.ts`.\n * Behavior-neutral — each arrow wires args to the same graph-layer call.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. Source\n * resolution flows through the adapter registry seam.\n */\n\nimport { parseDocId, parseSourceHandle } from \"../../adapters/registry.js\";\nimport {\n cluster,\n expand,\n listBacklinks,\n listForwardLinks,\n findBrokenLinks,\n} from \"../../graph/index.js\";\nimport type { ClusterOptions, ExpandDirection, ExpandOptions } from \"../../graph/index.js\";\nimport type { EdgeType } from \"../../db/queries/edges.js\";\nimport { hybridSearch } from \"../../search/index.js\";\nimport { displayUrl } from \"../utils.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nexport function makeGraphHandlers(deps: HandlerDeps): Partial> {\n const { manager, ollama, defaultModel, reranker, adapterRegistry } = deps;\n return {\n list_backlinks: async (a) => {\n const p = a as { vault: string; path: string };\n const vault = manager.require(p.vault);\n return { backlinks: listBacklinks(vault, p.path) };\n },\n list_forward_links: async (a) => {\n const p = a as { vault: string; path: string; include_broken: boolean };\n const vault = manager.require(p.vault);\n return { links: listForwardLinks(vault, p.path, p.include_broken) };\n },\n find_broken_links: async (a) => {\n const p = a as { vault: string };\n const vault = manager.require(p.vault);\n return { broken: findBrokenLinks(vault) };\n },\n\n // ── Phase 4 graph tools (Plan 04-03 / GRA-01) ─────────────────────────\n expand: async (a) => {\n const p = a as {\n seed_doc_ids: string[];\n hops: 1 | 2;\n direction: ExpandDirection;\n edge_types?: EdgeType[];\n filter_properties?: Record;\n include_superseded: boolean;\n };\n // Cast incoming validated DocId strings to the branded DocId\n // type via parseDocId; Zod already enforced DOC_ID_PATTERN at\n // the boundary so this is a no-op brand cast at runtime.\n const seeds = p.seed_doc_ids.map((s) => parseDocId(s));\n return expand(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n {\n seed_doc_ids: seeds,\n hops: p.hops,\n direction: p.direction,\n ...(p.edge_types !== undefined ? { edge_types: p.edge_types } : {}),\n ...(p.filter_properties !== undefined ? { filter_properties: p.filter_properties } : {}),\n include_superseded: p.include_superseded,\n } satisfies ExpandOptions,\n );\n },\n\n // ── Phase 4 graph tools (Plan 04-05 / GRA-02) ─────────────────────────\n cluster: async (a) => {\n const p = a as {\n query?: string;\n seed_doc_ids?: string[];\n vault?: string;\n method: \"edge-community\";\n query_top_k?: number;\n force?: boolean;\n };\n // Build a ClusterOptions discriminated value. Zod's mutual-\n // exclusion refinement has already rejected both-present /\n // neither-present inputs by the time we reach this handler, but\n // the runtime cluster() function performs the same validation as\n // a defense-in-depth check for direct (non-MCP) callers.\n let opts: ClusterOptions;\n if (p.query !== undefined) {\n // CR-02: propagate `vault` so cluster()'s query path can scope\n // search_hybrid deterministically on multi-vault setups.\n opts = {\n query: p.query,\n method: \"edge-community\",\n ...(p.vault !== undefined ? { vault: p.vault } : {}),\n ...(p.query_top_k !== undefined ? { query_top_k: p.query_top_k } : {}),\n ...(p.force !== undefined ? { force: p.force } : {}),\n };\n } else {\n const seeds = (p.seed_doc_ids ?? []).map((s) => parseDocId(s));\n opts = {\n seed_doc_ids: seeds,\n method: \"edge-community\",\n ...(p.force !== undefined ? { force: p.force } : {}),\n };\n }\n return cluster(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n // Bind hybridSearch at call time — avoids the\n // src/graph/cluster.ts → src/search/hybrid.ts circular\n // import. The injected callback returns SearchHit[]; the\n // dispatcher already has `ollama` + `defaultModel` in scope.\n hybridSearch: async (vault, query, limit) =>\n hybridSearch({\n query,\n embeddingModel: defaultModel,\n ollama,\n vaults: [vault],\n topK: limit,\n includeBreakdown: false,\n ...(reranker ? { reranker } : {}),\n displayUrlFor: (vaultName, notePath) =>\n displayUrl(adapterRegistry, vaultName, notePath),\n }),\n },\n opts,\n );\n },\n };\n}\n","/**\n * Memory-domain MCP handler factory.\n *\n * Tools: record_observation, supersede, recall.\n *\n * Extracted verbatim from the inline `handlers` literal in `src/server.ts`.\n * Behavior-neutral — each arrow wires args to the same memory-tool call and\n * applies the same post-write suppression bookkeeping.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. Memory writes\n * route through the delivery adapter seam; the `obsidian-fs://` handle\n * literals are adapter handle strings (not display URLs), used to resolve\n * the delivery/source connectors via the registry.\n */\n\nimport { parseSourceHandle } from \"../../adapters/registry.js\";\nimport {\n handleRecall,\n handleRecordObservation,\n handleSupersede,\n} from \"../../memory/tools/index.js\";\nimport { hybridSearch } from \"../../search/index.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nexport function makeMemoryHandlers(deps: HandlerDeps): Partial> {\n const { manager, ollama, defaultModel, adapterRegistry, suppression, memorySinkRegistry } = deps;\n return {\n // ── Phase 2 memory tools (Plan 02-04) ──────────────────────────────────\n record_observation: async (a) => {\n const p = a as {\n vault: string;\n claim: string;\n evidence: string[];\n confidence: \"direct\" | \"inferred\" | \"uncertain\";\n type: string;\n sink?: string;\n properties?: Record;\n };\n // Suppress the watcher event for the soon-to-be-written file.\n // We don't know the exact filename yet (controller mints it), so\n // suppress the observations/ folder path prefix; the watcher's\n // suppression set tolerates fuzzy matches via the TTL.\n const result = await handleRecordObservation(\n {\n memorySinkRegistry,\n manager,\n deliveryAdapterFor: (vaultName) =>\n adapterRegistry.resolveDelivery(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n // After the write, suppress the watcher event using the minted\n // DocId so live-indexing doesn't re-fire on our own write.\n if (result.ok) {\n const resource = result.doc_id.replace(`obsidian-fs://${p.vault}/`, \"\");\n suppression.add(resource);\n }\n return result;\n },\n supersede: async (a) => {\n const p = a as {\n doc_id: string;\n replacement_doc_id: string;\n reason: string;\n };\n const result = await handleSupersede(\n {\n memorySinkRegistry,\n manager,\n deliveryAdapterFor: (vaultName) =>\n adapterRegistry.resolveDelivery(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n if (result.ok) {\n const resource = result.doc_id.replace(/^obsidian-fs:\\/\\/[^/]+\\//, \"\");\n suppression.add(resource);\n }\n return result;\n },\n\n // ── Phase 2 memory tools (Plan 02-05) ──────────────────────────────────\n recall: async (a) => {\n const p = a as {\n query: string;\n min_confidence?: \"direct\" | \"inferred\" | \"uncertain\";\n types?: string[];\n max_age_days?: number;\n sink?: string;\n limit?: number;\n vaults?: string[];\n };\n const packets = await handleRecall(\n {\n memorySinkRegistry,\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n searchHybrid: async (input) =>\n hybridSearch({\n query: input.query,\n embeddingModel: defaultModel,\n ollama,\n vaults: input.vaults,\n topK: input.topK,\n rrfK: 60,\n includeBreakdown: false,\n }),\n },\n p,\n );\n return { packets, count: packets.length };\n },\n };\n}\n","/**\n * Brief-domain MCP handler factory.\n *\n * Tools: compile_brief, get_brief.\n *\n * Extracted verbatim from the inline `handlers` literal in `src/server.ts`.\n * Behavior-neutral — same brief-controller calls, same post-write\n * suppression bookkeeping.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. Brief writes\n * route through the delivery adapter seam; `obsidian-fs://` literals are\n * adapter handle strings, not display URLs.\n */\n\nimport { parseSourceHandle } from \"../../adapters/registry.js\";\nimport { handleCompileBrief, handleGetBrief } from \"../../brief/index.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nexport function makeBriefHandlers(deps: HandlerDeps): Partial> {\n const { manager, ollama, adapterRegistry, suppression, memorySinkRegistry, server, config } =\n deps;\n return {\n // ── Phase 5 brief tools (Plan 05-02 / BRF-03, BRF-04) ──────────────────\n compile_brief: async (a) => {\n const p = a as {\n vault: string;\n target: string;\n source_doc_ids: string[];\n purpose: string;\n max_tokens?: number;\n prepared_text?: string;\n sink?: string;\n };\n const result = await handleCompileBrief(\n {\n memorySinkRegistry,\n manager,\n deliveryAdapterFor: (vaultName) =>\n adapterRegistry.resolveDelivery(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n server,\n ollama,\n briefConfig: config.brief,\n },\n p,\n );\n // Suppress watcher events for the soon-to-be-indexed brief +\n // (when D-12 chain fires) the just-updated prior brief.\n if (result.ok) {\n const resource = result.doc_id.replace(`obsidian-fs://${p.vault}/`, \"\");\n suppression.add(resource);\n if (result.supersededPrior) {\n const oldResource = result.supersededPrior.replace(/^obsidian-fs:\\/\\/[^/]+\\//, \"\");\n suppression.add(oldResource);\n }\n }\n return result;\n },\n get_brief: async (a) => {\n const p = a as {\n vault: string;\n target: string;\n max_age_days?: number;\n allow_stale?: boolean;\n };\n return handleGetBrief(\n {\n memorySinkRegistry,\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n },\n };\n}\n","/**\n * `assembleDossier` — the ASM-04 controller.\n *\n * Resolves a `{type, key}` pair to an anchor `Document` and walks its\n * backlinks to produce a `DossierResult`:\n *\n * - `anchor` — citation packet for the matched document (or `null`\n * when no doc matches the type+key pair).\n * - `linked_documents` — citation packets for every document linking\n * TO the anchor (backlinks), each tagged with `relation: \"wikilink\"`.\n * In v2.0.0 the v1 `wikilinks` table only stores wikilink edges; the\n * `relation` field widens additively in Phase 4 (GRA-04 typed edges).\n * Search for `PHASE-4-WIDEN` to find the one-line change point.\n * - `property_rollups` — `{ linked_count, linked_types, status_distribution }`,\n * aggregated in a single pass over `linked_documents`. Keys missing\n * from a linked doc's properties are bucketed as `\"unknown\"`. Counts\n * are emitted with alphabetically-sorted keys for deterministic\n * JSON serialization.\n * - `error` — structured `{ code: \"no_matching_anchor_document\", type,\n * key }` when no anchor document matches; `null` on success. This\n * replaces the \"silent empty\" anti-pattern (D-04).\n *\n * # Resolution rules\n *\n * - **Strict `properties.type` match (D-03).** Exact string equality\n * against `Document.properties.type`. No tag fallback, no case\n * folding, no synonym expansion.\n * - **Key matches `title` OR any entry in `properties.aliases`\n * (D-04).** `aliases` is a `string[]` from frontmatter. The match\n * is exact-string. Aliases that are not strings are ignored (no\n * coercion).\n * - **Deterministic tiebreak** when multiple docs of `type` match a\n * given key (rare; can happen if two docs share a title): pick the\n * candidate whose `(title, doc_id)` sorts FIRST lexicographically.\n * This guarantees determinism across runs and across adapter\n * implementations.\n *\n * # No status filtering\n *\n * Per the CONTEXT.md §Specifics caveat, dossiers show the WHOLE\n * picture — superseded backlinks DO appear in `linked_documents` with\n * their `status` field populated. Agents that want a status filter\n * apply one client-side over the result; only search applies an\n * implicit `status: \"superseded\"` hide (recall, D-01).\n *\n * # Adapter-seam discipline (ADR-002 I-1..I-7)\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. All vault content access goes through\n * the injected `SourceConnector` (`readDocument`). Frontmatter reads\n * for the anchor-resolution step go through the `vault.db` query\n * namespace, which holds the already-indexed `notes.frontmatter` JSON\n * blob — that's L0 substrate, owned by the existing indexer.\n *\n * # Performance budget\n *\n * Anchor resolution is O(N) over `notes.frontmatter` rows whose\n * `properties.type === args.type`. The Atlas Robotics fixture is ~75\n * notes; query is sub-millisecond. If real-world dossiers exhibit hot\n * type queries (e.g. `type: \"Meeting\"` over a vault with thousands of\n * meeting notes), Phase 5 may add a `notes_type` index. Do not\n * pre-optimize.\n */\n\nimport { formatDocId } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport { queryFrontmatter } from \"../frontmatter/query.js\";\nimport { listBacklinks } from \"../graph/graph.js\";\nimport {\n type CitationPacket,\n displayUrlFor,\n toCitationPacket,\n withPropertyExtras,\n} from \"../memory/citation-packet.js\";\nimport type { Document } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\n\n/**\n * Dossier deps — supplied at server bootstrap. Mirrors the recall\n * controller's seam pattern so the production wiring and unit tests\n * use the same shape.\n */\nexport interface AssembleDossierDeps {\n /** Vault manager — resolves vault names to `Vault` records. */\n manager: VaultManager;\n /** Resolve the `SourceConnector` instance for a vault name. */\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\n/**\n * Dossier arguments — `{type, key, vaults?}`.\n *\n * `type` is matched exactly against `Document.properties.type`; `key`\n * matches against the candidate's `title` OR any entry in\n * `properties.aliases`. `vaults` optionally narrows the search to a\n * subset of registered vaults; omitting it falls back to \"all configured\n * vaults\" (mirrors the recall convention).\n */\nexport interface AssembleDossierArgs {\n type: string;\n key: string;\n vaults?: string[];\n}\n\n/**\n * One linked document — a full `CitationPacket` (8 required fields per\n * D-01, properties always populated) plus dossier-specific extras:\n *\n * - `status` (optional) — denormalized from `properties.status`;\n * surfaced as a top-level field for agent convenience (saves a\n * properties lookup in the common case).\n * - `superseded_by` (optional) — denormalized from\n * `properties.superseded_by` for the same reason.\n * - `relation` — edge type. Always `\"wikilink\"` in v2.0.0 (the v1\n * wikilinks table is the only edge source); Phase 4 widens to the\n * full `Edge.type` enum.\n *\n * Intersection (not redefinition) of `CitationPacket` — `linked.properties`\n * is REQUIRED and always a `Record`, never `undefined`.\n */\nexport type LinkedDocument = CitationPacket & {\n status?: string;\n superseded_by?: string;\n relation: \"wikilink\";\n};\n\n/**\n * Anchor — citation packet for the resolved document plus the same\n * `status` / `superseded_by` denormalized extras. `null` when no\n * matching anchor was found (see `error`).\n */\nexport type DossierAnchor = CitationPacket & {\n status?: string;\n superseded_by?: string;\n};\n\nexport interface DossierError {\n code: \"no_matching_anchor_document\";\n type: string;\n key: string;\n}\n\n/**\n * Structured dossier result. `anchor === null` iff `error !== null`.\n */\nexport interface DossierResult {\n anchor: DossierAnchor | null;\n linked_documents: LinkedDocument[];\n property_rollups: {\n linked_count: number;\n /** Bucketed by `properties.type` per linked doc. Missing → `\"unknown\"`. */\n linked_types: Record;\n /** Bucketed by `properties.status` per linked doc. Missing → `\"unknown\"`. */\n status_distribution: Record;\n };\n /** `null` on success; structured error code on no-match (D-04). */\n error: DossierError | null;\n}\n\n// ─── helpers ─────────────────────────────────────────────────────────────────\n\nfunction emptyResult(args: AssembleDossierArgs): DossierResult {\n return {\n anchor: null,\n linked_documents: [],\n property_rollups: {\n linked_count: 0,\n linked_types: {},\n status_distribution: {},\n },\n error: {\n code: \"no_matching_anchor_document\",\n type: args.type,\n key: args.key,\n },\n };\n}\n\n/**\n * Sort a `Record` by key (alphabetical) for\n * deterministic JSON serialization. Returns a fresh object; does not\n * mutate the input.\n */\nfunction sortByKey(counts: Record): Record {\n const keys = Object.keys(counts).sort();\n const out: Record = {};\n for (const k of keys) {\n out[k] = counts[k] as number;\n }\n return out;\n}\n\n/**\n * Read aliases from a parsed frontmatter object. Returns the array of\n * string entries (ignoring non-string entries). `null` when the\n * `aliases` key is missing or not an array.\n */\nfunction readAliases(props: Record): string[] {\n const raw = props.aliases;\n if (!Array.isArray(raw)) return [];\n const out: string[] = [];\n for (const v of raw) {\n if (typeof v === \"string\") out.push(v);\n }\n return out;\n}\n\n/**\n * Sort-key string for the deterministic tiebreak. NOT a real DocId —\n * just a stable, vault-scoped, lex-orderable identifier used inside\n * `findAnchorCandidate` / `findAnchorAcrossVaults`. The scheme prefix\n * is fixed at `\"vault\"` so the sort key is identical across adapters\n * (sort order is the contract, not the prefix). The actual minted\n * DocId for `readDocument` is derived from the resolving adapter's\n * scheme — see `schemeFromSource` and the call sites in\n * `assembleDossier`.\n */\nfunction noteSortKey(vaultName: string, notePath: string): string {\n return `vault://${vaultName}/${notePath}`;\n}\n\n/**\n * Extract the scheme portion of a SourceConnector.handle (e.g.\n * `\"obsidian-fs\"` from `\"obsidian-fs://my-vault\"`). Used to mint\n * adapter-correct DocIds in `assembleDossier` per ASM-12 source-\n * neutrality (Phase 3 / 03-07): the stub adapter publishes\n * `stub://memory` and dossier MUST construct linked-document DocIds\n * with the matching scheme so `StubSource.readDocument(id)` resolves.\n * Pre-03-07 the scheme was hardcoded to `\"obsidian-fs\"` which silently\n * broke non-Obsidian adapters.\n */\nfunction schemeFromSource(source: SourceConnector): string {\n const parts = source.handle.split(\"://\");\n return parts[0] ?? \"obsidian-fs\";\n}\n\n// ─── candidate resolution (anchor) ──────────────────────────────────────────\n\ninterface AnchorCandidate {\n vaultName: string;\n notePath: string;\n title: string;\n /** Lex tiebreak key — `\u0000<doc_id_string>` for total order. */\n sortKey: string;\n}\n\n/**\n * Walk the candidate set returned by `query_frontmatter({type: args.type})`\n * and return the FIRST candidate (per lex tiebreak) whose `title === args.key`\n * OR whose `properties.aliases` contains `args.key`.\n *\n * Returns `null` when no candidate matches. The candidate set is\n * already type-filtered by SQL; this loop is just the key match.\n */\nfunction findAnchorCandidate(vault: Vault, args: AssembleDossierArgs): AnchorCandidate | null {\n // SQL-level type filter via the existing query_frontmatter path.\n // This reads `notes.frontmatter` (JSON column) with JSON1 extract.\n const rows = queryFrontmatter(vault, {\n where: { type: args.type },\n limit: 1000,\n });\n if (rows.length === 0) return null;\n\n const matches: AnchorCandidate[] = [];\n for (const row of rows) {\n // queryFrontmatter only returns rows with non-null frontmatter, but\n // parse defensively — corrupt JSON has bitten us before.\n let props: Record<string, unknown> = {};\n if (row.frontmatter !== null) {\n try {\n const parsed = JSON.parse(row.frontmatter);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n props = parsed as Record<string, unknown>;\n }\n } catch {\n // Corrupt frontmatter — skip this candidate; do not throw.\n continue;\n }\n }\n\n const titleMatch = row.title === args.key;\n const aliasMatch = readAliases(props).includes(args.key);\n if (!titleMatch && !aliasMatch) continue;\n\n matches.push({\n vaultName: vault.config.name,\n notePath: row.path,\n title: row.title,\n sortKey: `${row.title}\u0000${noteSortKey(vault.config.name, row.path)}`,\n });\n }\n\n if (matches.length === 0) return null;\n // Deterministic tiebreak: sort by (title, doc_id) ASC, take first.\n matches.sort((a, b) => (a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey ? 1 : 0));\n return matches[0] ?? null;\n}\n\n/**\n * Across the candidate vault set, find the FIRST anchor by lex\n * tiebreak across all vaults. The cross-vault tiebreak uses the same\n * `(title, doc_id)` rule — the `doc_id` carries the vault name as the\n * authority, so cross-vault ordering is well-defined.\n */\nfunction findAnchorAcrossVaults(\n vaults: Vault[],\n args: AssembleDossierArgs,\n): AnchorCandidate | null {\n const matches: AnchorCandidate[] = [];\n for (const vault of vaults) {\n const c = findAnchorCandidate(vault, args);\n if (c) matches.push(c);\n }\n if (matches.length === 0) return null;\n matches.sort((a, b) => (a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey ? 1 : 0));\n return matches[0] ?? null;\n}\n\n// ─── public entry point ─────────────────────────────────────────────────────\n\n/**\n * Resolve a `{type, key}` pair to a structured dossier. See the file\n * header for the full algorithm.\n */\nexport async function assembleDossier(\n deps: AssembleDossierDeps,\n args: AssembleDossierArgs,\n): Promise<DossierResult> {\n // 1) Build the candidate vault list. Throws on unknown vault names\n // — the server wraps the exception in errorResponse() at the\n // dispatch boundary.\n const vaults: Vault[] = [];\n if (args.vaults && args.vaults.length > 0) {\n for (const name of args.vaults) {\n vaults.push(deps.manager.require(name));\n }\n } else {\n for (const v of deps.manager.list()) {\n vaults.push(v);\n }\n }\n if (vaults.length === 0) return emptyResult(args);\n\n // 2) Resolve the anchor (type-match + key-match, deterministic\n // tiebreak across all candidate vaults).\n const anchorCandidate = findAnchorAcrossVaults(vaults, args);\n if (anchorCandidate === null) return emptyResult(args);\n\n // 3) Hydrate the anchor Document via the SourceConnector seam.\n const anchorVault = vaults.find((v) => v.config.name === anchorCandidate.vaultName);\n if (anchorVault === undefined) return emptyResult(args);\n const anchorSource = deps.sourceConnectorFor(anchorCandidate.vaultName);\n // ASM-12 source-neutrality: derive scheme from the resolving adapter's\n // handle so non-Obsidian connectors (stub, future Notion) produce\n // adapter-correct DocIds rather than always emitting 'obsidian-fs://'.\n const anchorScheme = schemeFromSource(anchorSource);\n const anchorDocId = formatDocId(\n anchorScheme,\n anchorCandidate.vaultName,\n anchorCandidate.notePath,\n );\n let anchorDoc: Document;\n try {\n anchorDoc = await anchorSource.readDocument(anchorDocId);\n } catch {\n // The candidate row was indexed, but the file was deleted between\n // index and assembly. Treat as no-match — the structured error\n // surfaces \"no anchor document\" honestly without exposing the race.\n return emptyResult(args);\n }\n const anchorPacket: DossierAnchor = withPropertyExtras(\n toCitationPacket(anchorDoc, displayUrlFor(anchorDocId, anchorSource)),\n );\n\n // 4) Read backlinks via the Phase 1 graph layer. `listBacklinks`\n // looks the note up by path inside the anchor's vault, then walks\n // the v1 `wikilinks` table for source notes pointing to it. In\n // v2.0.0 every edge in that table is a wikilink; Phase 4 will\n // widen the surface to typed edges.\n let backlinkRows: ReturnType<typeof listBacklinks>;\n try {\n backlinkRows = listBacklinks(anchorVault, anchorCandidate.notePath);\n } catch {\n // listBacklinks throws if the note isn't indexed — same race\n // window as the readDocument try/catch above. Surface no-match.\n return emptyResult(args);\n }\n\n // 5) Hydrate each backlink: read the source Document via the\n // SourceConnector, build a citation packet, attach\n // `relation: \"wikilink\"` plus the denormalized extras.\n const linkedDocuments: LinkedDocument[] = [];\n for (const bl of backlinkRows) {\n const linkedDocId = formatDocId(anchorScheme, anchorCandidate.vaultName, bl.sourcePath);\n let linkedDoc: Document;\n try {\n linkedDoc = await anchorSource.readDocument(linkedDocId);\n } catch {\n // A stale backlink (source file deleted between index and read)\n // is harmless; silently drop. Same defensive posture as recall.\n continue;\n }\n const packet: CitationPacket = toCitationPacket(\n linkedDoc,\n displayUrlFor(linkedDocId, anchorSource),\n );\n const withExtras = withPropertyExtras(packet);\n // PHASE-4-WIDEN: v2.0.0 reads from the v1 wikilinks table, which\n // stores only `\"wikilink\"` edges. When GRA-04 introduces typed\n // edges, this hardcoded literal becomes `edge.type` and the\n // `relation` field in `LinkedDocument` widens to `EdgeType`.\n linkedDocuments.push({\n ...withExtras,\n relation: \"wikilink\" as const,\n });\n }\n\n // 6) Compute rollups in a single pass. Keys are sorted\n // alphabetically before return for deterministic JSON output.\n const linked_types: Record<string, number> = {};\n const status_distribution: Record<string, number> = {};\n for (const linked of linkedDocuments) {\n const type = typeof linked.properties.type === \"string\" ? linked.properties.type : \"unknown\";\n linked_types[type] = (linked_types[type] ?? 0) + 1;\n const status =\n typeof linked.properties.status === \"string\" ? linked.properties.status : \"unknown\";\n status_distribution[status] = (status_distribution[status] ?? 0) + 1;\n }\n\n return {\n anchor: anchorPacket,\n linked_documents: linkedDocuments,\n property_rollups: {\n linked_count: linkedDocuments.length,\n linked_types: sortByKey(linked_types),\n status_distribution: sortByKey(status_distribution),\n },\n error: null,\n };\n}\n","/**\n * `getDocumentBundle` — the ASM-01 controller (Phase 3, Plan 03-04).\n *\n * Returns the document-tree retrieval surface that composes every other\n * Phase 3 read into one response:\n *\n * - `anchor` — citation packet (8 required D-01 fields) for the\n * anchor document, plus optional `status` /\n * `superseded_by` denormalized extras (ASM-06) read\n * from `properties` via the same hydration path\n * extended by Plan 03-05.\n * - `outline` — the section tree from `buildOutlineTree`\n * (re-used from 03-02 — NOT duplicated).\n * - `backlinks` — citation packets + `property_snippet` (≤200 chars\n * of plain-text body from the linking doc) +\n * `relation: \"wikilink\"`. In v2.0.0 the v1\n * `wikilinks` table is the only edge source; Phase\n * 4 widens `relation` additively.\n * - `forward_links` — citation packets + `property_snippet` +\n * `relation: \"wikilink\"`.\n * - `recent_edits` — up to 10 most recent `audit_log` entries for the\n * anchor's CURRENT note path, mapped to\n * `BundleRecentEdit`.\n *\n * # Citation packet contract (M1 fix — single source of truth)\n *\n * Every packet (anchor, backlinks, forward_links) is built via\n * `toCitationPacket()` from `src/memory/citation-packet.ts`. The bundle\n * does NOT redefine the 8-field shape. Bundle-specific extras\n * (`property_snippet`, `relation`, `status?`, `superseded_by?`) are\n * intersected onto `CitationPacket` (`CitationPacket & { ...extras }`).\n *\n * `CitationPacket.properties` is REQUIRED (`Record<string, unknown>`,\n * always populated by the mapper; `{}` when the doc has no frontmatter).\n * Bundle entries therefore never carry `properties: undefined`.\n *\n * # `depth: 1` semantics (only value accepted in v2.0.0)\n *\n * One-hop backlinks / forward links. The Zod schema in `tool-registry.ts`\n * pins `depth` to `z.literal(1).optional().default(1)` — higher values\n * are not accepted today. Phase 4 may widen additively.\n *\n * # Recent-edits rename-history limitation (M3 — documented, no fix)\n *\n * `getAuditLog({notePath})` (see `src/audit/audit.ts:93-97`) looks up\n * entries by CURRENT note path. Pre-rename audit_log rows are keyed on\n * `note_id` internally, so the path-keyed lookup misses them. If a doc\n * was renamed from `foo.md` → `bar.md`, asking\n * `get_document_bundle({doc_id: \"obsidian-fs://vault/bar.md\"})`\n * surfaces only the post-rename edits.\n *\n * Why this is acceptable for v2.0.0:\n * - Phase 3 is read-side; no new write path widens the rename problem.\n * - The audit_log retains pre-rename rows for forensic purposes;\n * they're queryable directly via `audit_log({note_path})` for the\n * OLD path until the note row is purged.\n * - The collaborative-vault domain (\"tolerating collaborators\n * renaming notes\") names this as a design pressure but does not\n * require Phase 3 to surface pre-rename history in `recent_edits`.\n *\n * Phase 4 widens this — the graph layer will centralize\n * `doc_id → note_id` resolution and the bundle can switch to the\n * `note_id`-keyed audit_log lookup.\n *\n * # Adapter-seam discipline (ADR-002 I-1..I-7)\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. All `Document` reads route through the\n * injected `SourceConnector` (`readDocument`). SQLite namespace access\n * (`vault.db.notes`, `vault.db.sections`, `vault.db.chunks`,\n * `vault.db.audit`, `vault.db.wikilinks`) is L0 substrate, owned by the\n * existing query layer — fine.\n */\n\nimport { decomposeDocId, formatDocId, parseDocId } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport { getAuditLog } from \"../audit/audit.js\";\nimport { listBacklinks, listForwardLinks } from \"../graph/graph.js\";\nimport type { EdgeType } from \"../graph/graph.js\";\nimport {\n type CitationPacket,\n displayUrlFor,\n toCitationPacket,\n withPropertyExtras,\n} from \"../memory/citation-packet.js\";\nimport { DocNotFoundError } from \"./outline.js\";\nimport { buildOutlineTree } from \"./outline.js\";\nimport type { OutlineNode } from \"./types.js\";\nimport type { BlockNode, ChunkRow, Document, SectionRow } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\n\n/**\n * Maximum number of audit-log rows surfaced in `recent_edits`.\n * Plan §\"Acceptance criteria\" — `recent_edits` length ≤ 10 even when\n * the audit log has more entries.\n */\nconst RECENT_EDITS_LIMIT = 10;\n\n/**\n * Maximum length (in chars) of the body plain-text snippet attached to\n * each backlink / forward-link entry. Plan §\"Property snippet\":\n * \"first 200 chars of plain-text-rendered body.\"\n */\nconst PROPERTY_SNIPPET_MAX = 200;\n\n/**\n * Injected dependencies for `getDocumentBundle`. Mirrors `GetOutlineDeps`\n * / `AssembleDossierDeps` so production wiring + unit tests share one\n * shape.\n */\nexport interface GetDocumentBundleDeps {\n manager: VaultManager;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\n/**\n * Validated input shape for `get_document_bundle`. Matches the Zod\n * `GetDocumentBundleArgs` schema in `src/tool-registry.ts`.\n */\nexport interface GetDocumentBundleArgs {\n /** Opaque DocId — `<scheme>://<authority>/<resource>`. */\n doc_id: string;\n /**\n * Depth of the link walk. v2.0.0 accepts ONLY `1`. The Zod schema\n * pins the literal; this field is here for forward compatibility.\n */\n depth?: 1;\n /** Optional vault filter; usually omitted (the DocId names a vault). */\n vaults?: string[];\n}\n\n/**\n * Anchor citation packet — full 8-field `CitationPacket` plus the\n * optional ASM-06 denormalized extras (`status`, `superseded_by`).\n * Read from `Document.properties` via the same hydration path Plan\n * 03-05 extends in `search_hybrid` and `recall`.\n */\nexport type BundleAnchor = CitationPacket & {\n status?: string;\n superseded_by?: string;\n};\n\n/**\n * One backlink entry — full citation packet + bundle-specific extras.\n *\n * - `property_snippet` — first ≤200 chars of the linking doc's\n * plain-text body (frontmatter stripped — the\n * `Document` shape already separates\n * `properties` from `blocks`, so no manual\n * frontmatter strip is needed).\n * - `relation` — `EdgeType` (Phase 4 / 04-01 / D-04). Reads\n * route through `vault.db.edges` (post-04-01\n * backfill) and `bl.type` / `fl.type` carry\n * the actual edge type. Post-backfill every\n * row is `'wikilink'`; Plan 04-02 widens to\n * the other three `Edge.type` literals once\n * the indexer populates them. COMPLETED\n * Phase 4 / 04-01.\n *\n * `heading_path` is inherited from `CitationPacket` and is `[]` for\n * document-level links per `<specifics>` (only outline nodes carry a\n * non-empty heading_path).\n */\nexport type BacklinkEntry = CitationPacket & {\n property_snippet: string;\n // ── Phase 4 / 04-01 / GRA-04 (D-01, D-04): widen `relation` to EdgeType ──\n //\n // Strict widening from the prior `\"wikilink\"` literal. Post-backfill all\n // existing rows still carry `\"wikilink\"`; Plan 04-02 starts populating\n // the other three types in the same column.\n relation: EdgeType;\n};\n\n/**\n * One forward-link entry — same shape as `BacklinkEntry`. Distinct type\n * alias for clarity at call sites.\n */\nexport type ForwardLinkEntry = CitationPacket & {\n property_snippet: string;\n /** Phase 4 / 04-01 (D-04) — widened from `\"wikilink\"` to `EdgeType`. */\n relation: EdgeType;\n};\n\n/**\n * One row from `recent_edits`. Mapped from `AuditLogEntry`:\n *\n * - `at` — epoch ms.\n * - `op` — create | update | delete.\n * - `client_id` — `null` for user-originated writes, real string\n * for agent writes (a sink-route caller plus a\n * `client_id` argument to write tools).\n * - `is_memory_sink_write` — Plan 02-06 (MEM-08) discriminator.\n * Surfaced ONLY when `true` (optional field) so the\n * bundle wire shape stays compact for the common\n * non-memory case.\n *\n * `recent_edits` is keyed by the anchor's CURRENT note path; pre-rename\n * history is not surfaced. See the file header §\"Recent-edits\n * rename-history limitation\".\n */\nexport interface BundleRecentEdit {\n at: number;\n op: \"create\" | \"update\" | \"delete\";\n client_id: string | null;\n is_memory_sink_write?: boolean;\n}\n\n/**\n * Wire shape of the `get_document_bundle({doc_id})` MCP tool response.\n */\nexport interface BundleResult {\n anchor: BundleAnchor;\n outline: OutlineNode[];\n backlinks: BacklinkEntry[];\n forward_links: ForwardLinkEntry[];\n recent_edits: BundleRecentEdit[];\n}\n\n// ─── helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Render a `BlockNode[]` to plain text and truncate to\n * `PROPERTY_SNIPPET_MAX` chars. The `Document` block tree already\n * separates `properties` (frontmatter) from `blocks` (body), so no\n * frontmatter strip is needed — we just project block text.\n *\n * Adapter contract (`bodyShape: \"flat-text\"`, see\n * `src/adapters/capabilities.ts`): the obsidian-fs adapter emits a\n * single `{kind: \"paragraph\", text: body}` block, so the common case is\n * trivial. Other block kinds project their `text` / `items` content;\n * `section` blocks recurse into their nested `blocks`. Unknown kinds\n * project as `\"\"` (defensive — the closed union narrows this away at\n * the type level today).\n */\nfunction bodyPlainText(blocks: BlockNode[]): string {\n const parts: string[] = [];\n for (const b of blocks) {\n switch (b.kind) {\n case \"paragraph\":\n case \"code\":\n parts.push(b.text);\n break;\n case \"heading\":\n parts.push(b.text);\n break;\n case \"list\":\n parts.push(b.items.join(\" \"));\n break;\n case \"section\":\n // Recurse into the section's nested blocks. The section's own\n // heading is NOT projected here — it lives in `heading_path`,\n // which is presentation metadata, not body content.\n parts.push(bodyPlainText(b.blocks));\n break;\n default:\n // TypeScript narrows away the `default` for the closed union;\n // this is dead-code defense for future widenings.\n break;\n }\n }\n const text = parts.join(\" \").trim();\n if (text.length <= PROPERTY_SNIPPET_MAX) return text;\n return text.slice(0, PROPERTY_SNIPPET_MAX);\n}\n\n// ─── public entry point ─────────────────────────────────────────────────────\n\n/**\n * Assemble the document bundle for a `doc_id`. See file header for the\n * full algorithm.\n *\n * Throws `DocNotFoundError` (caught by the server dispatch and wrapped\n * into the `{error: \"doc_not_found\", doc_id}` payload) on:\n * - Malformed `doc_id`.\n * - Unknown vault.\n * - `vaults` filter that excludes the DocId's vault.\n * - Missing note row (note not indexed, OR deleted between catch-up\n * and this call).\n * - SourceConnector read failure on the anchor doc.\n */\nexport async function getDocumentBundle(\n deps: GetDocumentBundleDeps,\n args: GetDocumentBundleArgs,\n): Promise<BundleResult> {\n // 1) Validate-decompose the DocId. `parseDocId` throws on malformed\n // input — surface as `doc_not_found` (callers gave us a bad id).\n let parsed: { scheme: string; authority: string; resource: string };\n try {\n const docId = parseDocId(args.doc_id);\n parsed = decomposeDocId(docId);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n const { scheme: anchorScheme, authority: vaultName, resource: path } = parsed;\n\n // Optional vault-filter narrowing. The DocId already names a vault;\n // the filter exists for callers asserting a known tenant boundary.\n if (args.vaults && args.vaults.length > 0 && !args.vaults.includes(vaultName)) {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 2) Resolve the Vault. `manager.require` throws on unknown — map to\n // DocNotFoundError so the wire response is consistent with\n // get_outline.\n let vault: Vault;\n try {\n vault = deps.manager.require(vaultName);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 3) Look up the note row by path. Missing row → doc_not_found.\n const noteRow = vault.db.notes.getByPath(path);\n if (!noteRow) {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 4) Read the anchor Document via the SourceConnector seam. We use\n // the canonical packet helper so display-URL resolution matches\n // recall + the rest of Phase 3 byte-for-byte.\n const source = deps.sourceConnectorFor(vaultName);\n const docId = parseDocId(args.doc_id);\n let anchorDoc: Document;\n try {\n anchorDoc = await source.readDocument(docId);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n // `withPropertyExtras` returns `CitationPacket & {status?; superseded_by?}`,\n // structurally identical to `BundleAnchor`; the annotation pins the type.\n const anchorPacket: BundleAnchor = withPropertyExtras(\n toCitationPacket(anchorDoc, displayUrlFor(docId, source)),\n );\n\n // 5) Build the outline tree via 03-02's helper. Re-use, do NOT\n // duplicate. Sections are returned in parent-NULL-first order,\n // and chunks are pre-loaded once for all sections (see\n // outline.ts §\"7-prep\" note).\n const sectionRows: SectionRow[] = vault.db.sections.getByNote(noteRow.id);\n const allChunks: ChunkRow[] = vault.db.chunks.getByNote(noteRow.id);\n const outline = buildOutlineTree(sectionRows, allChunks);\n\n // 6) Read backlinks via the Phase 1 graph layer. In v2.0.0 every\n // edge in the v1 `wikilinks` table is a wikilink; Phase 4 widens\n // to typed edges. `listBacklinks` throws if the anchor note is\n // unindexed — we already verified its existence in step 3, so\n // any throw here is a genuine race we map to `doc_not_found`.\n let backlinkRows: ReturnType<typeof listBacklinks>;\n try {\n backlinkRows = listBacklinks(vault, path);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // Hydrate each backlink — read the source `Document` via the\n // SourceConnector (single adapter-seam read), build a citation\n // packet, attach `property_snippet` (first 200 chars of plain-text\n // body) and `relation: \"wikilink\"`. Stale backlink rows (source\n // file deleted between index and read) are silently dropped — same\n // defensive posture as dossier + recall.\n const backlinks: BacklinkEntry[] = [];\n for (const bl of backlinkRows) {\n const sourceDocId = formatDocId(anchorScheme, vaultName, bl.sourcePath);\n let linkedDoc: Document;\n try {\n linkedDoc = await source.readDocument(sourceDocId);\n } catch {\n continue;\n }\n const packet = toCitationPacket(linkedDoc, displayUrlFor(sourceDocId, source));\n // PHASE-4-WIDEN: v1 wikilinks-only graph reads now route through the\n // typed-edges substrate (`vault.db.edges`, Plan 04-01). `bl.type`\n // sources the actual edge type per row; post-backfill this is\n // `'wikilink'` for every row, and Plan 04-02 starts producing\n // mention / frontmatter-ref / hyperlink. COMPLETED Phase 4 / 04-01.\n backlinks.push({\n ...packet,\n property_snippet: bodyPlainText(linkedDoc.blocks),\n relation: bl.type,\n });\n }\n\n // 7) Read forward links via the symmetric graph helper. We pass\n // `includeBroken: false` because broken links (`resolved: false`)\n // carry no target note row and cannot be hydrated via the\n // SourceConnector — there's no document to cite. The user can\n // still discover broken outbound links via `find_broken_links` /\n // `list_forward_links`. Phase 4 may surface them as a separate\n // `broken_forward_links` array if the use case emerges.\n let forwardLinkRows: ReturnType<typeof listForwardLinks>;\n try {\n forwardLinkRows = listForwardLinks(vault, path, /* includeBroken */ false);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n\n const forward_links: ForwardLinkEntry[] = [];\n for (const fl of forwardLinkRows) {\n const targetDocId = formatDocId(anchorScheme, vaultName, fl.targetPath);\n let linkedDoc: Document;\n try {\n linkedDoc = await source.readDocument(targetDocId);\n } catch {\n continue;\n }\n const packet = toCitationPacket(linkedDoc, displayUrlFor(targetDocId, source));\n forward_links.push({\n ...packet,\n property_snippet: bodyPlainText(linkedDoc.blocks),\n // PHASE-4-WIDEN — see backlinks loop above. COMPLETED Phase 4 / 04-01.\n relation: fl.type,\n });\n }\n\n // 8) Recent edits — capped at RECENT_EDITS_LIMIT (10). `getAuditLog`\n // returns entries in DB-default order (newest first by id DESC;\n // see `src/db/queries/audit.ts` listWrites SQL). Map each entry\n // onto `BundleRecentEdit`, surfacing only the fields the bundle\n // documents — keeps the wire shape stable as the underlying\n // `AuditLogEntry` grows.\n //\n // Rename-history caveat: `getAuditLog({notePath})` is keyed on\n // the current note row; pre-rename entries are not surfaced. See\n // the file header §\"Recent-edits rename-history limitation\".\n const auditEntries = getAuditLog({\n vault,\n notePath: path,\n limit: RECENT_EDITS_LIMIT,\n });\n const recent_edits: BundleRecentEdit[] = auditEntries.map((e) => {\n const out: BundleRecentEdit = {\n at: e.at,\n op: e.op,\n client_id: e.clientId,\n };\n // Only surface the flag when truthy — keeps the bundle wire\n // shape compact for the common non-memory write case.\n if (e.is_memory_sink_write) out.is_memory_sink_write = true;\n return out;\n });\n\n // 9) Assemble. The bundle response does NOT carry a top-level\n // `source_handle` — the anchor citation packet already exposes\n // it as part of its 8-field shape, and every backlink /\n // forward-link entry carries its own (same vault in v2.0.0, but\n // Phase 4 cross-adapter graph walks may surface heterogeneous\n // source handles).\n return {\n anchor: anchorPacket,\n outline,\n backlinks,\n forward_links,\n recent_edits,\n };\n}\n","/**\n * Phase 3 — `src/assembly/` barrel.\n *\n * The assembly layer composes the section-identity substrate (`src/sections/`,\n * landed in 03-01) into higher-level reading tools:\n *\n * - 03-02: `get_outline` — nested section tree.\n * - 03-03: `search_sections` — section-level retrieval.\n * - 03-04: `get_bundle` — section-window assembly.\n * - 03-05: search_hybrid rescore (authority / staleness).\n * - 03-06: `assemble_dossier` — multi-bundle synthesis with property rollups.\n *\n * Adapter-seam discipline (per 03-CONTEXT.md, enforced by\n * `scripts/lint-adapters.sh`): nothing under `src/assembly/` imports\n * `fs`, `gray-matter`, `chokidar`, or `path.*`. Document reads go\n * through the injected `SourceConnector` seam.\n */\n\nexport { assembleDossier } from \"./dossier.js\";\nexport type {\n AssembleDossierArgs,\n AssembleDossierDeps,\n DossierAnchor,\n DossierError,\n DossierResult,\n LinkedDocument,\n} from \"./dossier.js\";\nexport { getDocumentBundle } from \"./bundle.js\";\nexport type {\n BacklinkEntry,\n BundleAnchor,\n BundleRecentEdit,\n BundleResult,\n ForwardLinkEntry,\n GetDocumentBundleArgs,\n GetDocumentBundleDeps,\n} from \"./bundle.js\";\nexport { getOutline, type GetOutlineDeps } from \"./outline.js\";\nexport type { OutlineNode, OutlineResult, GetOutlineArgs } from \"./types.js\";\n","/**\n * Assembly-domain MCP handler factory.\n *\n * Tools: get_outline, search_sections, assemble_dossier, get_document_bundle.\n *\n * Extracted verbatim from the inline `handlers` literal in `src/server.ts`.\n * Behavior-neutral — each arrow wires args to the same assembly-controller\n * call with the same injected seam closures.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. All document\n * reads + display-URL minting flow through the adapter registry seam.\n */\n\nimport { formatDocId, parseSourceHandle } from \"../../adapters/registry.js\";\nimport { getOutline } from \"../../assembly/outline.js\";\nimport { searchSections } from \"../../assembly/search-sections.js\";\nimport { assembleDossier, getDocumentBundle } from \"../../assembly/index.js\";\nimport { hybridSearch } from \"../../search/index.js\";\nimport type { Vault } from \"../../vault/index.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nexport function makeAssemblyHandlers(deps: HandlerDeps): Partial<Record<ToolName, Handler>> {\n const { manager, ollama, defaultModel, adapterRegistry } = deps;\n return {\n // ── Phase 3 assembly tools (Plan 03-02 / ASM-02) ───────────────────────\n get_outline: async (a) => {\n const p = a as { doc_id: string; vaults?: string[] };\n return getOutline(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n },\n\n // ── Phase 3 assembly tools (Plan 03-03) ──────────────────────────────────\n search_sections: async (a) => {\n const p = a as {\n query: string;\n limit?: number;\n vaults?: string[];\n recency_weight?: number;\n authority_weight?: number;\n include_superseded?: boolean;\n };\n // Resolve target vaults: callers may scope to a subset; default to\n // all configured vaults (mirrors search_hybrid's behavior).\n const allVaults = manager.list();\n const targetVaults: Vault[] = p.vaults\n ? p.vaults.map((name) => manager.require(name))\n : allVaults;\n\n const results = await searchSections(\n {\n searchHybrid: async (input) =>\n hybridSearch({\n query: input.query,\n embeddingModel: defaultModel,\n ollama,\n vaults: input.vaults\n ? input.vaults.map((name) => manager.require(name))\n : targetVaults,\n topK: input.topK,\n rrfK: 60,\n includeBreakdown: false,\n }),\n sectionForHit: (vaultName, notePath, chunkIdx) => {\n // Look up via the originating vault's DB. The mapping is\n // (notePath → noteId) → (noteId, chunkIdx → chunkId) →\n // findContainingChunk. Returns null on any miss (stale row\n // or pre-migration-010 chunk) so the controller drops it.\n let vault: Vault;\n try {\n vault = manager.require(vaultName);\n } catch {\n return null;\n }\n const note = vault.db.notes.getByPath(notePath);\n if (!note) return null;\n const chunks = vault.db.chunks.getByNote(note.id);\n const chunk = chunks.find((c) => c.idx === chunkIdx);\n if (!chunk) return null;\n const section = vault.db.sections.findContainingChunk(note.id, chunk.id);\n if (!section) return null;\n let headingPath: string[];\n try {\n const parsed = JSON.parse(section.heading_path);\n headingPath = Array.isArray(parsed) ? (parsed as string[]) : [];\n } catch {\n headingPath = [];\n }\n return {\n noteId: note.id,\n anchor: section.anchor,\n headingPath,\n // Sections with a NULL chunk_id_first have been filtered out\n // by findContainingChunk (it requires non-NULL bounds), so\n // chunk_id_first is guaranteed non-null here. Fall back to\n // MAX_SAFE_INTEGER defensively for the tie-break sort.\n chunkIdFirst: section.chunk_id_first ?? Number.MAX_SAFE_INTEGER,\n };\n },\n readDocument: async (vaultName, notePath) => {\n const docId = formatDocId(\"obsidian-fs\", vaultName, notePath);\n return adapterRegistry\n .resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`))\n .readDocument(docId);\n },\n displayUrlFor: (docId, vaultName) => {\n const source = adapterRegistry.resolveSource(\n parseSourceHandle(`obsidian-fs://${vaultName}`),\n );\n return source.formatDisplayUrl?.(docId) ?? docId;\n },\n },\n {\n query: p.query,\n limit: p.limit ?? 10,\n ...(p.vaults !== undefined ? { vaults: p.vaults } : {}),\n ...(p.recency_weight !== undefined ? { recency_weight: p.recency_weight } : {}),\n ...(p.authority_weight !== undefined ? { authority_weight: p.authority_weight } : {}),\n ...(p.include_superseded !== undefined\n ? { include_superseded: p.include_superseded }\n : {}),\n },\n );\n return { results, count: results.length };\n },\n\n // ── Phase 3 assembly tools (Plan 03-06) ────────────────────────────────\n assemble_dossier: async (a) => {\n const p = a as { type: string; key: string; vaults?: string[] };\n return assembleDossier(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n },\n\n // ── Phase 3 assembly tools (Plan 03-04 / ASM-01) ───────────────────────\n get_document_bundle: async (a) => {\n const p = a as { doc_id: string; depth?: 1; vaults?: string[] };\n return getDocumentBundle(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n },\n };\n}\n","/**\n * Contracts-domain MCP handler factory.\n *\n * Tools: register_contracts_as_tools, describe_contract, instantiate_contract.\n *\n * Extracted verbatim from the inline `handlers` literal in `src/server.ts`.\n * Behavior-neutral. Unlike the other domains, these handlers depend on three\n * serve()-local closures (`resolveContractVault`, `instantiateHandler`,\n * `buildInstantiateDeps`) that capture bootstrap state not present on\n * `HandlerDeps`. Those are passed in via the `ContractHelpers` parameter so\n * the closures stay defined in `serve()` and the call shapes are identical.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports.\n */\n\nimport {\n describeContract,\n instantiateContract,\n syncAutoRegistered,\n} from \"../../contracts/index.js\";\nimport type { InstantiateDeps } from \"../../contracts/index.js\";\nimport type { Vault } from \"../../vault/index.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\n/**\n * Result of resolving the target vault for describe/instantiate. Verbatim\n * from `serve()`'s local `resolveContractVault`.\n */\nexport type ResolveContractVaultResult =\n | { ok: true; vault: Vault }\n | { ok: false; reason: \"ambiguous_vault\"; available_vaults: string[] }\n | { ok: false; reason: \"unknown_vault\"; vault: string };\n\n/**\n * The three serve()-local closures the contract handlers depend on. They\n * capture bootstrap state (`manager`, `adapterRegistry`, `peerMcpRegistry`,\n * per-vault deps, baseline-verb thunks) that is not on `HandlerDeps`, so\n * they are injected rather than reconstructed.\n */\nexport interface ContractHelpers {\n resolveContractVault: (vaultArg: string | undefined) => ResolveContractVaultResult;\n instantiateHandler: (name: string, args: unknown) => Promise<unknown>;\n buildInstantiateDeps: (vault: Vault) => InstantiateDeps;\n}\n\nexport function makeContractsHandlers(\n deps: HandlerDeps,\n helpers: ContractHelpers,\n): Partial<Record<ToolName, Handler>> {\n const { manager, server, config, contractRegistries } = deps;\n const { resolveContractVault, instantiateHandler, buildInstantiateDeps } = helpers;\n return {\n // ── Phase 6 task-contract DSL (Plan 06-02 / D-A1 escape valve) ─────────\n //\n // Scans the per-vault contract registries and forces a sync of the\n // dynamic MCP tool list — regardless of [contracts.auto_register_tools]\n // (which is what makes this the explicit-control escape valve).\n // Returns per-vault diffs so the caller can confirm what landed.\n register_contracts_as_tools: async (a) => {\n const p = a as { vault?: string };\n const targetVaults =\n p.vault !== undefined ? [p.vault] : manager.list().map((v) => v.config.name);\n if (p.vault !== undefined) {\n const v = manager.list().find((vault) => vault.config.name === p.vault);\n if (v === undefined) {\n return { ok: false, reason: \"unknown_vault\", vault: p.vault };\n }\n }\n const results: {\n vault: string;\n registered: string[];\n unregistered: string[];\n }[] = [];\n const prefix = config.contracts.tool_prefix;\n for (const vname of targetVaults) {\n const state = contractRegistries.get(vname);\n if (state === undefined) continue;\n const v = manager.list().find((vault) => vault.config.name === vname);\n if (v === undefined) continue;\n const before = new Set(state.registered.keys());\n // FORCED enabled:true — explicit-control escape valve (D-A1).\n syncAutoRegistered(server, state.started.registry, prefix, state.registered, {\n enabled: true,\n instantiateHandler,\n });\n const after = new Set(state.registered.keys());\n results.push({\n vault: vname,\n registered: Array.from(after).filter((n) => !before.has(n)),\n unregistered: Array.from(before).filter((n) => !after.has(n)),\n });\n }\n if (p.vault !== undefined) {\n const single = results[0] ?? {\n vault: p.vault,\n registered: [],\n unregistered: [],\n };\n return { ok: true, ...single };\n }\n return { ok: true, vaults: results };\n },\n\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-05, Q-DESCRIBE) ────────\n //\n // Pure function over the per-vault ContractRegistry. Returns\n // {ok:true, json_schema, summary} or one of the sealed\n // InstantiateError reasons (`unknown_contract`, `ambiguous_vault`,\n // `unknown_vault`). NO LLM, NO side effects.\n describe_contract: async (a) => {\n const p = a as { name: string; vault?: string };\n const resolved = resolveContractVault(p.vault);\n if (!resolved.ok) return resolved;\n const state = contractRegistries.get(resolved.vault.config.name);\n if (state === undefined) {\n // Defense-in-depth: a vault without a contract registry happens\n // only if `start_contract_registries` skipped it (no change-feed)\n // — surface as unknown_contract for the caller.\n return { ok: false, reason: \"unknown_contract\", name: p.name };\n }\n return describeContract({ registry: state.started.registry }, { name: p.name });\n },\n\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-06) ────────────────────\n //\n // Replaces the Plan 06-02 stub. Routes through the per-vault deps\n // built by `buildInstantiateDeps`. On multi-vault setups, the caller\n // MUST pass `vault` — otherwise we return the WARNING-6\n // `ambiguous_vault` envelope (12th reason in the closed\n // InstantiateError union).\n instantiate_contract: async (a) => {\n const p = a as {\n name: string;\n inputs: Record<string, unknown>;\n source_overrides?: Record<string, string>;\n sink_overrides?: Record<string, string>;\n vault?: string;\n };\n const resolved = resolveContractVault(p.vault);\n if (!resolved.ok) return resolved;\n return instantiateContract(buildInstantiateDeps(resolved.vault), {\n name: p.name,\n inputs: p.inputs,\n ...(p.source_overrides !== undefined ? { source_overrides: p.source_overrides } : {}),\n ...(p.sink_overrides !== undefined ? { sink_overrides: p.sink_overrides } : {}),\n });\n },\n };\n}\n","{\n \"name\": \"@owrede/vault-memory\",\n \"version\": \"2.3.0\",\n \"description\": \"Local-first semantic memory MCP server for Obsidian vaults\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"workspaces\": [\n \"plugin\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/owrede/vault-memory.git\"\n },\n \"bin\": {\n \"vault-memory\": \"dist/cli.js\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\",\n \"CHANGELOG.md\"\n ],\n \"engines\": {\n \"node\": \">=22 <26\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsx watch src/cli.ts\",\n \"start\": \"node dist/cli.js\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"lint\": \"tsc --noEmit\",\n \"lint:adapters\": \"sh scripts/lint-adapters.sh\",\n \"lint:check\": \"sh scripts/check-fixture-privacy.sh && sh scripts/lint-no-telemetry.sh && sh scripts/lint-adapters.sh && tsc --noEmit && prettier --check \\\"src/**/*.ts\\\"\",\n \"format\": \"prettier --write \\\"src/**/*.ts\\\"\",\n \"eval:baseline\": \"vitest run evals/v1-baseline/baseline.test.ts\",\n \"eval:snapshot\": \"node evals/v1-baseline/dump-tools.mjs > evals/v1-baseline/tools-list.snapshot.json && node evals/v1-baseline/dump-resources.mjs > evals/v1-baseline/resources-list.snapshot.json\",\n \"eval:smoketest\": \"npm run build && node scripts/smoketest-non-claude.mjs\",\n \"release\": \"node scripts/release.mjs\",\n \"sync-marketplace\": \"node scripts/sync-marketplace.mjs\"\n },\n \"dependencies\": {\n \"@huggingface/tokenizers\": \"^0.1.3\",\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"better-sqlite3\": \"^11.7.0\",\n \"chokidar\": \"^4.0.1\",\n \"cross-spawn\": \"^7.0.6\",\n \"graphology\": \"^0.26.0\",\n \"graphology-communities-louvain\": \"^2.0.2\",\n \"gray-matter\": \"^4.0.3\",\n \"onnxruntime-node\": \"^1.26.0\",\n \"seedrandom\": \"^3.0.5\",\n \"smol-toml\": \"^1.3.1\",\n \"sqlite-vec\": \"^0.1.6\",\n \"yaml\": \"^2.9.0\",\n \"zod\": \"^4.4.3\"\n },\n \"devDependencies\": {\n \"@types/better-sqlite3\": \"^7.6.12\",\n \"@types/node\": \"^22.10.0\",\n \"@types/seedrandom\": \"^3.0.8\",\n \"prettier\": \"^3.4.0\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.7.0\",\n \"vitest\": \"^2.1.8\"\n }\n}\n","/**\n * Single source of truth for the vault-memory version (Issue #14 / P2).\n *\n * The version lives in `package.json` and nowhere else. `server.ts` previously\n * hardcoded `const VERSION = \"1.0.0\"` which drifted years behind the published\n * package — the MCP server advertised the wrong version and sink provisioning\n * stamped stale sentinels.\n *\n * tsup inlines this JSON import at build time (resolveJsonModule is on), so the\n * bundled `dist/cli.js` carries the literal string with no runtime file read.\n */\nimport pkg from \"../package.json\" with { type: \"json\" };\n\nexport const VERSION: string = pkg.version;\n","/**\n * MCP server.\n *\n * Phase 1 toolset:\n * - list_vaults, read_note, search_semantic\n *\n * Phase 2 toolset:\n * - search_text, search_hybrid\n * - list_backlinks, list_forward_links, find_broken_links\n * - query_frontmatter\n *\n * Phase 3 will add: write_note, update_frontmatter, audit_log\n */\n\nimport { McpServer, ResourceTemplate } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { loadConfig, configPath } from \"./config/index.js\";\nimport { syncPluginTools, RuntimeConfigStore } from \"./plugin-tools/index.js\";\nimport type { TriggerReindexProgress } from \"./plugin-tools/trigger-reindex.js\";\nimport { VaultManager } from \"./vault/index.js\";\nimport type { Vault } from \"./vault/index.js\";\nimport { OllamaClient } from \"./ollama/index.js\";\nimport { hybridSearch } from \"./search/index.js\";\nimport { OllamaReranker, OnnxReranker } from \"./rerank/index.js\";\nimport type { Reranker } from \"./rerank/index.js\";\nimport { errorMessage } from \"./errors/format.js\";\nimport { ok, errorResponse, errorResponseJson } from \"./server/responses.js\";\nimport { displayUrl } from \"./server/utils.js\";\n// Re-export the five utils that `src/server.test.ts` imports from \"./server.js\".\nexport {\n encodeNoteId,\n decodeNoteId,\n truncateSnippet,\n aggregateTopTags,\n aggregateTopFrontmatterKeys,\n} from \"./server/utils.js\";\nimport { homedir } from \"node:os\";\nimport { join as joinPath } from \"node:path\";\nimport { cluster, expand, listBacklinks } from \"./graph/index.js\";\nimport type { ClusterOptions, ExpandDirection, ExpandOptions } from \"./graph/index.js\";\nimport type { EdgeType } from \"./db/queries/edges.js\";\nimport { queryFrontmatter } from \"./frontmatter/index.js\";\nimport { ObsidianFsDelivery } from \"./adapters/delivery/obsidian-fs/index.js\";\nimport { provisionSink, sentinelExistsAt } from \"./adapters/delivery/obsidian-fs/sentinel.js\";\nimport {\n MemorySinkRegistry,\n readListSinks,\n readMemoryStats,\n RESOURCE_URI_LIST_SINKS,\n RESOURCE_URI_LIST_BRIEFS,\n RESOURCE_URI_MEMORY_STATS,\n RESOURCE_URI_LIST_CONTRACTS,\n RESOURCE_URI_LIST_CONTRACT_VERBS,\n RESOURCE_URI_SOURCES,\n RESOURCE_URI_VAULTS,\n RESOURCE_URI_MODELS,\n RESOURCE_URI_RECENT,\n RESOURCE_URI_STATS,\n RESOURCE_URI_BACKLINKS,\n type MemorySinkConfig,\n} from \"./memory/index.js\";\nimport { RESOURCES } from \"./resource-registry.js\";\nimport { handleRecall } from \"./memory/tools/index.js\";\nimport {\n BriefStalenessDaemon,\n handleCompileBrief,\n handleGetBrief,\n readListBriefs,\n} from \"./brief/index.js\";\nimport { searchSections } from \"./assembly/search-sections.js\";\nimport { DocNotFoundError, getOutline } from \"./assembly/outline.js\";\nimport {\n ObsidianFsChangeFeed,\n SuppressionSet,\n VaultWatcher,\n} from \"./adapters/change-feed/obsidian-fs/index.js\";\nimport { catchupVault, listModels } from \"./indexer/index.js\";\nimport { TOOL_SCHEMAS, TOOLS, buildToolSchema, type ToolName } from \"./tool-registry.js\";\nimport {\n AdapterRegistry,\n formatDocId,\n parseDocId,\n parseSourceHandle,\n} from \"./adapters/registry.js\";\nimport { ObsidianFsSource } from \"./adapters/source/obsidian-fs/index.js\";\nimport {\n startContractRegistry,\n syncAutoRegistered,\n PeerMcpRegistry,\n instantiateContract,\n readListContracts,\n readListContractVerbs,\n readListSources,\n readSourceTools,\n readSourceTool,\n type SourceConfigMeta,\n type StartedContractRegistry,\n type InstantiateDeps,\n} from \"./contracts/index.js\";\nimport type { RegisteredTool } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { Handler, HandlerDeps } from \"./server/deps.js\";\nimport {\n makeVaultHandlers,\n handleListVaults,\n handleVaultStats,\n handleRecentNotes,\n} from \"./server/handlers/vault.js\";\nimport { makeNotesHandlers, handleReadNote } from \"./server/handlers/notes.js\";\nimport { makeSearchHandlers, handleSearchHybrid } from \"./server/handlers/search.js\";\nimport { makeGraphHandlers } from \"./server/handlers/graph.js\";\nimport { makeMemoryHandlers } from \"./server/handlers/memory.js\";\nimport { makeBriefHandlers } from \"./server/handlers/brief.js\";\nimport { makeAssemblyHandlers } from \"./server/handlers/assembly.js\";\nimport { makeContractsHandlers } from \"./server/handlers/contracts.js\";\n// Issue #14 / P2: version comes from package.json via a single source of\n// truth — never hardcode it here (it drifted to \"1.0.0\" for years).\nimport { VERSION } from \"./version.js\";\n\n/**\n * Bootstrap phase names — surfaced via the optional `onPhase` callback on\n * `serve()`. Test-only hook used to assert the bootstrap order invariant\n * (per Plan 02-03b: `register_memory_sinks` MUST fire before\n * `start_catchup`).\n */\nexport type BootstrapPhase =\n | \"load_config\"\n | \"open_vaults\"\n | \"register_memory_sinks\"\n | \"start_contract_registries\"\n | \"start_catchup\"\n | \"connect_transport\";\n\nexport interface ServeOptions {\n /** Test-only hook: called as each bootstrap phase begins. */\n onPhase?: (name: BootstrapPhase) => void;\n}\n\n/**\n * Convention: when no `[[memory_sinks]]` is configured AND a vault root\n * contains `<this-folder>/.memory-sink`, `discoverMemorySinks` synthesizes\n * a default sink named `default` bound to the `default-memory-v1` contract.\n *\n * IN-05 closure: surfaced as an exported constant so the magic isn't\n * buried in a string literal. Users who want a different folder name\n * configure `[[memory_sinks]]` explicitly (which short-circuits auto-\n * discovery — see `discoverMemorySinks` body line 1).\n */\nexport const MEMORY_AUTO_DISCOVERY_FOLDER = \"_memory\";\n\n/**\n * Auto-discover memory sinks per Plan 02-03b. When `config.memory_sinks` is\n * empty AND a vault contains `<MEMORY_AUTO_DISCOVERY_FOLDER>/.memory-sink`,\n * synthesize a default sink config\n * `{name: \"default\", handle: \"obsidian-fs://<vault>/<MEMORY_AUTO_DISCOVERY_FOLDER>/\",\n * contract: \"default-memory-v1\"}`. This preserves the v2 fixture's existing\n * memory docs as a \"default sink\" without requiring config edits.\n *\n * Returns the explicit configs unchanged when `configured` is non-empty.\n */\nexport async function discoverMemorySinks(\n configured: readonly MemorySinkConfig[],\n vaults: readonly { name: string; path: string }[],\n): Promise<MemorySinkConfig[]> {\n if (configured.length > 0) {\n return [...configured];\n }\n const discovered: MemorySinkConfig[] = [];\n for (const v of vaults) {\n if (await sentinelExistsAt(v.path, MEMORY_AUTO_DISCOVERY_FOLDER)) {\n discovered.push({\n name: \"default\",\n handle: `obsidian-fs://${v.name}/${MEMORY_AUTO_DISCOVERY_FOLDER}/`,\n contract: \"default-memory-v1\",\n });\n }\n }\n return discovered;\n}\n\n/**\n * Construct and populate a `MemorySinkRegistry` per Plan 02-03b. Wraps\n * `discoverMemorySinks` + `registry.registerMemorySinks` with the\n * production provisioner (calls `provisionSink` from obsidian-fs/sentinel).\n *\n * Exported for use by `serve()` and by `src/server.test.ts` (MEM-11\n * integration + bootstrap-order assertion).\n */\nexport async function setupMemorySinks(\n config: {\n memory_sinks: MemorySinkConfig[];\n memory?: { default_sink?: string };\n },\n manager: VaultManager,\n): Promise<MemorySinkRegistry> {\n const registry = new MemorySinkRegistry();\n const vaults = manager.list().map((v) => ({\n name: v.config.name,\n path: v.config.path,\n }));\n const sinksConfig = await discoverMemorySinks(config.memory_sinks, vaults);\n await registry.registerMemorySinks(sinksConfig, {\n resolveVaultAbsolutePath: (name) => manager.require(name).config.path,\n ...(config.memory?.default_sink !== undefined\n ? { defaultSinkName: config.memory.default_sink }\n : {}),\n provisioner: async (sink, vaultAbs) => provisionSink(sink, vaultAbs, { version: VERSION }),\n });\n return registry;\n}\n\n// ─── Server bootstrap ────────────────────────────────────────────────────────\n\nexport async function serve(options: ServeOptions = {}): Promise<void> {\n const onPhase = options.onPhase ?? ((): void => undefined);\n\n onPhase(\"load_config\");\n const config = await loadConfig();\n\n onPhase(\"open_vaults\");\n const manager = new VaultManager();\n await manager.loadAll(config.vaults);\n\n // Plan 02-03b — wire the MemorySinkRegistry BEFORE catchup so any\n // sentinel provisioning completes before the catch-up walk touches the\n // _memory/ folder. Registration failures are fatal per ADR-004\n // §Provisioning fail-fast.\n onPhase(\"register_memory_sinks\");\n const memorySinkRegistry = await setupMemorySinks(config, manager);\n\n // ─── Adapter registry (Phase 1, plans 01-03 + 01-04) ──────────────────────\n //\n // One ObsidianFsSource + one ObsidianFsDelivery per vault; registered under\n // the canonical handle `obsidian-fs://<vault-name>`. The read_note handler\n // resolves the source; the write_note / update_frontmatter / delete_note\n // handlers resolve the delivery (plan 01-04 task 06).\n //\n // D-02 (client_info capture): the delivery takes a LAZY clientId getter\n // closure that reads `server.getClientVersion()?.name` on every call. This\n // lets us construct the registry BEFORE `server.connect()` while still\n // surfacing the post-handshake client_info into the audit log.\n // Pre-handshake (or if the client never sent clientInfo per the optional\n // spec field), the fallback is \"unknown\" — explicitly NOT a hardcoded\n // client name (the C-1 leak removed in plan 01-04). RESEARCH Pitfall 4.\n const adapterRegistry = new AdapterRegistry();\n // `serverRef` is assigned below; the closure captures the variable so the\n // delivery can see the post-init clientInfo without a re-registration.\n let serverRef: McpServer | undefined;\n // McpServer wraps an internal low-level `Server`; `getClientVersion()` is\n // on the inner instance.\n const getClientId = (): string => serverRef?.server.getClientVersion()?.name ?? \"unknown\";\n // One SuppressionSet shared by all watchers + the per-vault change-feed.\n // Paths are vault-relative; the chance of a collision across vaults is\n // negligible and a false positive just means one event is dropped —\n // harmless. (Pitfall 6 cross-adapter contract: ObsidianFsDelivery marks\n // a path on this set BEFORE atomicWriteFile; the change-feed +\n // VaultWatcher consume it on the corresponding chokidar event.)\n const suppression = new SuppressionSet({ ttlMs: 2000 });\n const changeFeeds = new Map<string, ObsidianFsChangeFeed>();\n for (const vault of manager.list()) {\n const source = new ObsidianFsSource(vault.config);\n adapterRegistry.registerSource(source.handle, source);\n\n const delivery = new ObsidianFsDelivery(vault, getClientId, memorySinkRegistry);\n adapterRegistry.registerDelivery(delivery.handle, delivery);\n\n // Plan 01-05 task 02: register a ChangeFeed per vault. Coexists with\n // the v1 VaultWatcher (driven from `startCatchupAndWatchers` below)\n // so existing live-indexing behavior is unchanged; a future plan will\n // retire VaultWatcher in favor of an indexer subscribing through this\n // ChangeFeed seam.\n const changeFeed = new ObsidianFsChangeFeed({\n vault,\n suppression,\n log: (m) => process.stderr.write(`[change-feed:${vault.config.name}] ${m}\\n`),\n });\n adapterRegistry.registerChangeFeed(changeFeed.handle, changeFeed);\n changeFeeds.set(vault.config.name, changeFeed);\n }\n\n const ollama = new OllamaClient({\n endpoint: config.server.ollama_endpoint,\n });\n\n const defaultModel = config.server.default_embedding_model ?? \"qwen3-embedding:0.6b\";\n\n // Default search scope. When VAULT_MEMORY_ACTIVE_VAULT is set, search_*\n // tools default to that single vault unless the caller passes an explicit\n // `vaults` array. This makes the common case (\"I'm working in this vault,\n // search this vault\") the default — cross-vault search is opt-in via an\n // explicit `vaults: [\"a\", \"b\"]` filter. If the env var is unset, the\n // legacy behaviour (search all configured vaults) applies.\n const activeVault = process.env.VAULT_MEMORY_ACTIVE_VAULT?.trim() || undefined;\n\n // Optional cross-encoder reranker (Phase 7d). Constructed once;\n // search_hybrid will pass it through only when the caller asks for it.\n // Phase 8: backend selection. Default to \"onnx\" when reranker_model is\n // set but no backend specified — the ONNX cross-encoder is the\n // recommended path; the Ollama L2-norm proxy is retained for\n // backward-compat only.\n const rerankerBackend =\n config.server.reranker_backend ?? (config.server.reranker_model ? \"onnx\" : undefined);\n const reranker: Reranker | undefined = config.server.reranker_model\n ? rerankerBackend === \"ollama\"\n ? new OllamaReranker({ ollama, model: config.server.reranker_model })\n : new OnnxReranker({\n modelDir:\n config.server.reranker_model_dir ??\n joinPath(homedir(), \".vault-memory\", \"models\", \"bge-reranker-v2-m3\"),\n })\n : undefined;\n\n // ─── File watchers (Phase 4) ──────────────────────────────────────────────\n //\n // The shared `suppression` set (hoisted above with the adapter-registry\n // construction so the per-vault ChangeFeed can share it with the v1\n // VaultWatcher) is also wired into each VaultWatcher below.\n const watchers = new Map<string, VaultWatcher>();\n\n // ─── Brief staleness daemons (Phase 5 / BRF-05..BRF-08) ────────────────────\n //\n // One daemon per vault, started after MemorySinkRegistry + catchup. Each\n // daemon subscribes to the same per-vault `ObsidianFsChangeFeed` the\n // VaultWatcher uses; ChangeFeed fan-out is documented (snapshot-then-\n // iterate per change-feed.ts:218), so multiple handlers per feed are\n // safe by contract. Lock contention is a NORMAL multi-MCP-client\n // outcome: the second server logs a structured WARN and serves\n // search/read/write identically.\n const briefDaemons = new Map<string, BriefStalenessDaemon>();\n\n // Codex MEDIUM-3: catch-up reconciliation can take seconds on large vaults\n // (re-embedding modified notes). We defer it until after MCP `connect()` so\n // the tool list responds immediately and the LLM doesn't time out waiting\n // for the handshake. Watchers start per-vault as each catch-up finishes.\n const startCatchupAndWatchers = async (): Promise<void> => {\n for (const vault of manager.list()) {\n // ADR-008: ContextFit vaults have no embedding model by design, but still\n // need catchup + a watcher (they build the SQLite layer + refresh the KB).\n const isContextFit = vault.config.backend === \"contextfit\";\n if (!isContextFit && !vault.config.embedding_model && !vault.db.models.getActive()) continue;\n const modelName = vault.config.embedding_model ?? defaultModel;\n\n try {\n const result = await catchupVault({\n vault,\n embeddingModel: modelName,\n ...(isContextFit ? {} : { ollama }),\n log: (m) => process.stderr.write(`[catchup:${vault.config.name}] ${m}\\n`),\n });\n if (result.reindexed > 0 || result.removed > 0) {\n process.stderr.write(\n `[catchup:${vault.config.name}] scanned ${result.scanned}, ` +\n `reindexed ${result.reindexed}, removed ${result.removed} ` +\n `(${result.durationMs}ms)\\n`,\n );\n }\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(\n `[catchup:${vault.config.name}] failed: ${message} (watcher will still start)\\n`,\n );\n }\n\n const watcher = new VaultWatcher({\n vault,\n embeddingModel: modelName,\n secondaryEmbeddingModel: vault.config.secondary_embedding_model,\n ollama,\n suppression,\n });\n await watcher.start();\n watchers.set(vault.config.name, watcher);\n\n // ── Phase 5 / D-07/D-08: brief staleness daemon ──────────────────\n //\n // Subscribes to the same ObsidianFsChangeFeed as the VaultWatcher.\n // Lock contention is logged as structured WARN to stderr; the\n // server continues to serve search/read/write — only the daemon\n // subscription is gated (D-08 multi-MCP-client norm).\n const feed = changeFeeds.get(vault.config.name);\n if (feed) {\n const daemon = new BriefStalenessDaemon();\n try {\n await daemon.start(vault, feed, {\n memorySinkRegistry,\n deliveryAdapterFor: (vaultName) =>\n adapterRegistry.resolveDelivery(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n log: (m) => process.stderr.write(`[brief-daemon:${vault.config.name}] ${m}\\n`),\n });\n briefDaemons.set(vault.config.name, daemon);\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[brief-daemon:${vault.config.name}] start failed: ${message}\\n`);\n }\n }\n }\n };\n\n const shutdown = async (): Promise<void> => {\n // Phase 6 (Plan 06-02): dispose ContractRegistry feed subscriptions\n // BEFORE the brief daemons + watchers so no contract reload races\n // with mid-shutdown disposal. The dispose() call is synchronous and\n // unsubscribes the per-vault ChangeFeed handler.\n for (const state of contractRegistries.values()) {\n try {\n state.started.dispose();\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[contract-registry] dispose error: ${message}\\n`);\n }\n }\n // Plan 06-03 (Pitfall F4) — kill peer-MCP child processes BEFORE\n // brief daemons + watchers + change-feeds drain. `shutdown()`\n // disposes each `PeerMcpClient`, which invokes `transport.close()`\n // → `child.kill()`. Idempotent; safe to call even when no clients\n // were configured.\n try {\n await peerMcpRegistry.shutdown();\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[peer-mcp-registry] shutdown error: ${message}\\n`);\n }\n // Phase 5 (Plan 05-03): dispose brief staleness daemons FIRST so\n // no in-flight ChangeEvents land mid-shutdown. Then drain + stop\n // watchers; finally close change-feeds (the underlying chokidar\n // watcher). Lock release happens inside daemon.shutdown() — a\n // crashed shutdown that fails here leaves the lock for the\n // PID-liveness stale-detection on next boot.\n for (const d of briefDaemons.values()) {\n try {\n await d.shutdown();\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[brief-daemon] shutdown error: ${message}\\n`);\n }\n }\n for (const w of watchers.values()) {\n await w.drain();\n await w.stop();\n }\n for (const cf of changeFeeds.values()) {\n await cf.close();\n }\n };\n process.on(\"SIGINT\", () => {\n void shutdown().finally(() => process.exit(0));\n });\n process.on(\"SIGTERM\", () => {\n void shutdown().finally(() => process.exit(0));\n });\n\n // Stdin-EOF watchdog. When stdio-MCP parents (Claude, Obsidian plugin)\n // die, they don't always succeed at SIGTERM-ing this child cleanly —\n // the parent may have been killed itself (force-quit Obsidian), the\n // transport.close() may not propagate, or the SIGTERM may race with\n // sustained file-IO and get queued. In all those cases stdin closes,\n // emitting 'end' (FIN received) or 'close' (FD closed). We exit then.\n //\n // Without this watchdog, EVERY plugin reload accumulates a zombie\n // `vault-memory serve` process holding ~22k chokidar FDs. After 10–15\n // reloads the system runs out of file descriptors (`kern.maxfiles`)\n // and Obsidian itself fails to scandir its vault with ENFILE.\n // Discovered the hard way 2026-05-20.\n //\n // Brief grace period: the MCP SDK reads stdin in object-mode chunks;\n // a final `tools/call` may still be processing when stdin closes. The\n // 500 ms timer lets in-flight work complete before exit; shutdown()\n // runs through the watcher/changeFeed drain just like the signal path.\n let stdinClosing = false;\n const onStdinClose = (reason: \"end\" | \"close\") => {\n if (stdinClosing) return;\n stdinClosing = true;\n // eslint-disable-next-line no-console -- direct stderr is intentional;\n // logger may already be draining as part of shutdown.\n process.stderr.write(`[vault-memory] stdin ${reason} — parent process gone; shutting down.\\n`);\n setTimeout(() => {\n void shutdown().finally(() => process.exit(0));\n }, 500);\n };\n process.stdin.on(\"end\", () => onStdinClose(\"end\"));\n process.stdin.on(\"close\", () => onStdinClose(\"close\"));\n\n const server = new McpServer(\n { name: \"vault-memory\", version: VERSION },\n // Plan 02-06 (MEM-09): advertise `resources` capability so MCP clients\n // call `resources/list` + `resources/read` on bootstrap. Polled-only —\n // no `subscribe` / `listChanged` flags asserted.\n { capabilities: { tools: {}, resources: {} } },\n );\n // Make the McpServer visible to the lazy clientId closure (see bootstrap).\n // After `server.connect(transport)` and the MCP initialize handshake,\n // `server.server.getClientVersion()` returns the client's `Implementation`\n // object — the `name` field is what we use for audit-log attribution.\n serverRef = server;\n\n // ─── Phase 6 (Plan 06-02) — per-vault ContractRegistry state ─────────────\n //\n // The map is created BEFORE the TOOLS loop so the `register_contracts_as_tools`\n // handler can capture it via closure. The registries themselves are\n // populated by `startContractRegistry({...})` AFTER all v1+v2 tools are\n // registered (so `syncAutoRegistered` is invoking `server.registerTool`\n // on an already-initialized server instance — RegisteredTool handles\n // are preserved per-vault for later remove() calls).\n //\n // The contractRegistries map carries one StartedContractRegistry per\n // vault plus a mutable RegisteredTool handle map for the dynamic\n // `vm_*` auto-registered tools. The Plan 06-02 stub `instantiateHandler`\n // is replaced (Plan 06-03 Task 5) by a closure over the per-vault\n // `buildInstantiateDeps` helper below.\n const contractRegistries = new Map<\n string,\n {\n started: StartedContractRegistry;\n registered: Map<string, RegisteredTool>;\n }\n >();\n\n // ─── Phase 6 (Plan 06-03) — peer-MCP registry + buildInstantiateDeps ─────\n //\n // ONE PeerMcpRegistry shared across all vaults (peer-MCP servers in\n // `[contracts.mcp_clients]` are vault-independent — a `mcp://gh/list_issues`\n // verb invocation does the same thing regardless of which vault's\n // contract triggered it). The registry boots BEFORE the per-vault\n // contract registries so each `buildInstantiateDeps(vault)` closure\n // captures the same registry instance.\n //\n // Failures during `peerMcpRegistry.start(...)` are NON-FATAL (CONTEXT.md\n // Claude's Discretion + PeerMcpRegistry semantics): individual clients\n // mark themselves unavailable with a stderr WARN. The server keeps booting.\n //\n // SIGTERM/SIGINT cleanup: the existing shutdown() at line ~391 already\n // runs on those signals; we wire `peerMcpRegistry.shutdown()` into it\n // below (Pitfall F4 — kill child processes on parent exit).\n const peerMcpRegistry = new PeerMcpRegistry();\n\n /**\n * Build per-vault `InstantiateDeps` for `instantiate_contract`. Each\n * baseline-verb thunk re-uses the existing Phase 1-5 handler functions;\n * arguments are passed through verbatim post-template-resolution (the\n * contract author is responsible for matching each verb's signature\n * per the JSDoc block in `src/contracts/verbs/index.ts`).\n */\n const buildInstantiateDeps = (vault: Vault): InstantiateDeps => {\n const state = contractRegistries.get(vault.config.name);\n if (state === undefined) {\n throw new Error(`ContractRegistry not initialized for vault \"${vault.config.name}\"`);\n }\n return {\n vault,\n registry: state.started.registry,\n memorySinks: memorySinkRegistry,\n delivery: adapterRegistry.resolveDelivery(\n parseSourceHandle(`obsidian-fs://${vault.config.name}`),\n ),\n contractAudit: vault.db.contractAudit,\n configDefaults: config.contracts.defaults,\n stepTimeoutSeconds: config.contracts.step_timeout_seconds,\n peerMcpRegistry,\n // The baseline verbs use the same args the contract YAML supplied\n // (post-template-resolution). Each thunk forwards to the existing\n // Phase 1-5 handler in the v1+v2 toolset. Contract authors match\n // each verb's signature per RESEARCH §A9.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n hybridSearch: async (args: any) => {\n const p = args as {\n query: string;\n vaults?: string[];\n top_k?: number;\n rrf_k?: number;\n exclude_paths?: string[];\n recency_weight?: number;\n authority_weight?: number;\n half_life_days?: number;\n include_superseded?: boolean;\n };\n return handleSearchHybrid(\n manager,\n ollama,\n defaultModel,\n activeVault,\n p.query,\n p.vaults ?? [vault.config.name],\n p.top_k ?? 10,\n p.rrf_k ?? 60,\n p.exclude_paths,\n reranker,\n p.recency_weight ?? 0,\n p.authority_weight ?? 0,\n p.half_life_days ?? 30,\n p.include_superseded ?? false,\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleExpand: async (args: any) => {\n const p = args as {\n seed_doc_ids: string[];\n hops: 1 | 2;\n direction?: ExpandDirection;\n edge_types?: EdgeType[];\n filter_properties?: Record<string, unknown>;\n include_superseded?: boolean;\n };\n const seeds = p.seed_doc_ids.map((s) => parseDocId(s));\n return expand(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n {\n seed_doc_ids: seeds,\n hops: p.hops,\n direction: p.direction ?? \"both\",\n ...(p.edge_types !== undefined ? { edge_types: p.edge_types } : {}),\n ...(p.filter_properties !== undefined\n ? { filter_properties: p.filter_properties }\n : {}),\n include_superseded: p.include_superseded ?? false,\n } satisfies ExpandOptions,\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleCluster: async (args: any) => {\n const p = args as {\n query?: string;\n seed_doc_ids?: string[];\n vault?: string;\n method?: \"edge-community\";\n query_top_k?: number;\n force?: boolean;\n };\n let opts: ClusterOptions;\n if (p.query !== undefined) {\n opts = {\n query: p.query,\n method: \"edge-community\",\n ...(p.vault !== undefined ? { vault: p.vault } : { vault: vault.config.name }),\n ...(p.query_top_k !== undefined ? { query_top_k: p.query_top_k } : {}),\n ...(p.force !== undefined ? { force: p.force } : {}),\n };\n } else {\n const seeds = (p.seed_doc_ids ?? []).map((s) => parseDocId(s));\n opts = {\n seed_doc_ids: seeds,\n method: \"edge-community\",\n ...(p.force !== undefined ? { force: p.force } : {}),\n };\n }\n return cluster(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n hybridSearch: async (v, query, limit) =>\n hybridSearch({\n query,\n embeddingModel: defaultModel,\n ollama,\n vaults: [v],\n topK: limit,\n includeBreakdown: false,\n ...(reranker ? { reranker } : {}),\n displayUrlFor: (vaultName, notePath) =>\n displayUrl(adapterRegistry, vaultName, notePath),\n }),\n },\n opts,\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleRecall: async (args: any) => {\n const p = args as {\n query: string;\n min_confidence?: \"direct\" | \"inferred\" | \"uncertain\";\n types?: string[];\n max_age_days?: number;\n sink?: string;\n limit?: number;\n vaults?: string[];\n };\n const packets = await handleRecall(\n {\n memorySinkRegistry,\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n searchHybrid: async (input) =>\n hybridSearch({\n query: input.query,\n embeddingModel: defaultModel,\n ollama,\n vaults: input.vaults,\n topK: input.topK,\n rrfK: 60,\n includeBreakdown: false,\n }),\n },\n { ...p, vaults: p.vaults ?? [vault.config.name] },\n );\n return { packets, count: packets.length };\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleCompileBrief: async (args: any) => {\n const p = args as {\n vault?: string;\n target: string;\n source_doc_ids: string[];\n purpose: string;\n max_tokens?: number;\n prepared_text?: string;\n sink?: string;\n };\n return handleCompileBrief(\n {\n memorySinkRegistry,\n manager,\n deliveryAdapterFor: (vaultName) =>\n adapterRegistry.resolveDelivery(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n server,\n ollama,\n briefConfig: config.brief,\n },\n { ...p, vault: p.vault ?? vault.config.name },\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleGetBrief: async (args: any) => {\n const p = args as {\n vault?: string;\n target: string;\n max_age_days?: number;\n allow_stale?: boolean;\n };\n return handleGetBrief(\n {\n memorySinkRegistry,\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n { ...p, vault: p.vault ?? vault.config.name },\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleQueryFrontmatter: async (args: any) => {\n const p = args as {\n vault?: string;\n where: Record<string, unknown>;\n limit?: number;\n };\n const v = p.vault ? manager.require(p.vault) : vault;\n return queryFrontmatter(v, {\n where: p.where as Record<string, never>,\n limit: p.limit ?? 100,\n });\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleListBacklinks: async (args: any) => {\n const p = args as { vault?: string; path: string };\n const v = p.vault ? manager.require(p.vault) : vault;\n return { backlinks: listBacklinks(v, p.path) };\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleGetOutline: async (args: any) => {\n const p = args as { doc_id: string; vaults?: string[] };\n return getOutline(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleSearchSections: async (args: any) => {\n const p = args as {\n query: string;\n limit?: number;\n vaults?: string[];\n recency_weight?: number;\n authority_weight?: number;\n include_superseded?: boolean;\n };\n // Default scope: caller's vault (the one the contract is bound to).\n const targetVaults: Vault[] = p.vaults\n ? p.vaults.map((name) => manager.require(name))\n : [vault];\n const results = await searchSections(\n {\n searchHybrid: async (input) =>\n hybridSearch({\n query: input.query,\n embeddingModel: defaultModel,\n ollama,\n vaults: input.vaults\n ? input.vaults.map((name) => manager.require(name))\n : targetVaults,\n topK: input.topK,\n rrfK: 60,\n includeBreakdown: false,\n }),\n sectionForHit: (vaultName, notePath, chunkIdx) => {\n let v: Vault;\n try {\n v = manager.require(vaultName);\n } catch {\n return null;\n }\n const note = v.db.notes.getByPath(notePath);\n if (!note) return null;\n const chunks = v.db.chunks.getByNote(note.id);\n const chunk = chunks.find((c) => c.idx === chunkIdx);\n if (!chunk) return null;\n const section = v.db.sections.findContainingChunk(note.id, chunk.id);\n if (!section) return null;\n let headingPath: string[];\n try {\n const parsed = JSON.parse(section.heading_path);\n headingPath = Array.isArray(parsed) ? (parsed as string[]) : [];\n } catch {\n headingPath = [];\n }\n return {\n noteId: note.id,\n anchor: section.anchor,\n headingPath,\n chunkIdFirst: section.chunk_id_first ?? Number.MAX_SAFE_INTEGER,\n };\n },\n readDocument: async (vaultName, notePath) => {\n const docId = formatDocId(\"obsidian-fs\", vaultName, notePath);\n return adapterRegistry\n .resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`))\n .readDocument(docId);\n },\n displayUrlFor: (docId, vaultName) => {\n const source = adapterRegistry.resolveSource(\n parseSourceHandle(`obsidian-fs://${vaultName}`),\n );\n return source.formatDisplayUrl?.(docId) ?? docId;\n },\n },\n {\n query: p.query,\n limit: p.limit ?? 10,\n ...(p.vaults !== undefined ? { vaults: p.vaults } : {}),\n ...(p.recency_weight !== undefined ? { recency_weight: p.recency_weight } : {}),\n ...(p.authority_weight !== undefined ? { authority_weight: p.authority_weight } : {}),\n ...(p.include_superseded !== undefined\n ? { include_superseded: p.include_superseded }\n : {}),\n },\n );\n return { results, count: results.length };\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleReadNote: async (args: any) => {\n const p = args as { vault?: string; path: string };\n return handleReadNote(adapterRegistry, p.vault ?? vault.config.name, p.path);\n },\n };\n };\n\n /**\n * Resolve the target vault for `describe_contract` / `instantiate_contract`.\n * Single-vault setups: use the only configured vault. Multi-vault setups:\n * the caller MUST pass `vault`; otherwise return the WARNING-6\n * `ambiguous_vault` envelope (12th reason in InstantiateError).\n */\n const resolveContractVault = (\n vaultArg: string | undefined,\n ):\n | { ok: true; vault: Vault }\n | { ok: false; reason: \"ambiguous_vault\"; available_vaults: string[] }\n | { ok: false; reason: \"unknown_vault\"; vault: string } => {\n const list = manager.list();\n if (vaultArg !== undefined) {\n const v = list.find((x) => x.config.name === vaultArg);\n if (v === undefined) {\n return { ok: false, reason: \"unknown_vault\", vault: vaultArg };\n }\n return { ok: true, vault: v };\n }\n if (list.length === 1) {\n const only = list[0];\n if (only === undefined) {\n return { ok: false, reason: \"ambiguous_vault\", available_vaults: [] };\n }\n return { ok: true, vault: only };\n }\n return {\n ok: false,\n reason: \"ambiguous_vault\",\n available_vaults: list.map((v) => v.config.name),\n };\n };\n\n /**\n * Bound to auto-registered `vm_*` tools. Each auto-registered tool's\n * callback (see `syncAutoRegistered` in `src/contracts/auto-register.ts`)\n * passes the contract name + the caller args through this closure. We\n * route to `instantiateContract` using the per-vault deps captured at\n * register time — single-vault deployments are the v2.0.0 norm; multi-\n * vault setups will surface `ambiguous_vault` until per-vault\n * tool-prefixing lands in a future slice.\n */\n const instantiateHandler = async (name: string, args: unknown): Promise<unknown> => {\n const resolved = resolveContractVault(undefined);\n if (!resolved.ok) return resolved;\n const inputs = ((args as { inputs?: Record<string, unknown> })?.inputs ?? {}) as Record<\n string,\n unknown\n >;\n return instantiateContract(buildInstantiateDeps(resolved.vault), {\n name,\n inputs,\n });\n };\n\n // ─── registerTool × 23 (SDK 1.29, plan 01-05 task 07) ─────────────────────\n //\n // Each handler receives ALREADY-VALIDATED args (the SDK runs the Zod\n // schema before invoking us). We layer a try/catch to convert thrown\n // errors into MCP error responses, preserving the v1 error shape.\n\n // Bundle the serve()-scope closure state once; per-domain handler\n // factories (server/handlers/*.ts) close over `deps.*` instead of the\n // bare serve() locals.\n const deps: HandlerDeps = {\n manager,\n ollama,\n defaultModel,\n reranker,\n adapterRegistry,\n suppression,\n memorySinkRegistry,\n server,\n contractRegistries,\n peerMcpRegistry,\n config,\n activeVault,\n };\n\n // Assembled from per-domain factories (spread) plus the not-yet-extracted\n // inline entries. Typed as Partial during assembly; completeness over the\n // ToolName union is re-asserted by `assertCompleteHandlers` below.\n const handlers: Partial<Record<ToolName, Handler>> = {\n ...makeVaultHandlers(deps),\n ...makeNotesHandlers(deps),\n ...makeSearchHandlers(deps),\n ...makeGraphHandlers(deps),\n ...makeMemoryHandlers(deps),\n\n ...makeBriefHandlers(deps),\n\n ...makeAssemblyHandlers(deps),\n ...makeContractsHandlers(deps, {\n resolveContractVault,\n instantiateHandler,\n buildInstantiateDeps,\n }),\n };\n\n // Completeness gate: every ToolName must have a handler. The per-domain\n // factories return `Partial<Record<ToolName, Handler>>`, so we re-assert\n // the union is fully covered (a missing key would be a wiring bug, caught\n // here at boot rather than on first tool call).\n for (const tool of TOOLS) {\n const name = tool.name as ToolName;\n if (handlers[name] === undefined) {\n throw new Error(`Internal error: no handler registered for tool \"${name}\".`);\n }\n }\n const completeHandlers = handlers as Record<ToolName, Handler>;\n\n // Wire each TOOLS entry through registerTool. The SDK runs the Zod\n // schema (built via buildToolSchema from tool-registry.ts) against the\n // incoming arguments BEFORE invoking our handler — so each handler\n // receives args matching the declared shape. Thrown errors are caught\n // and converted to MCP error responses (isError:true) per the v1\n // error-wrapping contract.\n for (const tool of TOOLS) {\n const name = tool.name as ToolName;\n const handler = completeHandlers[name];\n const schema = TOOL_SCHEMAS[name];\n // suggest_frontmatter layers an extra refinement on top of its raw\n // shape; the SDK only accepts a raw shape here, so we register the\n // shape directly and let the handler re-validate with the refined\n // schema (`buildToolSchema`) for the cross-field check. The same\n // pattern applies to `cluster` (D-15a mutual exclusion between\n // `query` and `seed_doc_ids`).\n const needsRefinementCheck = name === \"suggest_frontmatter\" || name === \"cluster\";\n server.registerTool(\n name,\n { description: tool.description, inputSchema: schema },\n async (args: unknown) => {\n try {\n let validated: unknown = args;\n if (needsRefinementCheck) {\n validated = buildToolSchema(name).parse(args);\n }\n const data = await handler(validated);\n return ok(data);\n } catch (err) {\n // Phase 3 ASM-02: a `DocNotFoundError` carries a structured\n // payload (`{error: \"doc_not_found\", doc_id}`) per the plan's\n // error contract. Other tools that resolve documents by id\n // (forthcoming get_bundle, dossier) will throw the same shape.\n if (err instanceof DocNotFoundError) {\n return errorResponseJson({ error: \"doc_not_found\", doc_id: err.doc_id });\n }\n const message = errorMessage(err);\n return errorResponse(message);\n }\n },\n );\n }\n\n // ─── MCP Resources (Plan 02-06 / MEM-09) ─────────────────────────────────\n //\n // Polled-only — no `notifyResourceUpdated` integration in v2.0.0\n // (CONTEXT D-Q4). URIs are FLAT per RESEARCH §Q4: one resource per\n // capability, not per sink. The registry is already populated above\n // (via `setupMemorySinks(...)`); the read callbacks just project from\n // it (list_sinks) or query the per-vault SQLite DB (memory_stats).\n server.registerResource(\n \"memory-sinks\",\n RESOURCE_URI_LIST_SINKS,\n {\n title: \"Memory sinks\",\n description:\n \"Configured + auto-discovered MemorySinks (name, handle, vault, contract, default). \" +\n \"Read to discover where memory documents (record_observation, supersede) land.\",\n mimeType: \"application/json\",\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(readListSinks(memorySinkRegistry), null, 2),\n },\n ],\n }),\n );\n server.registerResource(\n \"memory-stats\",\n RESOURCE_URI_MEMORY_STATS,\n {\n title: \"Memory sink stats\",\n description:\n \"Per-sink document counts, by_type / by_status breakdowns, and last memory-write timestamp. \" +\n \"Polled — re-read to refresh.\",\n mimeType: \"application/json\",\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(readMemoryStats(memorySinkRegistry, manager), null, 2),\n },\n ],\n }),\n );\n\n // ─── Plan 05-04 (BRF-09) — list_briefs MCP Resource ───────────────────────\n //\n // Discovery surface for compiled briefs. Filtered by optional `?target=`\n // query parameter (substring match on `properties.target`). The read\n // handler is a pure function over `MemorySinkRegistry + VaultManager +\n // SourceConnector` — see `src/brief/resources.ts`.\n server.registerResource(\n \"briefs\",\n RESOURCE_URI_LIST_BRIEFS,\n {\n title: \"Compiled briefs\",\n description:\n \"Discovery of compiled briefs by target. Supports optional `?target=<pattern>` \" +\n \"substring filter on `properties.target`. Includes `active`, `stale`, and \" +\n \"`superseded` entries so callers can build their own filter / inspect the \" +\n \"supersede chain. BRF-09.\",\n mimeType: \"application/json\",\n },\n async (uri) => {\n const target = uri.searchParams.get(\"target\") ?? undefined;\n const payload = await readListBriefs(\n {\n registry: memorySinkRegistry,\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n target !== undefined ? { target } : {},\n );\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n },\n ],\n };\n },\n );\n\n // ─── Phase 6 (Plan 06-04) — contract MCP Resources ───────────────────────\n //\n // Two Resources expose contract metadata for discovery (CON-04) and\n // verb-usage promotion signals (D-A2b). Both use the SDK 1.29\n // `ResourceTemplate` pattern with a `{vault}` URI variable so each\n // per-vault contract registry surfaces as its own readable URI.\n //\n // Resources do NOT count toward the REL-08 tool budget per Phase 5\n // BRF-09 precedent. They are listed under `resources/list` in the\n // MCP protocol, not `tools/list`.\n server.registerResource(\n \"contracts\",\n new ResourceTemplate(`${RESOURCE_URI_LIST_CONTRACTS}/{vault}`, {\n list: undefined,\n }),\n {\n title: \"Task contracts\",\n description:\n \"Discovery of task contracts available in a vault (CON-04). Each entry \" +\n \"carries name, description, source/sink counts, and write_back boolean. \" +\n \"Optional `?source=<prefix>` filters to contracts declaring a source \" +\n \"whose handle starts with the given prefix.\",\n mimeType: \"application/json\",\n },\n async (uri, variables) => {\n const vault = String(variables.vault ?? \"\");\n const state = contractRegistries.get(vault);\n if (state === undefined) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: `unknown vault: ${vault}` }),\n },\n ],\n };\n }\n const source = uri.searchParams.get(\"source\") ?? undefined;\n const payload = readListContracts(\n { registry: state.started.registry, vaultName: vault },\n source !== undefined ? { source } : {},\n );\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n },\n ],\n };\n },\n );\n\n server.registerResource(\n \"contract-verbs\",\n new ResourceTemplate(`${RESOURCE_URI_LIST_CONTRACT_VERBS}/{vault}`, {\n list: undefined,\n }),\n {\n title: \"Contract verbs\",\n description:\n \"List baseline assembly verbs + custom (mcp://) verbs in use, with \" +\n \"invocation_count + last_seen aggregated from contract_audit (D-A2b). \" +\n \"Baseline verbs are constant per ADR-006 §Decision 3.\",\n mimeType: \"application/json\",\n },\n async (uri, variables) => {\n const vault = String(variables.vault ?? \"\");\n // Look up the per-vault contractAudit directly through the manager\n // rather than via the contractRegistries map — the audit table is\n // populated regardless of whether the registry boot scan succeeded.\n const vaultRef = manager.list().find((vt) => vt.config.name === vault);\n if (vaultRef === undefined) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: `unknown vault: ${vault}` }),\n },\n ],\n };\n }\n const payload = readListContractVerbs({\n contractAudit: vaultRef.db.contractAudit,\n vaultName: vault,\n });\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n },\n ],\n };\n },\n );\n\n // ─── SOURCES-REGISTRY.md §5 (Stage 2) — peer-MCP source discovery ────────\n //\n // Three vault-independent resources over the live PeerMcpRegistry. The\n // command/args come from `config.contracts.mcp_clients`; runtime-added\n // sources (set_mcp_client without restart) appear with empty meta until\n // the next boot, which is acceptable for discovery.\n const sourceConfigMeta = (): Record<string, SourceConfigMeta> => {\n const out: Record<string, SourceConfigMeta> = {};\n for (const [name, cfg] of Object.entries(config.contracts.mcp_clients)) {\n out[name] = { command: cfg.command, args: cfg.args ?? [] };\n }\n return out;\n };\n\n server.registerResource(\n \"sources\",\n RESOURCE_URI_SOURCES,\n {\n title: \"Peer MCP sources\",\n description:\n \"List peer MCP servers vault-memory connects to, with per-source \" +\n \"status (connected/unavailable/unreachable), tool_count, and \" +\n \"last_refreshed. vault-memory itself is not included. SOURCES-REGISTRY §5.1.\",\n mimeType: \"application/json\",\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(readListSources(peerMcpRegistry, sourceConfigMeta()), null, 2),\n },\n ],\n }),\n );\n\n server.registerResource(\n \"source-tools\",\n new ResourceTemplate(`${RESOURCE_URI_SOURCES}/{name}/tools`, {\n list: undefined,\n }),\n {\n title: \"Peer MCP source tools\",\n description:\n \"List the cached tools/list for one peer MCP source. Empty when the \" +\n \"source is not connected. SOURCES-REGISTRY §5.2.\",\n mimeType: \"application/json\",\n },\n async (uri, variables) => {\n const name = String(variables.name ?? \"\");\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(readSourceTools(peerMcpRegistry, name), null, 2),\n },\n ],\n };\n },\n );\n\n server.registerResource(\n \"source-tool\",\n new ResourceTemplate(`${RESOURCE_URI_SOURCES}/{name}/tools/{tool}`, {\n list: undefined,\n }),\n {\n title: \"Peer MCP source tool\",\n description:\n \"Read a single tool's schema from one peer MCP source, inlined from \" +\n \"the cached tools/list (no extra peer call). SOURCES-REGISTRY §5.3.\",\n mimeType: \"application/json\",\n },\n async (uri, variables) => {\n const name = String(variables.name ?? \"\");\n const tool = String(variables.tool ?? \"\");\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(readSourceTool(peerMcpRegistry, name, tool), null, 2),\n },\n ],\n };\n },\n );\n\n // ─── Phase 8 (Plan 08-05 / REL-08) — promote 5 list-style v1 tools ───────\n //\n // Each Resource delegates to the existing internal tool handler (GAT-01\n // seam preservation: no logic duplication). The v1 tool handlers remain\n // wired in `tools/call` — only their descriptions get a DEPRECATED notice\n // (see src/tool-registry.ts).\n //\n // `vault-memory://vaults` is static (no per-vault variable). The other\n // four use ResourceTemplate with a `{vault}` variable; `backlinks`\n // additionally uses RFC 6570 reserved expansion `{+docId}` so multi-segment\n // paths (e.g. `notes/sub/file.md`) parse as a single value.\n const rel08Vaults = RESOURCES.find((r) => r.name === \"vaults\");\n const rel08Models = RESOURCES.find((r) => r.name === \"models\");\n const rel08Recent = RESOURCES.find((r) => r.name === \"recent\");\n const rel08Stats = RESOURCES.find((r) => r.name === \"stats\");\n const rel08Backlinks = RESOURCES.find((r) => r.name === \"backlinks\");\n if (\n rel08Vaults === undefined ||\n rel08Models === undefined ||\n rel08Recent === undefined ||\n rel08Stats === undefined ||\n rel08Backlinks === undefined\n ) {\n throw new Error(\n \"REL-08 Resources missing from RESOURCES registry — check src/resource-registry.ts\",\n );\n }\n\n server.registerResource(\n rel08Vaults.name,\n RESOURCE_URI_VAULTS,\n {\n title: \"Vaults\",\n description: rel08Vaults.description,\n mimeType: rel08Vaults.mimeType,\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(handleListVaults(manager), null, 2),\n },\n ],\n }),\n );\n\n server.registerResource(\n rel08Models.name,\n new ResourceTemplate(`${RESOURCE_URI_MODELS}/{vault}`, { list: undefined }),\n {\n title: \"Embedding models\",\n description: rel08Models.description,\n mimeType: rel08Models.mimeType,\n },\n async (uri, variables) => {\n const vaultName = String(variables.vault ?? \"\");\n try {\n const vault = manager.require(vaultName);\n const models = listModels(vault);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ models, count: models.length }, null, 2),\n },\n ],\n };\n } catch (err) {\n const message = errorMessage(err);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: message }),\n },\n ],\n };\n }\n },\n );\n\n server.registerResource(\n rel08Recent.name,\n new ResourceTemplate(`${RESOURCE_URI_RECENT}/{vault}`, { list: undefined }),\n {\n title: \"Recent notes\",\n description: rel08Recent.description,\n mimeType: rel08Recent.mimeType,\n },\n async (uri, variables) => {\n const vaultName = String(variables.vault ?? \"\");\n try {\n manager.require(vaultName);\n // Default limit matches the recent_notes tool's schema default (20).\n const limitParam = uri.searchParams.get(\"limit\");\n const sinceParam = uri.searchParams.get(\"since\");\n const limit = limitParam !== null ? Number(limitParam) : 20;\n const since = sinceParam !== null ? Number(sinceParam) : undefined;\n const payload = handleRecentNotes(manager, vaultName, limit, since);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n },\n ],\n };\n } catch (err) {\n const message = errorMessage(err);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: message }),\n },\n ],\n };\n }\n },\n );\n\n server.registerResource(\n rel08Stats.name,\n new ResourceTemplate(`${RESOURCE_URI_STATS}/{vault}`, { list: undefined }),\n {\n title: \"Vault stats\",\n description: rel08Stats.description,\n mimeType: rel08Stats.mimeType,\n },\n async (uri, variables) => {\n const vaultName = String(variables.vault ?? \"\");\n try {\n manager.require(vaultName);\n const payload = handleVaultStats(manager, vaultName);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n },\n ],\n };\n } catch (err) {\n const message = errorMessage(err);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: message }),\n },\n ],\n };\n }\n },\n );\n\n server.registerResource(\n rel08Backlinks.name,\n new ResourceTemplate(`${RESOURCE_URI_BACKLINKS}/{vault}/{+docId}`, {\n list: undefined,\n }),\n {\n title: \"Backlinks\",\n description: rel08Backlinks.description,\n mimeType: rel08Backlinks.mimeType,\n },\n async (uri, variables) => {\n const vaultName = String(variables.vault ?? \"\");\n const rawDocId = variables.docId;\n // RFC 6570 reserved expansion: when the URI contains percent-encoded\n // characters (e.g. spaces or unicode in path segments), the SDK\n // already decodes them. The variable arrives as the raw path string.\n const docId = Array.isArray(rawDocId) ? rawDocId.join(\"/\") : String(rawDocId ?? \"\");\n try {\n const vault = manager.require(vaultName);\n const backlinks = listBacklinks(vault, docId);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ backlinks }, null, 2),\n },\n ],\n };\n } catch (err) {\n const message = errorMessage(err);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: message }),\n },\n ],\n };\n }\n },\n );\n\n // ─── Phase 6 (Plan 06-02) — start per-vault ContractRegistry ─────────────\n //\n // Boot scan + ChangeFeed hot-reload subscriber per vault. The boot scan\n // is light (yaml@2.9 parse over a handful of contract YAMLs) and runs\n // synchronously here so the registry is populated before any client\n // request lands. The ChangeFeed subscription is the third concurrent\n // subscriber on the per-vault ObsidianFsChangeFeed (alongside the\n // VaultWatcher from Phase 1 and the BriefStalenessDaemon from Phase 5).\n // Lock contention is N/A — the ContractRegistry holds no lockfile.\n //\n // When [contracts.auto_register_tools] is true, an initial sync runs\n // after the boot scan completes, registering one MCP tool per parsed\n // contract (prefix from `config.contracts.tool_prefix`). Subsequent\n // ChangeFeed events trigger another sync via the onRegistryChange hook.\n onPhase(\"start_contract_registries\");\n // Plan 06-03 — boot the peer-MCP registry BEFORE per-vault contract\n // registries so each vault's instantiate deps share the same registry.\n // Failures inside `start()` are non-fatal — individual clients mark\n // themselves unavailable + log to stderr (Pitfall F4 mitigation).\n try {\n await peerMcpRegistry.start(config.contracts.mcp_clients);\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[peer-mcp-registry] start failed: ${message}\\n`);\n }\n for (const vault of manager.list()) {\n const feed = changeFeeds.get(vault.config.name);\n if (feed === undefined) continue;\n const source = adapterRegistry.resolveSource(\n parseSourceHandle(`obsidian-fs://${vault.config.name}`),\n );\n const registeredHandles = new Map<string, RegisteredTool>();\n let started: StartedContractRegistry;\n try {\n // eslint-disable-next-line prefer-const\n started = await startContractRegistry({\n vault,\n feed,\n source,\n auditDeps: { contractAudit: vault.db.contractAudit },\n // Phase 7 / Plan 07-07 / CAN-08 — hash-keyed echo suppression\n // for the plugin's `.yaml` companion writes. Shared with the\n // change-feed watcher above so a single set sees both write\n // pathways (writer, indexer, plugin).\n suppression,\n // CAN-08 D-WATCH-SERVER-NOTIFY — emit the external-edit MCP\n // Resource notification when (and only when) the gate is on.\n // The plugin's `ReloadNotifier` (plan 07-07 task 3) subscribes\n // via `notifications/resources/updated` for this URI and\n // prompts the user with a Modal.\n onExternalReload: config.plugin.enabled\n ? (file) => {\n try {\n server.server.notification({\n method: \"notifications/resources/updated\",\n params: {\n uri: \"vault-memory://contracts/reloaded\",\n // Body is non-standard for resources/updated but\n // MCP clients ignore unknown params. Carrying the\n // file path here saves the plugin a follow-up\n // resource read in the common case.\n _meta: { path: file, reason: \"external_edit\" },\n },\n });\n } catch (err) {\n const msg = errorMessage(err);\n process.stderr.write(`[contracts-reloaded-notify] ${vault.config.name}: ${msg}\\n`);\n }\n }\n : undefined,\n onRegistryChange: () => {\n if (config.contracts.auto_register_tools) {\n syncAutoRegistered(\n server,\n started.registry,\n config.contracts.tool_prefix,\n registeredHandles,\n { enabled: true, instantiateHandler },\n );\n }\n },\n });\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[contract-registry:${vault.config.name}] start failed: ${message}\\n`);\n continue;\n }\n if (config.contracts.auto_register_tools) {\n syncAutoRegistered(\n server,\n started.registry,\n config.contracts.tool_prefix,\n registeredHandles,\n { enabled: true, instantiateHandler },\n );\n }\n contractRegistries.set(vault.config.name, {\n started,\n registered: registeredHandles,\n });\n }\n\n // ─── Phase 7 (Plan 07-04) — plugin-control MCP tools ─────────────────────\n //\n // Gated by `config.plugin.enabled` (default OFF). When false, zero plugin\n // tools register and `tools/list` is byte-equivalent to the v1-baseline\n // snapshot for non-plugin deployments (REL-08 ≤32-tool budget).\n //\n // The runtime-config store is owned at the serve() lifetime and threaded\n // into the `set_runtime_config` handler. Hot-swap mutations are NOT\n // persisted — `~/.vault-memory/config.toml` remains authoritative across\n // restarts (PLG-01 §\"Hot-swap semantics\").\n const runtimeConfigStore = new RuntimeConfigStore({});\n const pluginToolsRegistered = new Map<string, RegisteredTool>();\n // `reindexVault` shim — wraps the existing `indexVault` entry point so the\n // trigger_reindex tool is decoupled from the full indexer surface.\n const reindexVault = async (\n vaultName: string,\n onProgress?: (p: TriggerReindexProgress) => void,\n ): Promise<void> => {\n const v = manager.list().find((vt) => vt.config.name === vaultName);\n if (v === undefined) throw new Error(`unknown vault: ${vaultName}`);\n const embeddingModel =\n v.config.embedding_model ?? config.server.default_embedding_model ?? \"qwen3-embedding\";\n // Use a dynamic import to keep the indexer module out of the startup\n // critical path when the plugin gate is OFF.\n const { indexVault } = await import(\"./indexer/index.js\");\n let lastReported = 0;\n await indexVault(v, {\n mode: \"full\",\n embeddingModel,\n ollama,\n onProgress: (_msg: string) => {\n // The current indexer onProgress signal is a free-text status line;\n // we increment a per-call counter as a coarse progress proxy. The\n // chrome can render that as \"reindex in progress\" until the call\n // resolves. A future indexer enhancement (out of scope for 07-04)\n // can replace this with structured progress events.\n lastReported += 1;\n onProgress?.({ progress: lastReported });\n },\n });\n };\n // Snapshot peer-MCP availability for get_runtime_stats.\n const peerMcpStatus = (): Array<{ name: string; available: boolean }> => {\n const out: Array<{ name: string; available: boolean }> = [];\n for (const name of Object.keys(config.contracts.mcp_clients)) {\n const client = peerMcpRegistry.get(name);\n out.push({ name, available: client?.available ?? false });\n }\n return out;\n };\n // Contract count per vault — reads from the live registry map populated above.\n const contractCountFor = (vaultName: string): number => {\n const state = contractRegistries.get(vaultName);\n if (state === undefined) return 0;\n let count = 0;\n for (const _ of state.started.registry.entries()) count += 1;\n return count;\n };\n syncPluginTools(server, pluginToolsRegistered, {\n enabled: config.plugin.enabled,\n runtimeConfig: runtimeConfigStore,\n configPath: configPath(),\n listVaults: () => manager.list() as never,\n peerMcpStatus,\n contractCountFor,\n reindexVault,\n // Plan 07-07 / CAN-08 — same shared instance the contract loader\n // sees, so the plugin's `suppress_contract_write` call and the\n // change-feed handler observe the same entries.\n suppression,\n // SOURCES-REGISTRY.md §6 (Stage 2) — live registry for refresh_source\n // + unset_mcp_client. The singleton booted above.\n sourceRegistry: peerMcpRegistry,\n notifier: (notification) => {\n // Forward to the underlying MCP server transport. The McpServer\n // wraps a low-level Server with `server.server`; the notification\n // method is exposed there.\n server.server.notification(notification);\n },\n });\n\n onPhase(\"connect_transport\");\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n // Fire-and-forget — the MCP handshake is complete, tools are usable, and\n // catch-up runs in the background. Errors are already logged inside the\n // function; we still catch here to satisfy the linter and surface anything\n // unexpected on stderr.\n //\n // Plan 02-03b: `start_catchup` fires AFTER `register_memory_sinks` (the\n // sentinel-provisioning step above has already completed). The phase\n // hook fires synchronously so the bootstrap-order assertion in tests\n // can observe the invariant `register_memory_sinks` < `start_catchup`.\n onPhase(\"start_catchup\");\n startCatchupAndWatchers().catch((err) => {\n const message = errorMessage(err);\n process.stderr.write(`[catchup] unexpected failure: ${message}\\n`);\n });\n}\n\n// ─── Tool handlers ───────────────────────────────────────────────────────────\n","/**\n * vault-memory CLI entrypoint.\n */\n\nexport {};\n\nconst args = process.argv.slice(2);\nconst command = args[0] ?? \"serve\";\n\nswitch (command) {\n case \"serve\":\n await import(\"./server.js\").then((m) => m.serve());\n break;\n\n case \"index\":\n await runIndex(args.slice(1));\n break;\n\n case \"add-vault\":\n await runAddVault(args.slice(1));\n break;\n\n case \"--help\":\n case \"-h\":\n case \"help\":\n printHelp();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n printHelp();\n process.exit(2);\n}\n\nasync function runIndex(rest: string[]): Promise<void> {\n const { loadConfig } = await import(\"./config/index.js\");\n const { VaultManager } = await import(\"./vault/index.js\");\n const { OllamaClient } = await import(\"./ollama/index.js\");\n const { indexVault } = await import(\"./indexer/index.js\");\n\n // Parse flags\n let vaultName: string | null = null;\n let mode: \"full\" | \"incremental\" = \"incremental\";\n\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n if (arg === \"--full\") mode = \"full\";\n else if (arg === \"--vault\") {\n vaultName = rest[i + 1] ?? null;\n i++;\n } else if (arg && !arg.startsWith(\"--\") && vaultName === null) {\n vaultName = arg;\n }\n }\n\n const config = await loadConfig();\n if (config.vaults.length === 0) {\n console.error(\"No vaults configured. Edit ~/.vault-memory/config.toml.\");\n process.exit(2);\n }\n\n const manager = new VaultManager();\n await manager.loadAll(config.vaults);\n\n const ollama = new OllamaClient({\n endpoint: config.server.ollama_endpoint,\n });\n\n const targets = vaultName ? [manager.require(vaultName)] : manager.list();\n\n for (const vault of targets) {\n // ADR-008: ContextFit-backed vaults use the CPU-only token-native engine.\n // Two-part index: (1) build the full SQLite content layer WITHOUT embeddings\n // (powers graph/sections/frontmatter/stats tools, the watcher, catchup, and\n // write re-index) and (2) build the ContextFit search KB. No Ollama, no GPU.\n if (vault.config.backend === \"contextfit\") {\n const { indexVaultWithContextFit } = await import(\"./adapters/retrieval/contextfit/index.js\");\n console.error(\n `\\n→ Indexing \"${vault.config.name}\" with ContextFit (CPU-only, no embeddings)`,\n );\n // (1) SQLite content layer — embeddings:\"none\" skips Ollama entirely.\n const sqlite = await indexVault(vault, {\n mode,\n embeddingModel: \"contextfit\",\n embeddings: \"none\",\n onProgress: (msg) => console.error(` ${msg}`),\n });\n if (sqlite.status !== \"completed\") {\n console.error(`✗ ${vault.config.name}: SQLite layer failed — ${sqlite.error}`);\n process.exitCode = 1;\n continue;\n }\n // (2) ContextFit search KB.\n const cfResult = await indexVaultWithContextFit(vault.config, {\n onProgress: (msg) => console.error(` ${msg}`),\n });\n if (cfResult.status === \"completed\") {\n console.error(\n `✓ ${vault.config.name}: ${sqlite.notesIndexed} notes (SQLite) + ContextFit KB · ${sqlite.durationMs + cfResult.durationMs}ms`,\n );\n } else {\n console.error(`✗ ${vault.config.name}: ContextFit KB failed — ${cfResult.error}`);\n process.exitCode = 1;\n }\n continue;\n }\n\n const model =\n vault.config.embedding_model ?? config.server.default_embedding_model ?? \"qwen3-embedding\";\n\n console.error(`\\n→ Indexing \"${vault.config.name}\" (${mode}) with ${model}`);\n const result = await indexVault(vault, {\n mode,\n embeddingModel: model,\n ollama,\n onProgress: (msg) => console.error(` ${msg}`),\n });\n\n if (result.status === \"completed\") {\n const skipSuffix = result.notesSkipped > 0 ? `, ${result.notesSkipped} skipped` : \"\";\n console.error(\n `✓ ${vault.config.name}: ${result.notesIndexed} new, ` +\n `${result.notesUpdated} updated, ${result.notesDeleted} deleted${skipSuffix}, ` +\n `${result.chunksCreated} chunks · ${result.durationMs}ms`,\n );\n } else {\n console.error(`✗ ${vault.config.name}: ${result.error}`);\n process.exitCode = 1;\n }\n }\n\n manager.closeAll();\n}\n\n/**\n * add-vault: onboard a new Obsidian vault end-to-end.\n * 1. append a [[vaults]] block to ~/.vault-memory/config.toml\n * 2. write/merge .mcp.json in the vault root (so an MCP-aware client\n * can auto-spawn the MCP server when that vault is opened)\n * 3. build an initial index (unless --no-index is passed)\n *\n * Idempotent: re-running with a known path skips config mutation\n * and only refreshes the .mcp.json + delta-indexes.\n */\nasync function runAddVault(rest: string[]): Promise<void> {\n const { addVault } = await import(\"./config/index.js\");\n\n // Parse positional path + flags.\n let path: string | null = null;\n let name: string | undefined;\n let writeEnabled = false;\n let skipIndex = false;\n let backend: \"ollama\" | \"contextfit\" | undefined;\n\n const USAGE =\n \"Usage: vault-memory add-vault <path> [--name <name>] [--write] \" +\n \"[--backend ollama|contextfit] [--no-index]\";\n\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n if (arg === \"--name\") {\n name = rest[i + 1];\n i++;\n } else if (arg === \"--write\" || arg === \"--write-enabled\") {\n writeEnabled = true;\n } else if (arg === \"--backend\") {\n const v = rest[i + 1];\n i++;\n if (v !== \"ollama\" && v !== \"contextfit\") {\n console.error(`--backend must be \"ollama\" or \"contextfit\" (got: ${v ?? \"<missing>\"})`);\n process.exit(2);\n }\n backend = v;\n } else if (arg === \"--no-index\") {\n skipIndex = true;\n } else if (arg === \"--help\" || arg === \"-h\") {\n console.error(`${USAGE}\n\nRegisters a vault in ~/.vault-memory/config.toml, writes a .mcp.json\ninto the vault root, and runs an initial index. Idempotent.\n\n--backend contextfit Use the CPU-only, token-native ContextFit engine\n (no Ollama / embeddings / GPU). Requires the\n \\`contextfit\\` CLI (pipx install contextfit). Ideal for\n resource-limited / non-GPU hosts (e.g. a Synology NAS).`);\n return;\n } else if (arg && !arg.startsWith(\"--\") && path === null) {\n path = arg;\n }\n }\n\n if (path === null) {\n console.error(USAGE);\n process.exit(2);\n }\n\n console.error(`→ Registering vault: ${path}${backend ? ` (backend: ${backend})` : \"\"}`);\n const result = await addVault({ path, name, writeEnabled, ...(backend ? { backend } : {}) });\n\n // Render the per-step transcript so users see exactly what changed.\n for (const step of result.steps) {\n switch (step.kind) {\n case \"config-added\":\n console.error(` ✓ config.toml: added [[vaults]] \"${step.name}\"`);\n break;\n case \"config-already-registered\":\n console.error(\n ` • config.toml: already registered as \"${step.name}\" (${step.existingPath})`,\n );\n break;\n case \"mcp-json-created\":\n console.error(` ✓ ${step.mcpPath}: created`);\n break;\n case \"mcp-json-merged\":\n console.error(` ✓ ${step.mcpPath}: merged vault-memory entry`);\n break;\n case \"mcp-json-unchanged\":\n console.error(` • ${step.mcpPath}: already up to date`);\n break;\n }\n }\n\n if (skipIndex) {\n console.error(`\\nSkipped indexing (--no-index). Run later:`);\n console.error(` vault-memory index ${result.name}`);\n } else {\n console.error(`\\n→ Building initial index for \"${result.name}\"…`);\n // Reuse the existing index flow. Pass the vault name as positional arg.\n await runIndex([result.name]);\n }\n\n console.error(\n `\\nDone. Open ${result.resolvedPath} in your MCP-aware client — the vault-memory MCP server will be available.`,\n );\n}\n\nfunction printHelp(): void {\n console.error(`vault-memory — local-first semantic memory MCP server\n\nUSAGE:\n vault-memory [COMMAND] [OPTIONS]\n\nCOMMANDS:\n serve Start MCP server on stdio (default)\n index [VAULT] Build/refresh index for a vault (or all if omitted)\n --full Wipe derived layer and re-embed everything\n --vault NAME Alternative flag form\n add-vault <path> Register a new vault end-to-end (config + .mcp.json + index)\n --name NAME Override the auto-slugified name\n --write Allow MCP write operations (default: read-only)\n --no-index Skip the initial index (you can run it later)\n init Interactive config wizard (Phase 5 — not yet)\n help, --help Show this message\n\nCONFIG:\n ~/.vault-memory/config.toml`);\n}\n"],"mappings":"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","names":["path","join","homedir","path","z","args","z","handler","args","z","parseToml","readFile","configPath","handler","args","root","clients","entry","z","handler","args","z","handler","args","z","handler","args","path","z","args","args","topIdx","existing","createHash","rows","path","rows","homedir","join","resolve","z","displayUrl","init_graph","init_graph","args","resolve","homedir","join","searchVaultWithContextFit","path","readFile","join","ok","errorResponse","path","fs","path","init_wikilinks","open","createHash","fs","path","countWords","toPosix","init_wikilinks","fs","path","posix","init_chunker","lineOf","init_sections","computeSectionOffsetRanges","init_chunker","init_sections","path","upsert","insertWikilinks","writeAllEdges","init_chunker","isContextFit","indexVaultWithContextFit","relative","randomUUID","init_indexer","fs","isAbsolute","resolve","sep","fs","basename","matter","extractTitle","countWords","init_indexer","z","z","requiredKeys","baseShape","path","readFile","z","init_schema","z","init_loader","init_schema","init_loader","fs","SENTINEL_FILENAME","args","obsidian_fs_exports","fs","matter","SCHEME","init_obsidian_fs","ok","path","isPlainObject","stripWikilinks","docId","ObsidianFsSource","ObsidianFsDelivery","init_registry","init_registry","createHash","randomBytes","args","args","args","init_chunk_id","init_chunk_id","WIKILINK_RE","args","findBriefByTarget","args","DEFAULT_BRIEF_SINK_NAME","readFile","mkdir","homedir","join","path","DEFAULT_BRIEF_SINK_NAME","DEFAULT_BRIEF_SINK_NAME","init_resources","init_chunk_id","init_resources","args","args","docId","path","displayUrl","path","init_indexer","path","resolve","indexVaultWithContextFit","isContextFit","result","path","chokidar","nativeSep","SCHEME","handler","resolve","init_obsidian_fs","z","DOC_ID_PATTERN","CONTRACT_PATH_REGEX","z","init_registry","slugify","args","init_audit","z","init_schema","CONTRACT_PATH_REGEX","ok","init_loader","init_schema","init_registry","init_audit","slugify","args","path","args","args","MCP_VERB_RE","args","z","args","init_audit","args","BASELINE_VERBS","init_resources","init_registry","init_audit","init_schema","init_loader","init_resources","init_audit","init_audit","init_vault","init_indexer","init_audit","row","aggregateEntries","safeParse","path","args","init_schema","path","indexVaultWithContextFit","result","init_notes","init_schema","displayUrlFor","path","init_search","init_graph","init_memory","init_brief","emptyResult","args","args","docId","path","init_audit","init_assembly","init_contracts","homedir","isContextFit","args","handler","ok","errorResponse","indexVault","init_graph","init_obsidian_fs","init_indexer","init_vault","init_notes","init_search","init_memory","init_brief","init_assembly","init_contracts","loadConfig","VaultManager","OllamaClient","indexVault","indexVaultWithContextFit","addVault","path"]} \ No newline at end of file +{"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../src/config/loader.ts","../src/config/add-vault.ts","../src/config/index.ts","../src/plugin-tools/runtime-config.ts","../src/plugin-tools/set-runtime-config.ts","../src/plugin-tools/resolve-secret.ts","../src/plugin-tools/set-mcp-client.ts","../src/plugin-tools/get-runtime-stats.ts","../src/plugin-tools/trigger-reindex.ts","../src/plugin-tools/suppress-contract-write.ts","../src/plugin-tools/source-tools.ts","../src/errors/format.ts","../src/plugin-tools/index.ts","../src/chunker/headings.ts","../src/sections/anchor.ts","../src/sections/extract.ts","../src/sections/backfill.ts","../src/chunker/chunk-id.ts","../src/db/schema.ts","../src/db/queries/notes.ts","../src/db/queries/chunks.ts","../src/db/queries/embeddings.ts","../src/db/queries/wikilinks.ts","../src/db/queries/edges.ts","../src/db/queries/audit.ts","../src/db/queries/models.ts","../src/db/queries/fts.ts","../src/db/queries/aliases.ts","../src/db/queries/sections.ts","../src/db/queries/brief_sources.ts","../src/db/queries/daemon_state.ts","../src/db/queries/contract-audit.ts","../src/db/database.ts","../src/db/index.ts","../src/vault/manager.ts","../src/vault/index.ts","../src/ollama/retry.ts","../src/ollama/client.ts","../src/ollama/index.ts","../src/adapters/registry.ts","../src/graph/graph.ts","../src/memory/citation-packet.ts","../src/graph/expand.ts","../src/graph/cluster.ts","../src/graph/index.ts","../src/search/hybrid.ts","../src/adapters/retrieval/contextfit/cli.ts","../src/adapters/retrieval/contextfit/ingest-lock.ts","../src/adapters/retrieval/contextfit/index.ts","../src/search/dispatch.ts","../src/search/glob.ts","../src/search/index.ts","../src/rerank/reranker.ts","../src/rerank/onnx-reranker.ts","../src/rerank/index.ts","../src/server/responses.ts","../src/server/utils.ts","../src/frontmatter/query.ts","../src/adapters/source/obsidian-fs/scanner.ts","../src/adapters/source/obsidian-fs/wikilinks.ts","../src/reader/datacore.ts","../src/adapters/source/obsidian-fs/hash.ts","../src/adapters/source/obsidian-fs/parser.ts","../src/adapters/source/obsidian-fs/index.ts","../src/chunker/tokens.ts","../src/chunker/chunker.ts","../src/chunker/index.ts","../src/indexer/resolver.ts","../src/indexer/extract-edges.ts","../src/sections/index.ts","../src/indexer/indexer.ts","../src/indexer/single.ts","../src/indexer/catchup.ts","../src/indexer/shadow.ts","../src/indexer/vacuum.ts","../src/indexer/index.ts","../src/adapters/delivery/obsidian-fs/fs.ts","../src/adapters/delivery/obsidian-fs/write.ts","../src/memory/validator.ts","../src/memory/contract/default-v1.ts","../src/memory/contract/default-brief-v1.ts","../src/adapters/delivery/obsidian-fs/path.ts","../src/adapters/delivery/obsidian-fs/contract-yaml-read.ts","../src/memory/contract/schema.ts","../src/memory/contract/loader.ts","../src/memory/contract/index.ts","../src/memory/sink.ts","../src/adapters/delivery/obsidian-fs/sentinel.ts","../src/adapters/delivery/obsidian-fs/index.ts","../src/frontmatter/update.ts","../src/frontmatter/index.ts","../src/memory/registry.ts","../src/memory/resources/list-sinks.ts","../src/memory/resources/memory-stats.ts","../src/memory/resources/index.ts","../src/memory/index.ts","../src/resource-registry.ts","../src/memory/tools/record-observation.ts","../src/memory/tools/supersede.ts","../src/memory/tools/recall.ts","../src/memory/tools/index.ts","../src/brief/chunk-id.ts","../src/brief/source-hashes.ts","../src/brief/llm-ladder.ts","../src/brief/body-validator.ts","../src/brief/compile.ts","../src/brief/get.ts","../src/brief/lock.ts","../src/brief/daemon.ts","../src/brief/resources.ts","../src/brief/index.ts","../src/assembly/search-sections.ts","../src/assembly/outline.ts","../src/adapters/change-feed/obsidian-fs/queue.ts","../src/adapters/change-feed/obsidian-fs/chokidar-config.ts","../src/adapters/change-feed/obsidian-fs/watcher.ts","../src/adapters/change-feed/obsidian-fs/suppression.ts","../src/adapters/change-feed/obsidian-fs/change-feed.ts","../src/adapters/change-feed/obsidian-fs/index.ts","../src/tool-registry.ts","../src/contracts/types.ts","../src/contracts/types-catalog.ts","../src/contracts/json-schema-ref.ts","../src/contracts/input-schema.ts","../src/contracts/registry.ts","../src/contracts/slug.ts","../src/contracts/audit.ts","../src/contracts/schema.ts","../src/contracts/loader.ts","../src/contracts/auto-register.ts","../src/contracts/templates.ts","../src/contracts/mcp-clients.ts","../src/contracts/verbs/mcp-extension.ts","../src/contracts/verbs/index.ts","../src/contracts/instantiate.ts","../src/contracts/describe.ts","../src/contracts/resources.ts","../src/contracts/sources-resources.ts","../src/contracts/index.ts","../src/audit/audit.ts","../src/audit/index.ts","../src/server/handlers/vault.ts","../src/schema/folder-conventions.ts","../src/schema/neighbor-inference.ts","../src/schema/content-heuristics.ts","../src/schema/combiner.ts","../src/schema/index.ts","../src/server/handlers/notes.ts","../src/server/handlers/search.ts","../src/server/handlers/graph.ts","../src/server/handlers/memory.ts","../src/server/handlers/brief.ts","../src/assembly/dossier.ts","../src/assembly/bundle.ts","../src/assembly/index.ts","../src/server/handlers/assembly.ts","../src/server/handlers/contracts.ts","../package.json","../src/version.ts","../src/server.ts","../src/cli.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","/**\n * Configuration loader.\n *\n * Reads `~/.vault-memory/config.toml`. Returns sensible defaults when the\n * file does not exist (empty vault list, default Ollama endpoint). Validates\n * shape with Zod.\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { parse as parseToml } from \"smol-toml\";\nimport { z } from \"zod\";\nimport type { AppConfig } from \"../types.js\";\n\nconst ServerConfigSchema = z.object({\n log_level: z.enum([\"debug\", \"info\", \"warn\", \"error\"]).optional(),\n ollama_endpoint: z.string().url().optional(),\n default_embedding_model: z.string().optional(),\n reranker_model: z.string().optional(),\n reranker_backend: z.enum([\"onnx\", \"ollama\"]).optional(),\n reranker_model_dir: z.string().optional(),\n});\n\n/**\n * ADR-008: per-vault ContextFit settings. Only consulted when\n * `backend = \"contextfit\"`. All optional — a bare `backend = \"contextfit\"`\n * uses ContextFit on PATH with the cl100k_base tokenizer + hybrid method.\n */\nconst ContextFitConfigSchema = z.object({\n command: z.string().min(1).optional(),\n tokenizer: z.string().min(1).optional(),\n method: z.enum([\"exact\", \"bm25\", \"sid\", \"graph\", \"hierarchy\", \"hybrid\"]).optional(),\n});\n\nconst VaultConfigSchema = z.object({\n name: z.string().min(1),\n path: z.string().min(1),\n // ADR-008: retrieval engine. Omitted ⇒ \"ollama\" (back-compat default).\n backend: z.enum([\"ollama\", \"contextfit\"]).optional(),\n contextfit: ContextFitConfigSchema.optional(),\n embedding_model: z.string().optional(),\n secondary_embedding_model: z.string().optional(),\n write_enabled: z.boolean().optional(),\n exclude_globs: z.array(z.string()).optional(),\n});\n\n/**\n * Phase 5 / D-10 ladder tier 2: per-vault Ollama brief-compile config.\n *\n * `[brief.ollama] model = \"...\"` opts the vault into Tier 2 of the\n * capability-first LLM ladder. The MCP Sampling tier (Tier 1) is\n * checked first per-call; this block is only consulted when Sampling\n * is not available. Strictly localhost (existing OllamaClient binds\n * to `http://localhost:11434`).\n *\n * Schema is OPTIONAL: backwards-compatible. Existing v1.x configs\n * without `[brief]` still parse identically; the ladder simply\n * skips Tier 2 and tries Tier 3 (`prepared_text`) → Tier 4\n * (structured error). See ADR-005 §\"Capability-first LLM ladder\".\n */\nconst BriefOllamaConfigSchema = z.object({\n model: z.string().min(1),\n});\nconst BriefConfigSchema = z.object({\n ollama: BriefOllamaConfigSchema.optional(),\n});\n\n/**\n * Phase 6 / ADR-006 §Decision 1: `[contracts]` block (per-vault gate).\n *\n * Backwards-compatible: a config.toml with no `[contracts]` block parses\n * to the documented defaults via `.optional().default(...)` at the\n * AppConfigSchema attach site.\n *\n * Trust scope (T-06-01-04 disposition: accept): `mcp_clients.<name>.command`\n * is the same trust level as the rest of `~/.vault-memory/config.toml`\n * (user-owned). Plan 06-03 uses `child_process.spawn(command, args)` with\n * NO shell — args pass verbatim. Documented in ADR-006 §Threat Model.\n */\nconst ContractsMcpClientConfigSchema = z.object({\n command: z.string().min(1).describe(\"Peer MCP server executable path\"),\n args: z.array(z.string()).optional(),\n env: z.record(z.string(), z.string()).optional(),\n});\n\nconst ContractsConfigSchema = z.object({\n auto_register_tools: z\n .boolean()\n .default(false)\n .describe(\"D-A1b — per-vault gate for auto-registering contracts as MCP Tools\"),\n tool_prefix: z\n .string()\n .min(1)\n .regex(/^[a-z_][a-z0-9_]*$/)\n .default(\"vm_\")\n .describe(\"D-A1c — slug prefix for auto-registered tool names; A7 enforces non-empty\"),\n step_timeout_seconds: z\n .number()\n .int()\n .positive()\n .default(30)\n .describe(\n \"Q-TIMEOUT — applied only to peer-MCP verbs (baseline verbs use their own discipline)\",\n ),\n defaults: z\n .record(z.string(), z.string())\n .default({})\n .describe(\"D-A4b — default chain step 2: handle → URI fallback\"),\n mcp_clients: z\n .record(z.string(), ContractsMcpClientConfigSchema)\n .default({})\n .describe(\"D-A2a — peer MCP clients vault-memory connects to as an MCP client\"),\n});\n\nconst DEFAULT_CONTRACTS_CONFIG = {\n auto_register_tools: false,\n tool_prefix: \"vm_\",\n step_timeout_seconds: 30,\n defaults: {},\n mcp_clients: {},\n} as const;\n\n/**\n * Phase 7 / Plan 07-04 / D-MCP-SURFACE: `[plugin]` block.\n *\n * Single field for v2.0.0: `enabled` — gates the five plugin-control MCP tools\n * (`set_runtime_config`, `resolve_secret`, `set_mcp_client`, `get_runtime_stats`,\n * `trigger_reindex`). Default OFF preserves v1 tools-list snapshot stability\n * (REL-08 ≤32-tool budget for non-plugin deployments).\n *\n * Backwards-compatible: configs without `[plugin]` resolve to\n * `DEFAULT_PLUGIN_CONFIG` via `.optional().default(...)` at the AppConfigSchema\n * attach site.\n */\nconst PluginConfigSchema = z.object({\n enabled: z\n .boolean()\n .default(false)\n .describe(\n \"D-MCP-SURFACE — gates the 5 plugin-control MCP tools (set_runtime_config, resolve_secret, set_mcp_client, get_runtime_stats, trigger_reindex). Default OFF preserves v1 tools-list snapshot stability per REL-08.\",\n ),\n});\n\nconst DEFAULT_PLUGIN_CONFIG = { enabled: false } as const;\n\n// Phase 2: optional [memory] and [[memory_sinks]] blocks.\n//\n// The handle string is intentionally NOT validated against\n// MEMORY_SINK_HANDLE_PATTERN here — the brand-cast (and resulting\n// throw on malformed input) happens in `MemorySinkRegistry`. Keeping\n// the config loader free of `src/memory/*` imports preserves the\n// ADR-002 layering (config is infrastructure; memory is a domain\n// module that depends on config, not the other way around).\nconst MemorySinkConfigSchema = z.object({\n name: z.string().min(1),\n handle: z.string().min(1),\n contract: z.string().min(1).default(\"default-memory-v1\"),\n});\n\nconst MemoryConfigSchema = z.object({\n default_sink: z.string().min(1).optional(),\n});\n\nconst AppConfigSchema = z.object({\n server: ServerConfigSchema.optional().default({}),\n vaults: z.array(VaultConfigSchema).optional().default([]),\n memory: MemoryConfigSchema.optional(),\n memory_sinks: z.array(MemorySinkConfigSchema).optional().default([]),\n // Phase 5 / D-10 tier 2 (ADR-005). Backwards-compatible: existing\n // configs without `[brief]` parse identically.\n brief: BriefConfigSchema.optional(),\n // Phase 6 / ADR-006 §Decision 1. Backwards-compatible: configs without\n // `[contracts]` resolve to DEFAULT_CONTRACTS_CONFIG.\n contracts: ContractsConfigSchema.optional().default(DEFAULT_CONTRACTS_CONFIG),\n // Phase 7 / Plan 07-04 / D-MCP-SURFACE. Backwards-compatible: configs\n // without `[plugin]` resolve to DEFAULT_PLUGIN_CONFIG (enabled: false).\n plugin: PluginConfigSchema.optional().default(DEFAULT_PLUGIN_CONFIG),\n});\n\nconst DEFAULT_CONFIG: AppConfig = {\n server: {\n log_level: \"info\",\n ollama_endpoint: \"http://localhost:11434\",\n default_embedding_model: \"qwen3-embedding\",\n },\n vaults: [],\n memory_sinks: [],\n contracts: { ...DEFAULT_CONTRACTS_CONFIG },\n plugin: { ...DEFAULT_PLUGIN_CONFIG },\n};\n\nexport function configPath(): string {\n return join(homedir(), \".vault-memory\", \"config.toml\");\n}\n\nexport async function loadConfig(path: string = configPath()): Promise<AppConfig> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n return DEFAULT_CONFIG;\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = parseToml(raw);\n } catch (err) {\n throw new Error(`Failed to parse TOML at ${path}: ${(err as Error).message}`);\n }\n\n const validated = AppConfigSchema.parse(parsed);\n\n return {\n server: {\n ...DEFAULT_CONFIG.server,\n ...validated.server,\n },\n vaults: validated.vaults,\n memory: validated.memory,\n // Phase 5 / ADR-005 §\"Sub-folder MemorySink ordering\": sort the\n // memory_sinks array by path-specificity (longest resource first)\n // so `MemorySinkRegistry.findSinkContaining` (startsWith over\n // insertion order, src/memory/registry.ts:190-202) resolves\n // sub-folder sinks BEFORE their parents. Concretely:\n // `_memory/_briefs/` MUST be registered before `_memory/` so a\n // brief write routes into the brief-specific sink (bound to\n // `default-brief-v1`, accepts `status: \"stale\"`) instead of the\n // parent (bound to `default-memory-v1`, rejects `\"stale\"`).\n memory_sinks: sortSinksByPathSpecificity(validated.memory_sinks),\n brief: validated.brief,\n contracts: validated.contracts,\n plugin: validated.plugin,\n };\n}\n\n/**\n * Phase 5: sort `[[memory_sinks]]` so more-specific paths come first.\n *\n * The `handle` shape is `<scheme>://<authority>/<resource>` (ADR-001\n * URI form). Path-specificity is measured by the length of the\n * `<resource>` portion — longer resources are more specific and MUST\n * register first. Comparator is stable (Array.prototype.sort is\n * stable in V8 ≥ Node 12); equal-length resources preserve their\n * declaration order.\n *\n * Pitfall 1 mitigation (ADR-005): without this normalization, a TOML\n * that declares `_memory/` before `_memory/_briefs/` would route\n * brief writes through the parent sink's `default-memory-v1`\n * contract, which rejects `status: \"stale\"`.\n */\nfunction sortSinksByPathSpecificity<T extends { handle: string }>(sinks: T[]): T[] {\n // Compute the resource length once per sink — avoids re-parsing\n // inside the comparator (n*log(n) calls).\n type Tagged = { sink: T; resourceLength: number; order: number };\n const tagged: Tagged[] = sinks.map((s, i) => ({\n sink: s,\n resourceLength: extractResourceLength(s.handle),\n order: i,\n }));\n tagged.sort((a, b) => {\n // Primary: longer resource (more specific) first.\n if (a.resourceLength !== b.resourceLength) {\n return b.resourceLength - a.resourceLength;\n }\n // Secondary: preserve declaration order on ties (defensive — V8\n // sort is already stable but the explicit tie-breaker documents\n // the intent).\n return a.order - b.order;\n });\n return tagged.map((t) => t.sink);\n}\n\n/**\n * Extract the `<resource>` portion length from a `<scheme>://<authority>/<resource>`\n * handle. Returns 0 for malformed handles — they fall to the bottom\n * of the sorted list, which is harmless because malformed handles\n * are caught downstream by `parseMemorySinkHandle` in\n * `src/memory/sink.ts`.\n */\nfunction extractResourceLength(handle: string): number {\n const schemeEnd = handle.indexOf(\"://\");\n if (schemeEnd === -1) return 0;\n const afterScheme = handle.slice(schemeEnd + 3);\n const firstSlash = afterScheme.indexOf(\"/\");\n if (firstSlash === -1) return 0;\n return afterScheme.length - (firstSlash + 1);\n}\n","/**\n * Atomically add a new vault to vault-memory:\n * 1. Validate the path is a directory and not already registered.\n * 2. Append a [[vaults]] block to ~/.vault-memory/config.toml.\n * 3. Write/merge .mcp.json in the vault root so an MCP-aware client\n * (e.g. ChatGPT Custom Connectors, Claude Desktop, or any other // vault-memory:claude-ok\n * stdio MCP host) can spawn the MCP server when the user opens\n * the vault.\n *\n * This is the source of truth for \"onboard a new vault\" — invoked by both\n * the CLI `add-vault` subcommand and the `/add-vault` skill bundled in\n * `skills/`.\n *\n * Idempotent: re-running with the same path is a no-op for config.toml\n * and a merge for .mcp.json (vault-memory entry under mcpServers gets\n * its env updated if the active-vault flag changed, other servers stay\n * untouched).\n */\n\nimport { promises as fs } from \"node:fs\";\nimport { join, basename, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { loadConfig, configPath } from \"./loader.js\";\n\nexport interface AddVaultOptions {\n /** Absolute path to the Obsidian vault root. */\n path: string;\n /** Optional explicit name. Defaults to slugified basename(path). */\n name?: string;\n /** Whether the MCP server may write to this vault. Default false (safer). */\n writeEnabled?: boolean;\n /** ADR-008: retrieval engine. \"contextfit\" = CPU-only token-native engine\n * (no Ollama/GPU). Omitted/\"ollama\" = the embeddings+sqlite-vec default. */\n backend?: \"ollama\" | \"contextfit\";\n /** Custom exclude_globs. Default = sensible Obsidian-system folders. */\n excludeGlobs?: string[];\n /** Custom config.toml path (testing). */\n configFile?: string;\n /** Custom binary command for .mcp.json (default \"vault-memory\"). */\n binary?: string;\n}\n\nexport type AddVaultStep =\n | { kind: \"config-added\"; name: string; path: string }\n | { kind: \"config-already-registered\"; name: string; existingPath: string }\n | { kind: \"mcp-json-created\"; mcpPath: string }\n | { kind: \"mcp-json-merged\"; mcpPath: string }\n | { kind: \"mcp-json-unchanged\"; mcpPath: string };\n\nexport interface AddVaultResult {\n /** Resolved vault name as it appears in config.toml. */\n name: string;\n /** Absolute, normalised vault path. */\n resolvedPath: string;\n /** Where in config.toml the vault is registered. */\n configFile: string;\n /** Where the .mcp.json was written. */\n mcpJsonPath: string;\n /** Per-step transcript so callers can render a status report. */\n steps: AddVaultStep[];\n}\n\nconst DEFAULT_EXCLUDE_GLOBS = [\n \".obsidian/**\",\n \".trash/**\",\n \"Trash/**\",\n \".claude/**\", // vault-memory:claude-ok — `.claude/` is the literal Obsidian-side directory name for any MCP host integration; not a Claude-only path.\n \".smart-connections/**\",\n \".smart-env/**\",\n \".systemsculpt/**\",\n \".makemd/**\",\n];\n\n/**\n * Slugify a vault basename for use as a vault `name`:\n * - lowercase\n * - non-alnum (except dash) → dash\n * - collapse repeats, trim leading/trailing dashes\n *\n * Names must satisfy: ^[a-z0-9][a-z0-9-]*$ (becomes the SQLite DB filename).\n */\nexport function slugifyVaultName(input: string): string {\n const cleaned = input\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n if (cleaned.length === 0) return \"vault\";\n if (/^[0-9]/.test(cleaned)) return `v-${cleaned}`;\n return cleaned;\n}\n\nexport async function addVault(opts: AddVaultOptions): Promise<AddVaultResult> {\n const resolvedPath = resolve(opts.path);\n const cfgFile = opts.configFile ?? configPath();\n const binary = opts.binary ?? \"vault-memory\";\n const steps: AddVaultStep[] = [];\n\n // 1. Validate the vault path exists and is a directory.\n const stat = await fs.stat(resolvedPath).catch((err) => {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new Error(`Vault path does not exist: ${resolvedPath}`);\n }\n throw err;\n });\n if (!stat.isDirectory()) {\n throw new Error(`Vault path is not a directory: ${resolvedPath}`);\n }\n\n // 2. Determine the canonical name.\n const proposedName = opts.name ?? slugifyVaultName(basename(resolvedPath));\n if (!/^[a-z0-9][a-z0-9-]*$/.test(proposedName)) {\n throw new Error(\n `Vault name \"${proposedName}\" must match /^[a-z0-9][a-z0-9-]*$/ ` +\n `(lowercase alphanumeric + dashes, starting with a letter or digit).`,\n );\n }\n\n // 3. Read existing config to check for duplicates.\n const existing = await loadConfig(cfgFile);\n const sameName = existing.vaults.find((v) => v.name === proposedName);\n const samePath = existing.vaults.find((v) => resolve(v.path) === resolvedPath);\n\n if (samePath) {\n steps.push({\n kind: \"config-already-registered\",\n name: samePath.name,\n existingPath: samePath.path,\n });\n } else if (sameName) {\n throw new Error(\n `A different vault is already registered under name \"${proposedName}\" ` +\n `(path: ${sameName.path}). Pass --name <other> to choose a different one.`,\n );\n } else {\n // Append a new [[vaults]] block. We do not re-stringify the whole\n // config — that would discard user comments. Append-only is safer.\n const block = renderVaultBlock({\n name: proposedName,\n path: resolvedPath,\n writeEnabled: opts.writeEnabled ?? false,\n excludeGlobs: opts.excludeGlobs ?? DEFAULT_EXCLUDE_GLOBS,\n ...(opts.backend ? { backend: opts.backend } : {}),\n });\n await ensureFileExists(cfgFile);\n await appendToFile(cfgFile, block);\n steps.push({ kind: \"config-added\", name: proposedName, path: resolvedPath });\n }\n\n const finalName = samePath?.name ?? proposedName;\n\n // 4. Write/merge .mcp.json in the vault.\n const mcpPath = join(resolvedPath, \".mcp.json\");\n const step = await writeOrMergeMcpJson(mcpPath, finalName, binary);\n steps.push(step);\n\n return {\n name: finalName,\n resolvedPath,\n configFile: cfgFile,\n mcpJsonPath: mcpPath,\n steps,\n };\n}\n\ninterface VaultBlockInput {\n name: string;\n path: string;\n writeEnabled: boolean;\n excludeGlobs: string[];\n /** ADR-008: retrieval engine. Only emitted when \"contextfit\" (ollama is the\n * implicit default and is left out for back-compat clean configs). */\n backend?: \"ollama\" | \"contextfit\";\n}\n\nfunction renderVaultBlock(input: VaultBlockInput): string {\n // Hand-rolled TOML so we control formatting + comments.\n const lines: string[] = [\n \"\",\n `# Added by vault-memory add-vault on ${new Date().toISOString()}`,\n \"[[vaults]]\",\n `name = ${JSON.stringify(input.name)}`,\n `path = ${JSON.stringify(input.path)}`,\n ];\n if (input.backend === \"contextfit\") {\n lines.push(\n `# ADR-008: CPU-only, token-native engine (no Ollama/embeddings/GPU).`,\n `backend = \"contextfit\"`,\n );\n }\n lines.push(\n `write_enabled = ${input.writeEnabled}`,\n `exclude_globs = [`,\n ...input.excludeGlobs.map((g) => ` ${JSON.stringify(g)},`),\n `]`,\n \"\",\n );\n return lines.join(\"\\n\");\n}\n\nasync function ensureFileExists(path: string): Promise<void> {\n try {\n await fs.access(path);\n } catch {\n await fs.mkdir(join(homedir(), \".vault-memory\"), { recursive: true });\n await fs.writeFile(path, \"# vault-memory configuration\\n\", \"utf-8\");\n }\n}\n\nasync function appendToFile(path: string, content: string): Promise<void> {\n await fs.appendFile(path, content, \"utf-8\");\n}\n\ninterface McpServerEntry {\n type?: string;\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n}\ninterface McpJsonShape {\n mcpServers?: Record<string, McpServerEntry>;\n}\n\nasync function writeOrMergeMcpJson(\n mcpPath: string,\n vaultName: string,\n binary: string,\n): Promise<AddVaultStep> {\n const desiredEntry: McpServerEntry = {\n type: \"stdio\",\n command: binary,\n args: [\"serve\"],\n env: { VAULT_MEMORY_ACTIVE_VAULT: vaultName },\n };\n\n let existing: McpJsonShape | null = null;\n try {\n const raw = await fs.readFile(mcpPath, \"utf-8\");\n existing = JSON.parse(raw) as McpJsonShape;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n throw new Error(`Failed to read existing .mcp.json at ${mcpPath}: ${(err as Error).message}`);\n }\n }\n\n if (existing === null) {\n const fresh: McpJsonShape = { mcpServers: { \"vault-memory\": desiredEntry } };\n await fs.writeFile(mcpPath, JSON.stringify(fresh, null, 2) + \"\\n\", \"utf-8\");\n return { kind: \"mcp-json-created\", mcpPath };\n }\n\n // Merge: keep other servers untouched, replace/insert vault-memory.\n const before = existing.mcpServers?.[\"vault-memory\"];\n const beforeJson = before ? JSON.stringify(before) : null;\n const merged: McpJsonShape = {\n ...existing,\n mcpServers: {\n ...(existing.mcpServers ?? {}),\n \"vault-memory\": desiredEntry,\n },\n };\n const afterJson = JSON.stringify(merged.mcpServers?.[\"vault-memory\"]);\n if (beforeJson === afterJson) {\n return { kind: \"mcp-json-unchanged\", mcpPath };\n }\n await fs.writeFile(mcpPath, JSON.stringify(merged, null, 2) + \"\\n\", \"utf-8\");\n return { kind: \"mcp-json-merged\", mcpPath };\n}\n","export { loadConfig, configPath } from \"./loader.js\";\nexport { addVault, slugifyVaultName } from \"./add-vault.js\";\nexport type { AddVaultOptions, AddVaultResult, AddVaultStep } from \"./add-vault.js\";\n","/**\n * RuntimeConfigStore — Phase 7 / Plan 07-04 / PLG-01, ADR-007 §D-CHROME-SETTINGS.\n *\n * In-memory mirror of selected `AppConfig` knobs that can be hot-swapped at\n * runtime without restarting the server. The CONFIG FILE\n * (`~/.vault-memory/config.toml`) remains the authoritative source of record\n * across restarts — this store is intentionally NOT persisted. Restarting the\n * server reverts every hot-swap to the on-disk value.\n *\n * Closed enum of hot-swappable keys (RESEARCH Open Q #1, RESOLVED):\n * - reranker_enabled (boolean) — toggles `vault.config` rerank gate in-memory\n * - default_vault (string) — overrides `VAULT_MEMORY_ACTIVE_VAULT`\n * - indexer_batch_size (number) — informational; consulted by next indexVault call\n *\n * Restart-required keys are surfaced via `RESTART_REQUIRED_KEYS` and produce\n * a structured `{ok: false, reason: \"restart_required\", key}` response in\n * the `set_runtime_config` tool — no mutation occurs.\n *\n * # Adapter-seam discipline\n *\n * Pure in-memory key-value store. Zero `fs` / `path` / `yaml` / `chokidar`\n * imports. Zod schemas live in the consuming tool file; this module is just\n * the store.\n */\n\nexport const HOT_SWAPPABLE_KEYS = [\n \"reranker_enabled\",\n \"default_vault\",\n \"indexer_batch_size\",\n] as const;\n\nexport type HotSwappableKey = (typeof HOT_SWAPPABLE_KEYS)[number];\n\nexport const RESTART_REQUIRED_KEYS = [\"ollama_url\", \"embedding_model\", \"fts_tokenizer\"] as const;\n\nexport type RestartRequiredKey = (typeof RESTART_REQUIRED_KEYS)[number];\n\nexport type RuntimeConfigValue = boolean | string | number;\n\nexport interface RuntimeConfigSnapshot {\n reranker_enabled?: boolean;\n default_vault?: string;\n indexer_batch_size?: number;\n}\n\n/**\n * In-memory store. The owning module (typically `src/server.ts` bootstrap)\n * constructs ONE instance, seeds it with the initial on-disk values, and\n * threads it into each tool handler's dependency bag.\n */\nexport class RuntimeConfigStore {\n private values: RuntimeConfigSnapshot;\n\n constructor(initial?: RuntimeConfigSnapshot) {\n this.values = { ...(initial ?? {}) };\n }\n\n /** Read a single hot-swappable value, or `undefined` if never set. */\n get<K extends HotSwappableKey>(key: K): RuntimeConfigSnapshot[K] {\n return this.values[key];\n }\n\n /** Read the full snapshot (immutable copy). */\n snapshot(): RuntimeConfigSnapshot {\n return { ...this.values };\n }\n\n /** Write a hot-swappable value. Caller is responsible for type validation. */\n set<K extends HotSwappableKey>(key: K, value: RuntimeConfigSnapshot[K]): void {\n this.values[key] = value;\n }\n}\n\n/** True iff `key` is in the closed hot-swappable enum. */\nexport function isHotSwappableKey(key: string): key is HotSwappableKey {\n return (HOT_SWAPPABLE_KEYS as readonly string[]).includes(key);\n}\n\n/** True iff `key` is in the closed restart-required enum. */\nexport function isRestartRequiredKey(key: string): key is RestartRequiredKey {\n return (RESTART_REQUIRED_KEYS as readonly string[]).includes(key);\n}\n","/**\n * set_runtime_config — Phase 7 / Plan 07-04 / PLG-01, ADR-007 §D-CHROME-SETTINGS.\n *\n * Per-key runtime settings tool. Applies hot-swappable settings to the\n * in-memory `RuntimeConfigStore` ONLY — the on-disk `~/.vault-memory/config.toml`\n * is authoritative across restarts and is never mutated by this tool. Server\n * restart reverts hot-swaps to the file values (this is intentional; see PLG-01\n * §\"Hot-swap semantics\").\n *\n * Closed enum of allowed keys (RESEARCH Open Q #1, RESOLVED):\n * - reranker_enabled (boolean)\n * - default_vault (string)\n * - indexer_batch_size (number, positive integer)\n *\n * Restart-required keys (`ollama_url`, `embedding_model`, `fts_tokenizer`)\n * return `{ok: false, reason: \"restart_required\", key}` without mutating.\n * Unknown keys return `{ok: false, reason: \"unknown_key\", key}`.\n *\n * # Adapter-seam discipline\n *\n * Imports only `zod` + sibling `runtime-config.js` / `errors.js`. Zero `fs`,\n * `path`, `yaml`, `chokidar`, MCP SDK. The MCP SDK wiring happens in\n * `src/plugin-tools/index.ts`.\n */\n\nimport { z } from \"zod\";\nimport {\n RuntimeConfigStore,\n HOT_SWAPPABLE_KEYS,\n isHotSwappableKey,\n isRestartRequiredKey,\n} from \"./runtime-config.js\";\n\nconst SetRuntimeConfigArgs = z.object({\n key: z\n .string()\n .min(1)\n .describe(\n \"Closed enum of hot-swappable keys: \" +\n `${HOT_SWAPPABLE_KEYS.join(\", \")}. Restart-required keys ` +\n \"(ollama_url, embedding_model, fts_tokenizer) return reason='restart_required'.\",\n ),\n value: z\n .union([z.boolean(), z.string(), z.number()])\n .describe(\n \"New value. Type must match the key: reranker_enabled = boolean, \" +\n \"default_vault = string, indexer_batch_size = positive integer.\",\n ),\n});\n\nexport type SetRuntimeConfigInput = z.infer<typeof SetRuntimeConfigArgs>;\n\nexport interface SetRuntimeConfigDeps {\n store: RuntimeConfigStore;\n}\n\nexport type SetRuntimeConfigResult =\n | { ok: true; key: string; value: boolean | string | number }\n | { ok: false; reason: \"unknown_key\"; key: string }\n | { ok: false; reason: \"restart_required\"; key: string }\n | { ok: false; reason: \"type_mismatch\"; key: string; expected: string };\n\nasync function handler(\n args: SetRuntimeConfigInput,\n deps: SetRuntimeConfigDeps,\n): Promise<SetRuntimeConfigResult> {\n const { key, value } = args;\n\n if (isRestartRequiredKey(key)) {\n return { ok: false, reason: \"restart_required\", key };\n }\n if (!isHotSwappableKey(key)) {\n return { ok: false, reason: \"unknown_key\", key };\n }\n\n // Per-key type-narrow validation. Zod already constrained `value` to\n // boolean | string | number; this layer enforces the per-key expected\n // type (e.g. reranker_enabled must be boolean, not \"true\" string).\n switch (key) {\n case \"reranker_enabled\": {\n if (typeof value !== \"boolean\") {\n return { ok: false, reason: \"type_mismatch\", key, expected: \"boolean\" };\n }\n deps.store.set(\"reranker_enabled\", value);\n return { ok: true, key, value };\n }\n case \"default_vault\": {\n if (typeof value !== \"string\") {\n return { ok: false, reason: \"type_mismatch\", key, expected: \"string\" };\n }\n deps.store.set(\"default_vault\", value);\n return { ok: true, key, value };\n }\n case \"indexer_batch_size\": {\n if (typeof value !== \"number\" || !Number.isInteger(value) || value <= 0) {\n return {\n ok: false,\n reason: \"type_mismatch\",\n key,\n expected: \"positive integer\",\n };\n }\n deps.store.set(\"indexer_batch_size\", value);\n return { ok: true, key, value };\n }\n }\n}\n\nexport const setRuntimeConfigTool = {\n name: \"set_runtime_config\" as const,\n description:\n \"Apply a hot-swappable runtime config key (in-memory only — config.toml \" +\n \"remains authoritative across restarts). Closed enum of keys: \" +\n `${HOT_SWAPPABLE_KEYS.join(\", \")}. ADR-007 §D-CHROME-SETTINGS.`,\n inputSchema: SetRuntimeConfigArgs,\n handler,\n};\n","/**\n * resolve_secret — Phase 7 / Plan 07-04 / PLG-02, ADR-007 §D-CHROME-SECRETS.\n *\n * Receives plaintext from the plugin (which decrypted it via Electron\n * `safeStorage.decryptString(...)` inside the Obsidian renderer process) and\n * makes it available to the server-side `${secret:name}` substitution layer.\n *\n * Architectural rationale (RESEARCH §\"Architectural Responsibility Map\"):\n * `safeStorage` is an Electron-renderer API only reachable inside the\n * Obsidian process. The plugin owns ciphertext storage in `data.json` (per-\n * device ciphertext is the correct security posture per CONTEXT\n * D-CHROME-SECRETS); the server tool merely consumes the plaintext for\n * substitution and never logs it.\n *\n * Input shape:\n * {name: string, ciphertext: string} — plugin succeeded; field\n * carries plaintext-of-this-call\n * {name: string, error: \"safe_storage_unavailable\" | \"decrypt_failed\"}\n * — plugin reports decryption failure\n *\n * Output:\n * {ok: true, plaintext: string} — success\n * {ok: false, reason: \"safe_storage_unavailable\", name} — OS keyring missing\n * {ok: false, reason: \"decrypt_failed\", name} — other failure\n *\n * SECURITY: response payload contains plaintext only — handler MUST NOT\n * include `name` in any log line at level >= info; debug-level logging must\n * redact the plaintext. Source-file scan in `resolve-secret.test.ts` enforces\n * that no logging statement references the secret value.\n *\n * # Adapter-seam discipline\n *\n * Imports only `zod` + sibling `errors.js`. Zero `fs`, `path`, `yaml`,\n * `chokidar`, MCP SDK, Electron.\n */\n\nimport { z } from \"zod\";\n\n/**\n * Raw object shape (no `.refine`). Exposed separately so the MCP SDK\n * `registerTool(..., {inputSchema: ResolveSecretShape})` accepts a\n * ZodRawShapeCompat instead of a `ZodEffects` (which the SDK rejects).\n * The refined schema (`ResolveSecretArgs`) layers a cross-field check on\n * top and is used inside the handler for runtime validation.\n */\nexport const ResolveSecretShape = {\n name: z\n .string()\n .min(1)\n .describe(\"Secret identifier referenced as `${secret:name}` in a contract.\"),\n ciphertext: z\n .string()\n .optional()\n .describe(\n \"Plaintext-of-this-call (the plugin has already decrypted ciphertext \" +\n \"in-process via safeStorage). Field name preserved for provenance.\",\n ),\n error: z\n .enum([\"safe_storage_unavailable\", \"decrypt_failed\"])\n .optional()\n .describe(\n \"Plugin-side failure indicator. `safe_storage_unavailable` means \" +\n \"the OS keyring backend was missing; `decrypt_failed` covers any \" +\n \"other plugin-side decryption failure.\",\n ),\n} as const;\n\nconst ResolveSecretArgs = z\n .object(ResolveSecretShape)\n .refine((v) => v.ciphertext !== undefined || v.error !== undefined, {\n message: \"must provide either `ciphertext` or `error`\",\n });\n\nexport type ResolveSecretInput = z.infer<typeof ResolveSecretArgs>;\n\nexport type ResolveSecretResult =\n | { ok: true; plaintext: string }\n | { ok: false; reason: \"safe_storage_unavailable\"; name: string }\n | { ok: false; reason: \"decrypt_failed\"; name: string };\n\nasync function handler(args: ResolveSecretInput): Promise<ResolveSecretResult> {\n if (args.error !== undefined) {\n return { ok: false, reason: args.error, name: args.name };\n }\n if (args.ciphertext === undefined) {\n // Defensive: Zod refine should have caught this; preserve a typed\n // fall-through so the discriminated-union remains exhaustive.\n return { ok: false, reason: \"decrypt_failed\", name: args.name };\n }\n // SECURITY: do not log or stringify `args.ciphertext` here.\n return { ok: true, plaintext: args.ciphertext };\n}\n\nexport const resolveSecretTool = {\n name: \"resolve_secret\" as const,\n description:\n \"Resolve a secret to plaintext for ${secret:name} substitution. The plugin \" +\n \"decrypts ciphertext in-process via Electron safeStorage; this tool consumes \" +\n \"the plaintext and never logs it. ADR-007 §D-CHROME-SECRETS.\",\n inputSchema: ResolveSecretArgs,\n handler,\n};\n","/**\n * set_mcp_client — Phase 7 / Plan 07-04 / PLG-05, ADR-007 §D-CHROME-CONNECTORS.\n *\n * CRUD for `[contracts.mcp_clients.<name>]` blocks in\n * `~/.vault-memory/config.toml`. Discriminated-union input:\n *\n * Variant A (add/update): {name, command, args?, env_secrets?}\n * - mutates [contracts.mcp_clients.<name>]; idempotent\n * - returns {ok: true, name, action: \"added\" | \"updated\"}\n *\n * Variant B (remove): {name, remove: true}\n * - deletes the entry; idempotent (no-op if absent)\n * - returns {ok: true, name, action: \"removed\"}\n *\n * Variant C (list): {list: true}\n * - reads inventory; returns key-list of env_secrets (no values)\n * - returns {ok: true, clients: Array<{name, command, args, env_secrets, status?}>}\n *\n * In the list response, `env_secrets` is a key-list ONLY (no values, no\n * ciphertext) — values stay in plugin storage; the server only knows the key\n * names that will be substituted at connect time.\n *\n * # Adapter-seam discipline\n *\n * Imports `zod`, `smol-toml`, and node:fs/promises. The config-file mutator\n * is the ONLY plugin-tool that writes to `~/.vault-memory/config.toml`;\n * justified by D-CHROME-CONNECTORS (the connector list is the user-visible\n * source of truth, hot-swap would orphan running peer-MCP clients).\n */\n\nimport { z } from \"zod\";\nimport { parse as parseToml, stringify as stringifyToml } from \"smol-toml\";\nimport { readFile, writeFile } from \"node:fs/promises\";\n\n/**\n * Raw object shape exposed to the MCP SDK (`registerTool({inputSchema})`).\n * The SDK 1.29 input-schema slot accepts a `ZodRawShapeCompat` — a plain\n * object whose properties are Zod schemas. We can't directly hand it a\n * `z.union(...)` because the discriminator decision is per-call, so the\n * shape is union-relaxed: every field is optional at the schema level and\n * the cross-field invariant is enforced by the refined union below\n * (`SetMcpClientArgs`) which the handler re-parses with.\n */\nexport const SetMcpClientShape = {\n name: z.string().min(1).optional().describe(\"Client name (required for Variants A and B).\"),\n command: z\n .string()\n .min(1)\n .optional()\n .describe(\"Executable path. Required for Variant A (add/update).\"),\n args: z.array(z.string()).optional().describe(\"Argv tail for child_process.spawn (Variant A).\"),\n env_secrets: z\n .record(z.string(), z.string())\n .optional()\n .describe(\n \"Map of ENV_NAME → secret-key-name (Variant A). Values resolved via \" +\n \"resolve_secret at connect time; this map carries key names only.\",\n ),\n remove: z\n .literal(true)\n .optional()\n .describe(\"Variant B trigger — set true together with `name` to delete.\"),\n list: z\n .literal(true)\n .optional()\n .describe(\"Variant C trigger — set true to read [contracts.mcp_clients] inventory.\"),\n} as const;\n\nconst SetMcpClientArgs = z.union([\n // Variant A — add/update\n z.object({\n name: z.string().min(1).describe(\"Peer-MCP client name (used as TOML table key).\"),\n command: z\n .string()\n .min(1)\n .describe(\"Executable path. Same trust scope as ~/.vault-memory/config.toml.\"),\n args: z.array(z.string()).optional().describe(\"Argv tail for child_process.spawn.\"),\n env_secrets: z\n .record(z.string(), z.string())\n .optional()\n .describe(\n \"Map of ENV_NAME → secret-key-name. Values are looked up via \" +\n \"resolve_secret at connect time; this map carries key names only.\",\n ),\n }),\n // Variant B — remove\n z.object({\n name: z.string().min(1).describe(\"Client name to remove.\"),\n remove: z.literal(true).describe(\"Set to true to delete the entry.\"),\n }),\n // Variant C — list (inventory)\n z.object({\n list: z.literal(true).describe(\"Set to true to read [contracts.mcp_clients] inventory.\"),\n }),\n]);\n\nexport type SetMcpClientInput = z.infer<typeof SetMcpClientArgs>;\n\nexport interface SetMcpClientDeps {\n /** Path to config.toml. Defaults to `~/.vault-memory/config.toml`. */\n configPath: string;\n}\n\nexport interface McpClientInventoryEntry {\n name: string;\n command: string;\n args: string[];\n env_secrets: string[];\n status?: \"connected\" | \"disconnected\" | \"untested\";\n}\n\nexport type SetMcpClientResult =\n | { ok: true; name: string; action: \"added\" | \"updated\" | \"removed\" }\n | { ok: true; clients: McpClientInventoryEntry[] };\n\ntype TomlRoot = Record<string, unknown> & {\n contracts?: { mcp_clients?: Record<string, McpClientTomlEntry> } & Record<string, unknown>;\n};\n\ninterface McpClientTomlEntry {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n env_secrets?: Record<string, string>;\n}\n\nasync function readConfig(configPath: string): Promise<TomlRoot> {\n try {\n const raw = await readFile(configPath, \"utf-8\");\n return parseToml(raw) as TomlRoot;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") return {};\n throw err;\n }\n}\n\nasync function writeConfig(configPath: string, root: TomlRoot): Promise<void> {\n // smol-toml stringify is total over JSON-serializable values; the round-trip\n // here is parse → mutate → stringify, which preserves field types (TOML\n // strings stay strings, booleans stay booleans, integers stay integers).\n // Comments and blank lines are NOT preserved — this is documented in the\n // ADR-007 threat model under \"TOML round-trip side effects\".\n await writeFile(configPath, stringifyToml(root), \"utf-8\");\n}\n\nasync function handler(\n args: SetMcpClientInput,\n deps: SetMcpClientDeps,\n): Promise<SetMcpClientResult> {\n // Variant C — list\n if (\"list\" in args) {\n const root = await readConfig(deps.configPath);\n const map = root.contracts?.mcp_clients ?? {};\n const clients: McpClientInventoryEntry[] = Object.entries(map).map(([name, entry]) => ({\n name,\n command: entry.command ?? \"\",\n args: entry.args ?? [],\n // SECURITY: emit key-list only — values stay in plugin storage.\n env_secrets: Object.keys(entry.env_secrets ?? {}),\n }));\n return { ok: true, clients };\n }\n\n const root = await readConfig(deps.configPath);\n if (root.contracts === undefined) root.contracts = {};\n // We control the shape; cast to a mutable record for the local mutation.\n const contracts = root.contracts as { mcp_clients?: Record<string, McpClientTomlEntry> };\n if (contracts.mcp_clients === undefined) contracts.mcp_clients = {};\n const clients = contracts.mcp_clients;\n\n // Variant B — remove\n if (\"remove\" in args) {\n if (args.name in clients) {\n delete clients[args.name];\n await writeConfig(deps.configPath, root);\n } else {\n // Idempotent — nothing to write, but still report success.\n }\n return { ok: true, name: args.name, action: \"removed\" };\n }\n\n // Variant A — add/update\n const existing = clients[args.name];\n const entry: McpClientTomlEntry = {\n command: args.command,\n };\n if (args.args !== undefined) entry.args = args.args;\n if (args.env_secrets !== undefined) entry.env_secrets = args.env_secrets;\n clients[args.name] = entry;\n await writeConfig(deps.configPath, root);\n return {\n ok: true,\n name: args.name,\n action: existing === undefined ? \"added\" : \"updated\",\n };\n}\n\nexport const setMcpClientTool = {\n name: \"set_mcp_client\" as const,\n description:\n \"Manage [contracts.mcp_clients] in ~/.vault-memory/config.toml. \" +\n \"Variant A: add/update (name + command [+ args, env_secrets]). \" +\n \"Variant B: remove (name + remove:true). \" +\n \"Variant C: list (list:true — inventory, env_secrets is key-list only). \" +\n \"ADR-007 §D-CHROME-CONNECTORS.\",\n inputSchema: SetMcpClientArgs,\n handler,\n};\n","/**\n * get_runtime_stats — Phase 7 / Plan 07-04 / PLG-04, ADR-007 §D-CHROME-STATS.\n *\n * Read-only per-vault stats aggregation for the chrome stats panel.\n *\n * Input: {vault?: string}\n * Output: {\n * vault, notes, chunks, last_index_at, embedding_model, embedding_dim,\n * audit_log_by_kind: Record<op, count>,\n * peer_mcp_status: Array<{name, available}>,\n * contract_count\n * }\n *\n * `vault` defaults to the single registered vault when only one exists, or\n * is required when multiple are configured (callers receive\n * {ok: false, reason: \"unknown_vault\", vault}). Reads via existing query\n * layers — no new DB statements.\n *\n * # Adapter-seam discipline\n *\n * Imports `zod` only. Deps are threaded via dependency injection so the\n * tool is unit-testable without booting a real VaultManager.\n */\n\nimport { z } from \"zod\";\n\nconst GetRuntimeStatsArgs = z.object({\n vault: z\n .string()\n .min(1)\n .optional()\n .describe(\"Vault name. Defaults to the only registered vault when N=1.\"),\n});\n\nexport type GetRuntimeStatsInput = z.infer<typeof GetRuntimeStatsArgs>;\n\n/**\n * Minimal vault facade used by the tool. Real callers pass the live\n * `Vault` struct from `src/vault/manager.ts`; tests pass a fake conforming\n * to this shape.\n */\nexport interface StatsVault {\n config: { name: string; embedding_model?: string };\n db: {\n notes: { countAll: () => number };\n audit: {\n listRuns: (limit: number) => Array<{\n run_id: string;\n started_at: number;\n finished_at: number | null;\n }>;\n listWrites: (filter: { limit?: number }) => Array<{ op: string }>;\n };\n models: { getActive: () => { name: string; dim: number } | null };\n handle: {\n prepare: <T>(sql: string) => { get: (...args: unknown[]) => T };\n };\n };\n}\n\nexport interface GetRuntimeStatsDeps {\n listVaults: () => StatsVault[];\n peerMcpStatus: () => Array<{ name: string; available: boolean }>;\n contractCountFor: (vault: string) => number;\n}\n\nexport type GetRuntimeStatsResult =\n | {\n vault: string;\n notes: number;\n chunks: number;\n last_index_at: number | null;\n embedding_model: string;\n embedding_dim: number;\n audit_log_by_kind: Record<string, number>;\n peer_mcp_status: Array<{ name: string; available: boolean }>;\n contract_count: number;\n }\n | { ok: false; reason: \"unknown_vault\"; vault: string }\n | { ok: false; reason: \"ambiguous_vault\"; available_vaults: string[] };\n\nfunction resolveVault(\n arg: string | undefined,\n vaults: StatsVault[],\n):\n | StatsVault\n | { reason: \"unknown_vault\" | \"ambiguous_vault\"; vault?: string; available_vaults?: string[] } {\n if (arg !== undefined) {\n const v = vaults.find((vt) => vt.config.name === arg);\n if (v === undefined) return { reason: \"unknown_vault\", vault: arg };\n return v;\n }\n if (vaults.length === 0) return { reason: \"unknown_vault\", vault: \"(none)\" };\n if (vaults.length > 1) {\n return {\n reason: \"ambiguous_vault\",\n available_vaults: vaults.map((v) => v.config.name),\n };\n }\n return vaults[0]!;\n}\n\nasync function handler(\n args: GetRuntimeStatsInput,\n deps: GetRuntimeStatsDeps,\n): Promise<GetRuntimeStatsResult> {\n const vaults = deps.listVaults();\n const resolved = resolveVault(args.vault, vaults);\n if (\"reason\" in resolved) {\n if (resolved.reason === \"unknown_vault\") {\n return { ok: false, reason: \"unknown_vault\", vault: resolved.vault ?? args.vault ?? \"\" };\n }\n return {\n ok: false,\n reason: \"ambiguous_vault\",\n available_vaults: resolved.available_vaults ?? [],\n };\n }\n\n const vault = resolved;\n const notes = vault.db.notes.countAll();\n // No `countAll` on ChunksQueries — execute a raw COUNT via the SQLite handle.\n const chunksRow = vault.db.handle\n .prepare<{ c: number }>(\"SELECT COUNT(*) AS c FROM chunks\")\n .get();\n const chunks = chunksRow?.c ?? 0;\n\n const runs = vault.db.audit.listRuns(1);\n const lastRun = runs[0];\n const last_index_at = lastRun?.finished_at ?? null;\n\n const activeModel = vault.db.models.getActive();\n const embedding_model = activeModel?.name ?? vault.config.embedding_model ?? \"\";\n const embedding_dim = activeModel?.dim ?? 0;\n\n // Aggregate the most recent write-audit rows by op. The 1000 cap mirrors\n // the audit_log MCP tool's default — bounded to keep this read cheap.\n const writes = vault.db.audit.listWrites({ limit: 1000 });\n const audit_log_by_kind: Record<string, number> = {};\n for (const w of writes) {\n audit_log_by_kind[w.op] = (audit_log_by_kind[w.op] ?? 0) + 1;\n }\n\n return {\n vault: vault.config.name,\n notes,\n chunks,\n last_index_at,\n embedding_model,\n embedding_dim,\n audit_log_by_kind,\n peer_mcp_status: deps.peerMcpStatus(),\n contract_count: deps.contractCountFor(vault.config.name),\n };\n}\n\nexport const getRuntimeStatsTool = {\n name: \"get_runtime_stats\" as const,\n description:\n \"Per-vault stats for the chrome stats panel: notes, chunks, last_index_at, \" +\n \"embedding model+dim, audit_log_by_kind, peer_mcp_status, contract_count. \" +\n \"Read-only. ADR-007 §D-CHROME-STATS.\",\n inputSchema: GetRuntimeStatsArgs,\n handler,\n};\n","/**\n * trigger_reindex — Phase 7 / Plan 07-04 / PLG-03, ADR-007 §D-CHROME-REINDEX.\n *\n * Triggers a full or per-vault reindex via the injected `reindexVault`\n * callback (which wraps the existing `indexVault` entry point). When the\n * caller supplies a `progressToken`, the handler emits\n * `notifications/progress` updates via the injected `notifier` so the plugin\n * UI can render progress.\n *\n * Input: {scope: \"this\" | \"all\", vault?: string, progressToken?: string}\n * Output: {ok: true, vaults: string[]} after all triggered vaults finish.\n *\n * # Adapter-seam discipline\n *\n * Imports `zod` only. The `indexVault` call is threaded via dependency\n * injection so this tool is unit-testable without booting a real Ollama\n * client or VaultManager.\n */\n\nimport { z } from \"zod\";\n\nconst TriggerReindexArgs = z.object({\n scope: z\n .enum([\"this\", \"all\"])\n .describe(\"'this' reindexes the named vault; 'all' reindexes every registered vault.\"),\n vault: z\n .string()\n .min(1)\n .optional()\n .describe(\n \"Required when scope='this' AND more than one vault is registered; \" +\n \"defaults to the single registered vault otherwise.\",\n ),\n progressToken: z\n .string()\n .min(1)\n .optional()\n .describe(\"MCP SDK 1.29 progressToken — when set, emits notifications/progress.\"),\n});\n\nexport type TriggerReindexInput = z.infer<typeof TriggerReindexArgs>;\n\nexport interface ReindexVault {\n config: { name: string };\n}\n\nexport interface TriggerReindexProgress {\n progress: number;\n total?: number;\n}\n\nexport interface TriggerReindexDeps {\n listVaults: () => ReindexVault[];\n /**\n * Reindex one vault. The `onProgress` callback receives raw counts; the\n * tool layer translates those into MCP notifications/progress when a\n * progressToken is set.\n */\n reindexVault: (\n vaultName: string,\n onProgress?: (p: TriggerReindexProgress) => void,\n ) => Promise<void>;\n /**\n * MCP SDK notification injector. Real callers pass\n * `server.server.notification.bind(server.server)`; tests pass a vi.fn().\n */\n notifier: (notification: {\n method: \"notifications/progress\";\n params: { progressToken: string; progress: number; total?: number };\n }) => void;\n}\n\nexport type TriggerReindexResult =\n | { ok: true; vaults: string[] }\n | { ok: false; reason: \"unknown_vault\"; vault: string }\n | { ok: false; reason: \"ambiguous_vault\"; available_vaults: string[] };\n\nasync function handler(\n args: TriggerReindexInput,\n deps: TriggerReindexDeps,\n): Promise<TriggerReindexResult> {\n const allVaults = deps.listVaults().map((v) => v.config.name);\n\n // Resolve target vaults\n let targets: string[];\n if (args.scope === \"all\") {\n targets = allVaults;\n } else {\n // scope === \"this\"\n if (args.vault !== undefined) {\n if (!allVaults.includes(args.vault)) {\n return { ok: false, reason: \"unknown_vault\", vault: args.vault };\n }\n targets = [args.vault];\n } else if (allVaults.length === 1) {\n targets = [allVaults[0]!];\n } else if (allVaults.length === 0) {\n return { ok: false, reason: \"unknown_vault\", vault: \"(none)\" };\n } else {\n return { ok: false, reason: \"ambiguous_vault\", available_vaults: allVaults };\n }\n }\n\n // Run reindex per-target. Progress notifications are emitted only when a\n // progressToken was supplied; otherwise onProgress is undefined and the\n // indexer runs silently (matching the existing CLI behavior).\n const token = args.progressToken;\n for (const vname of targets) {\n const onProgress =\n token !== undefined\n ? (p: TriggerReindexProgress) => {\n deps.notifier({\n method: \"notifications/progress\",\n params:\n token !== undefined && p.total !== undefined\n ? { progressToken: token, progress: p.progress, total: p.total }\n : { progressToken: token!, progress: p.progress },\n });\n }\n : undefined;\n await deps.reindexVault(vname, onProgress);\n }\n\n return { ok: true, vaults: targets };\n}\n\nexport const triggerReindexTool = {\n name: \"trigger_reindex\" as const,\n description:\n \"Trigger a full vault reindex with optional progress notifications. \" +\n \"scope='this' reindexes one vault; scope='all' reindexes every registered vault. \" +\n \"Supply a progressToken to receive notifications/progress updates. \" +\n \"ADR-007 §D-CHROME-REINDEX.\",\n inputSchema: TriggerReindexArgs,\n handler,\n};\n","/**\n * suppress_contract_write — Phase 7 / Plan 07-07 / CAN-08, ADR-007 §D-WATCH-PLUGIN-OUT.\n *\n * Plugin-control MCP tool. Called by the contract editor's\n * `emitYamlCompanion` BEFORE every `.yaml` companion write so the\n * Phase 6 ContractRegistry ChangeFeed handler can recognize the\n * resulting filesystem event as \"our own echo\" and drop it silently.\n *\n * Workflow:\n * 1. Plugin computes `yamlBody = emitYaml(file)` via the 07-02 codec.\n * 2. Plugin computes `hash = sha256(yamlBody)` (SubtleCrypto in the\n * renderer process).\n * 3. Plugin calls THIS tool with {path, hash}.\n * 4. Plugin writes the YAML via `app.vault.adapter.write(...)`.\n * 5. ChangeFeed observes the write → loader.ts hashes the on-disk\n * body → `SuppressionSet.consume(path, hash)` returns true (match)\n * → reload is skipped.\n *\n * # Input validation (THREAT-T-07-07-01 mitigation)\n *\n * - `path`: must match `^_contracts/[^/]+\\.yaml$` (non-recursive,\n * Pitfall F3-aligned with the loader's `CONTRACT_PATH_REGEX`).\n * - `hash`: 64-char lowercase hex (SHA-256 digest format).\n * - `ttl_ms`: bounded 200..30_000 (defends THREAT-T-07-07-02 — a\n * too-long TTL could swallow a legitimate later edit).\n *\n * Invalid paths return a structured `{ok: false, reason: \"invalid_path\"}`\n * result without registering a suppression entry. Zod schema failures\n * surface as exceptions caught by `syncPluginTools`'s wrapper and\n * returned as `isError: true` MCP responses.\n *\n * # Plugin-gating\n *\n * Like the other 5 plugin-control tools, this one only registers when\n * `[plugin] enabled = true` (D-MCP-SURFACE). The v1-baseline tools-list\n * snapshot stays byte-identical under the default-OFF gate.\n *\n * # Adapter-seam discipline\n *\n * Imports only `zod` + the sibling `SuppressionSet` type. Zero `fs`,\n * `path`, `yaml`, `chokidar`, MCP SDK.\n */\n\nimport { z } from \"zod\";\nimport type { SuppressionSet } from \"../adapters/change-feed/obsidian-fs/suppression.js\";\n\n/**\n * `^_contracts/<name>.yaml$` — non-recursive, matches the loader's\n * `CONTRACT_PATH_REGEX` (Pitfall F3). Tools written for a contract\n * outside this shape are rejected with `invalid_path` rather than\n * silently registering a useless suppression entry.\n */\nconst CONTRACT_PATH_REGEX = /^_contracts\\/[^/]+\\.yaml$/;\n\nconst SuppressContractWriteArgs = z.object({\n path: z\n .string()\n .min(1)\n .describe(\n \"Vault-relative path of the YAML companion (e.g. `_contracts/foo.yaml`). \" +\n \"Non-recursive — `_contracts/sub/foo.yaml` is rejected with invalid_path.\",\n ),\n hash: z\n .string()\n .regex(/^[0-9a-f]{64}$/, \"must be 64-char lowercase hex (SHA-256)\")\n .describe(\n \"SHA-256 of the YAML body the plugin is about to write. Used by the \" +\n \"ChangeFeed handler to distinguish echo events from real external edits.\",\n ),\n ttl_ms: z\n .number()\n .int()\n .min(200)\n .max(30_000)\n .optional()\n .describe(\n \"Suppression entry TTL in ms (default 2000). Bounded 200..30000 to \" +\n \"defend against an over-long entry swallowing a legitimate later edit.\",\n ),\n});\n\nexport type SuppressContractWriteInput = z.infer<typeof SuppressContractWriteArgs>;\n\nexport interface SuppressContractWriteDeps {\n suppression: SuppressionSet;\n}\n\nexport type SuppressContractWriteResult =\n | { ok: true }\n | { ok: false; reason: \"invalid_path\"; path: string };\n\nasync function handler(\n args: SuppressContractWriteInput,\n deps: SuppressContractWriteDeps,\n): Promise<SuppressContractWriteResult> {\n const { path, hash, ttl_ms } = args;\n\n if (!CONTRACT_PATH_REGEX.test(path)) {\n return { ok: false, reason: \"invalid_path\", path };\n }\n\n deps.suppression.add(path, { hash, ttlMs: ttl_ms ?? 2000 });\n return { ok: true };\n}\n\nexport const suppressContractWriteTool = {\n name: \"suppress_contract_write\" as const,\n description:\n \"Register a hash-keyed suppression entry for an upcoming `.yaml` \" +\n \"companion write. The Phase 6 ContractRegistry ChangeFeed handler \" +\n \"uses this to distinguish plugin-driven echoes from external edits \" +\n \"(CAN-08 D-WATCH-PLUGIN-OUT). Plugin must call BEFORE writing.\",\n inputSchema: SuppressContractWriteArgs,\n handler,\n};\n","/**\n * unset_mcp_client + refresh_source — SOURCES-REGISTRY.md §6 (Stage 2).\n *\n * Two plugin-gated tools that operate on the LIVE PeerMcpRegistry (not\n * config.toml). They complement `set_mcp_client`, which mutates the\n * persisted config:\n *\n * - refresh_source({name}) — re-issue tools/list against the live peer\n * and refresh the cache. Returns the updated status + tool_count.\n *\n * - unset_mcp_client({name}) — dispose the live client and drop it from\n * the registry. Idempotent. NOTE: this affects the running process\n * only; to also remove the persisted entry, the caller pairs this\n * with `set_mcp_client({name, remove:true})`.\n *\n * # Adapter-seam discipline\n *\n * Imports `zod` only. The registry is threaded via a minimal facade so\n * the tools are unit-testable without spawning peers.\n */\n\nimport { z } from \"zod\";\nimport type { PeerMcpStatus } from \"../contracts/mcp-clients.js\";\n\n/**\n * Minimal live-registry facade the tools depend on. The real caller\n * passes the singleton `PeerMcpRegistry`; tests pass a fake.\n */\nexport interface SourceRegistryFacade {\n refresh(\n name: string,\n ): Promise<{ status: PeerMcpStatus; tools: readonly unknown[]; error?: string } | undefined>;\n remove(name: string): boolean;\n}\n\n// ─── refresh_source ─────────────────────────────────────────────────────\n\nconst RefreshSourceArgs = z.object({\n name: z.string().min(1).describe(\"Peer-MCP source name to refresh (re-poll tools/list).\"),\n});\n\nexport type RefreshSourceInput = z.infer<typeof RefreshSourceArgs>;\n\nexport type RefreshSourceResult =\n | { ok: true; name: string; status: PeerMcpStatus; tool_count: number; error?: string }\n | { ok: false; name: string; error: string };\n\nasync function refreshHandler(\n args: RefreshSourceInput,\n deps: SourceRegistryFacade,\n): Promise<RefreshSourceResult> {\n const info = await deps.refresh(args.name);\n if (info === undefined) {\n return { ok: false, name: args.name, error: `unknown source: ${args.name}` };\n }\n const result: RefreshSourceResult = {\n ok: true,\n name: args.name,\n status: info.status,\n tool_count: info.tools.length,\n };\n if (info.error !== undefined) result.error = info.error;\n return result;\n}\n\nexport const refreshSourceTool = {\n name: \"refresh_source\" as const,\n description:\n \"Re-poll tools/list against a live peer-MCP source and refresh its cached \" +\n \"tool list. Returns the updated status (connected/unavailable/unreachable) \" +\n \"and tool_count. SOURCES-REGISTRY §6.3.\",\n inputSchema: RefreshSourceArgs,\n handler: refreshHandler,\n};\n\n// ─── unset_mcp_client ─────────────────────────────────────────────────────\n\nconst UnsetMcpClientArgs = z.object({\n name: z.string().min(1).describe(\"Peer-MCP source name to disconnect + drop from the registry.\"),\n});\n\nexport type UnsetMcpClientInput = z.infer<typeof UnsetMcpClientArgs>;\n\nexport type UnsetMcpClientResult = {\n ok: true;\n name: string;\n /** True when a live client was disposed; false when the name was unknown. */\n removed: boolean;\n};\n\nasync function unsetHandler(\n args: UnsetMcpClientInput,\n deps: SourceRegistryFacade,\n): Promise<UnsetMcpClientResult> {\n const removed = deps.remove(args.name);\n return { ok: true, name: args.name, removed };\n}\n\nexport const unsetMcpClientTool = {\n name: \"unset_mcp_client\" as const,\n description:\n \"Disconnect a live peer-MCP source and drop it from the running registry. \" +\n \"Idempotent (removed:false when the name is unknown). Affects the running \" +\n \"process only — pair with set_mcp_client({name, remove:true}) to also \" +\n \"delete the persisted config entry. SOURCES-REGISTRY §6.2.\",\n inputSchema: UnsetMcpClientArgs,\n handler: unsetHandler,\n};\n","/**\n * Error formatting helper.\n *\n * Collapses the recurring \"instanceof Error ? .message : String()\"\n * boilerplate into a single, testable function.\n *\n * # Adapter-seam discipline\n *\n * Pure helper. Zero runtime imports.\n */\n\n/**\n * Render an unknown thrown value as a human-readable string.\n *\n * Byte-identical to the inline ternary it replaces (an Error's `.message`,\n * otherwise `String(value)`).\n */\nexport function errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","/**\n * syncPluginTools — Phase 7 / Plan 07-04 / D-MCP-SURFACE, ADR-007.\n *\n * Diff-based dynamic MCP Tool registration for the five plugin-control tools.\n * Mirrors `syncAutoRegistered` from Phase 6 (`src/contracts/auto-register.ts`)\n * line-for-line:\n * 1. computes the desired set from `PLUGIN_TOOL_NAMES` based on `opts.enabled`;\n * 2. removes tools no longer desired via `RegisteredTool.remove()`;\n * 3. adds new tools via `server.registerTool(name, config, callback)`;\n * 4. calls `server.sendToolListChanged()` exactly ONCE per mutation cycle.\n *\n * No-op (after removing any prior registrations) when `opts.enabled === false`.\n * Default-OFF gate is the structural mechanism that keeps the v1-baseline\n * tools-list snapshot byte-stable for non-plugin deployments (Phase 8 REL-08\n * ≤32-tool budget).\n *\n * # Adapter-seam discipline\n *\n * Imports only `@modelcontextprotocol/sdk` types + sibling tool modules.\n * Zero `fs` / `path` / `yaml` / `chokidar`.\n */\n\nimport type { McpServer, RegisteredTool } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport { setRuntimeConfigTool } from \"./set-runtime-config.js\";\nimport type { SetRuntimeConfigInput } from \"./set-runtime-config.js\";\nimport { resolveSecretTool, ResolveSecretShape } from \"./resolve-secret.js\";\nimport type { ResolveSecretInput } from \"./resolve-secret.js\";\nimport { setMcpClientTool, SetMcpClientShape } from \"./set-mcp-client.js\";\nimport type { SetMcpClientInput } from \"./set-mcp-client.js\";\nimport { getRuntimeStatsTool } from \"./get-runtime-stats.js\";\nimport type { GetRuntimeStatsInput, StatsVault } from \"./get-runtime-stats.js\";\nimport { triggerReindexTool } from \"./trigger-reindex.js\";\nimport type {\n ReindexVault,\n TriggerReindexInput,\n TriggerReindexProgress,\n} from \"./trigger-reindex.js\";\nimport { suppressContractWriteTool } from \"./suppress-contract-write.js\";\nimport type { SuppressContractWriteInput } from \"./suppress-contract-write.js\";\nimport {\n refreshSourceTool,\n unsetMcpClientTool,\n type RefreshSourceInput,\n type UnsetMcpClientInput,\n type SourceRegistryFacade,\n} from \"./source-tools.js\";\nimport type { SuppressionSet } from \"../adapters/change-feed/obsidian-fs/suppression.js\";\nimport type { RuntimeConfigStore } from \"./runtime-config.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\n// Re-exports — consumed by server.ts wiring + tests.\nexport { setRuntimeConfigTool } from \"./set-runtime-config.js\";\nexport { resolveSecretTool } from \"./resolve-secret.js\";\nexport { setMcpClientTool } from \"./set-mcp-client.js\";\nexport { getRuntimeStatsTool } from \"./get-runtime-stats.js\";\nexport { triggerReindexTool } from \"./trigger-reindex.js\";\nexport { suppressContractWriteTool } from \"./suppress-contract-write.js\";\nexport { refreshSourceTool, unsetMcpClientTool } from \"./source-tools.js\";\nexport type { SourceRegistryFacade } from \"./source-tools.js\";\nexport { RuntimeConfigStore } from \"./runtime-config.js\";\n\n/**\n * Canonical list of plugin-control tool names. ORDER is significant only for\n * stable `tools/list` output — pinned here so the gating test can match\n * deterministically.\n *\n * Plan 07-07 added `suppress_contract_write` (CAN-08). The v1-baseline\n * tools-list snapshot stays byte-identical because the gate is default-OFF\n * — these names only land on the wire when `[plugin] enabled = true`.\n */\nexport const PLUGIN_TOOL_NAMES = [\n \"set_runtime_config\",\n \"resolve_secret\",\n \"set_mcp_client\",\n \"get_runtime_stats\",\n \"trigger_reindex\",\n \"suppress_contract_write\",\n // SOURCES-REGISTRY.md §6 (Stage 2) — live-registry source management.\n \"refresh_source\",\n \"unset_mcp_client\",\n] as const;\n\nexport type PluginToolName = (typeof PLUGIN_TOOL_NAMES)[number];\n\nexport interface SyncPluginToolsOpts {\n /** D-MCP-SURFACE — default-OFF gate. No-op when false. */\n enabled: boolean;\n /** Runtime-config store consumed by set_runtime_config (PLG-01). */\n runtimeConfig: RuntimeConfigStore;\n /** Path to config.toml consumed by set_mcp_client (PLG-05). */\n configPath: string;\n /** Vault list provider consumed by get_runtime_stats + trigger_reindex. */\n listVaults: () => StatsVault[] & ReindexVault[];\n /** Peer-MCP status snapshot consumed by get_runtime_stats. */\n peerMcpStatus: () => Array<{ name: string; available: boolean }>;\n /** Contract count provider consumed by get_runtime_stats. */\n contractCountFor: (vault: string) => number;\n /** Reindex callback consumed by trigger_reindex (wraps indexVault). */\n reindexVault: (\n vaultName: string,\n onProgress?: (p: TriggerReindexProgress) => void,\n ) => Promise<void>;\n /** MCP SDK notifier consumed by trigger_reindex (for progressToken). */\n notifier: (notification: {\n method: \"notifications/progress\";\n params: { progressToken: string; progress: number; total?: number };\n }) => void;\n /**\n * Phase 7 / Plan 07-07 / CAN-08. Shared SuppressionSet consumed by\n * `suppress_contract_write`. Required when `enabled === true` — the\n * server bootstrap owns the singleton instance and threads it both\n * here and into `startContractRegistry` so a single set sees both\n * pathways.\n */\n suppression: SuppressionSet;\n /**\n * SOURCES-REGISTRY.md §6 (Stage 2). Live peer-MCP registry facade\n * consumed by `refresh_source` + `unset_mcp_client`. Required when\n * `enabled === true` — the server bootstrap owns the singleton\n * `PeerMcpRegistry` and threads it here.\n */\n sourceRegistry: SourceRegistryFacade;\n}\n\n/**\n * Wrap a handler result as an MCP `content[]` response. Mirrors `ok()` in\n * `src/server.ts`. We inline it rather than importing from server.ts to\n * preserve the adapter-seam discipline (no upward imports).\n */\nfunction ok(data: unknown): { content: Array<{ type: \"text\"; text: string }> } {\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n}\n\nfunction errorResponse(message: string): {\n isError: true;\n content: Array<{ type: \"text\"; text: string }>;\n} {\n return { isError: true, content: [{ type: \"text\", text: message }] };\n}\n\n/**\n * Sync the plugin-control MCP tools against the McpServer. Idempotent: a\n * second call with the same `enabled` state is a no-op (no register/remove\n * happens, `sendToolListChanged` does not fire).\n */\nexport function syncPluginTools(\n server: McpServer,\n registered: Map<string, RegisteredTool>,\n opts: SyncPluginToolsOpts,\n): void {\n const desired = new Set<string>(opts.enabled ? PLUGIN_TOOL_NAMES : []);\n\n let mutated = false;\n\n // Remove tools no longer desired.\n for (const [toolName, regd] of Array.from(registered)) {\n if (!desired.has(toolName)) {\n regd.remove();\n registered.delete(toolName);\n mutated = true;\n }\n }\n\n if (!opts.enabled) {\n if (mutated) server.sendToolListChanged();\n return;\n }\n\n // Add missing tools. Each tool's Zod input schema is its raw object shape\n // (SDK 1.29 accepts a Zod schema OR a raw shape). We pass the schema's\n // `.shape` to satisfy the SDK type expectations (Pitfall F1 of Phase 6).\n const adds: Array<{ name: PluginToolName; reg: () => RegisteredTool }> = [\n {\n name: \"set_runtime_config\",\n reg: () =>\n server.registerTool(\n setRuntimeConfigTool.name,\n {\n description: setRuntimeConfigTool.description,\n inputSchema: setRuntimeConfigTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = setRuntimeConfigTool.inputSchema.parse(\n args,\n ) as SetRuntimeConfigInput;\n const result = await setRuntimeConfigTool.handler(validated, {\n store: opts.runtimeConfig,\n });\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"resolve_secret\",\n reg: () =>\n server.registerTool(\n resolveSecretTool.name,\n {\n description: resolveSecretTool.description,\n // The exported raw shape (no .refine) is what SDK 1.29 accepts.\n // The handler re-validates with the refined schema for the\n // cross-field invariant (ciphertext OR error).\n inputSchema: ResolveSecretShape,\n },\n async (args: unknown) => {\n try {\n const validated = resolveSecretTool.inputSchema.parse(args) as ResolveSecretInput;\n const result = await resolveSecretTool.handler(validated);\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"set_mcp_client\",\n reg: () =>\n server.registerTool(\n setMcpClientTool.name,\n {\n description: setMcpClientTool.description,\n // SDK 1.29 wants a ZodRawShapeCompat — the discriminator is\n // re-validated inside the handler via the refined union schema.\n inputSchema: SetMcpClientShape,\n },\n async (args: unknown) => {\n try {\n const validated = setMcpClientTool.inputSchema.parse(args) as SetMcpClientInput;\n const result = await setMcpClientTool.handler(validated, {\n configPath: opts.configPath,\n });\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"get_runtime_stats\",\n reg: () =>\n server.registerTool(\n getRuntimeStatsTool.name,\n {\n description: getRuntimeStatsTool.description,\n inputSchema: getRuntimeStatsTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = getRuntimeStatsTool.inputSchema.parse(args) as GetRuntimeStatsInput;\n const result = await getRuntimeStatsTool.handler(validated, {\n listVaults: opts.listVaults,\n peerMcpStatus: opts.peerMcpStatus,\n contractCountFor: opts.contractCountFor,\n });\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"trigger_reindex\",\n reg: () =>\n server.registerTool(\n triggerReindexTool.name,\n {\n description: triggerReindexTool.description,\n inputSchema: triggerReindexTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = triggerReindexTool.inputSchema.parse(args) as TriggerReindexInput;\n const result = await triggerReindexTool.handler(validated, {\n listVaults: opts.listVaults,\n reindexVault: opts.reindexVault,\n notifier: opts.notifier,\n });\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"suppress_contract_write\",\n reg: () =>\n server.registerTool(\n suppressContractWriteTool.name,\n {\n description: suppressContractWriteTool.description,\n inputSchema: suppressContractWriteTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = suppressContractWriteTool.inputSchema.parse(\n args,\n ) as SuppressContractWriteInput;\n const result = await suppressContractWriteTool.handler(validated, {\n suppression: opts.suppression,\n });\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"refresh_source\",\n reg: () =>\n server.registerTool(\n refreshSourceTool.name,\n {\n description: refreshSourceTool.description,\n inputSchema: refreshSourceTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = refreshSourceTool.inputSchema.parse(args) as RefreshSourceInput;\n const result = await refreshSourceTool.handler(validated, opts.sourceRegistry);\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n {\n name: \"unset_mcp_client\",\n reg: () =>\n server.registerTool(\n unsetMcpClientTool.name,\n {\n description: unsetMcpClientTool.description,\n inputSchema: unsetMcpClientTool.inputSchema.shape,\n },\n async (args: unknown) => {\n try {\n const validated = unsetMcpClientTool.inputSchema.parse(args) as UnsetMcpClientInput;\n const result = await unsetMcpClientTool.handler(validated, opts.sourceRegistry);\n return ok(result);\n } catch (err) {\n return errorResponse(errorMessage(err));\n }\n },\n ) as RegisteredTool,\n },\n ];\n\n for (const { name, reg } of adds) {\n if (registered.has(name)) continue;\n registered.set(name, reg());\n mutated = true;\n }\n\n if (mutated) server.sendToolListChanged();\n}\n","/**\n * Heading extraction for Markdown content.\n *\n * Recognizes ATX-style headings (`#`..`######`) outside fenced code blocks.\n * Setext-style headings (underlined with `===` / `---`) are not supported —\n * they are extremely rare in Obsidian vaults and skipping them keeps the\n * parser simple and predictable.\n */\n\nexport interface HeadingRef {\n /** Heading level, 1–6. */\n level: number;\n /** Heading text, without leading `#` markers or trimming whitespace. */\n text: string;\n /** 1-based line number in source content. */\n line: number;\n /** Character offset where the heading line starts in source content. */\n startOffset: number;\n}\n\nconst ATX_HEADING_RE = /^(#{1,6})\\s+(.+?)\\s*#*\\s*$/;\nconst FENCE_RE = /^(\\s*)(`{3,}|~{3,})/;\n\n/**\n * Extract all ATX headings from the content, ignoring anything inside fenced\n * code blocks. Returns headings in document order.\n */\nexport function extractHeadings(content: string): HeadingRef[] {\n const headings: HeadingRef[] = [];\n if (content.length === 0) return headings;\n\n const lines = content.split(\"\\n\");\n let offset = 0;\n let inFence = false;\n let fenceMarker: string | null = null;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n const fenceMatch = FENCE_RE.exec(line);\n if (fenceMatch) {\n const marker = fenceMatch[2] ?? \"\";\n if (!inFence) {\n inFence = true;\n fenceMarker = marker[0] ?? null; // remember whether it was ` or ~\n } else if (fenceMarker && marker.startsWith(fenceMarker)) {\n inFence = false;\n fenceMarker = null;\n }\n } else if (!inFence) {\n const m = ATX_HEADING_RE.exec(line);\n if (m) {\n const hashes = m[1] ?? \"\";\n const text = m[2] ?? \"\";\n headings.push({\n level: hashes.length,\n text: text.trim(),\n line: i + 1,\n startOffset: offset,\n });\n }\n }\n // +1 for the newline character (the last line may have no trailing newline,\n // but we never read past the end of the lines array).\n offset += line.length + 1;\n }\n\n return headings;\n}\n\n/**\n * Return the nearest preceding heading as a short path string,\n * e.g. `\"## 5. Empfehlung\"`. Returns `null` if no heading precedes the offset.\n *\n * This is an MVP-style path: only the immediate predecessor, not a full\n * `H1 > H2 > H3` breadcrumb.\n */\nexport function headingPathAtOffset(headings: HeadingRef[], offset: number): string | null {\n let last: HeadingRef | null = null;\n for (const h of headings) {\n if (h.startOffset <= offset) {\n last = h;\n } else {\n break;\n }\n }\n if (!last) return null;\n return `${\"#\".repeat(last.level)} ${last.text}`;\n}\n","/**\n * Phase 3 — section anchor computation.\n *\n * Per ADR-003 H-7:\n * anchor = sha256_hex(NFC(heading_text) || \"\\n\" || NFC(plain_text_body))\n *\n * The `plain_text_body` is produced by `blockToPlainText` (defined below)\n * walking the section's `BlockNode[]`. The renderer is intentionally\n * minimal — it is NOT a markdown round-trip; its only contract is that\n * identical-content sections produce identical hashes.\n *\n * Pure function. No fs / gray-matter / chokidar / path imports. The\n * adapter-seam linter (`scripts/lint-adapters.sh`) enforces this.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { BlockNode } from \"../types.js\";\n\n/**\n * Compute the canonical content-hash anchor for a section.\n *\n * Algorithm:\n * plainBody = blocks.map(blockToPlainText).join(\"\\n\")\n * canonical = headingText.normalize(\"NFC\") + \"\\n\" + plainBody.normalize(\"NFC\")\n * anchor = sha256_hex(canonical)\n *\n * NFC normalization is required so that the same logical string\n * encoded differently (precomposed vs decomposed Unicode) produces\n * identical anchors. LF (0x0A) is the only separator.\n *\n * The trailing newline separator (between heading and body) is emitted\n * UNCONDITIONALLY — even when the body is empty or the heading is the\n * synthetic preamble \"\" — so that a section with `heading_text = \"\"`\n * and `blocks = []` produces a deterministic, well-defined hash\n * (not the sha256 of the empty string).\n */\nexport function computeAnchor(headingText: string, blocks: readonly BlockNode[]): string {\n const plainBody = blocks.map(blockToPlainText).join(\"\\n\");\n const canonical = headingText.normalize(\"NFC\") + \"\\n\" + plainBody.normalize(\"NFC\");\n return createHash(\"sha256\").update(canonical, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Deterministic plain-text rendering of a single block. Identical\n * content produces identical output; this is the only requirement.\n *\n * Discriminated-union exhaustiveness is enforced via the `never`\n * fallthrough — a future block variant added without updating this\n * function fails type-check.\n */\nexport function blockToPlainText(block: BlockNode): string {\n switch (block.kind) {\n case \"paragraph\":\n return block.text;\n case \"heading\":\n return \"#\".repeat(block.level) + \" \" + block.text;\n case \"code\":\n return \"```\" + (block.lang ?? \"\") + \"\\n\" + block.text + \"\\n```\";\n case \"list\": {\n const marker = block.ordered ? \"1.\" : \"-\";\n return block.items.map((item) => marker + \" \" + item).join(\"\\n\");\n }\n case \"section\":\n // Recursive case — sections nesting sections is permitted by the\n // type union (the canonical Phase 3 `BlockNode` tree). The plain\n // text of a section block is its own heading line + its blocks'\n // plain text, joined consistently with the top-level anchor\n // algorithm above.\n return (\n \"#\".repeat(Math.max(1, block.level)) +\n \" \" +\n // For the synthetic preamble (level 0, empty heading_text) the\n // hash collapses to \"# \" + \"\" which is fine — sections-of-sections\n // is an unusual shape and only appears in tree-builder outputs.\n (block.heading_path[block.heading_path.length - 1] ?? \"\") +\n \"\\n\" +\n block.blocks.map(blockToPlainText).join(\"\\n\")\n );\n default: {\n const _exhaustive: never = block;\n return _exhaustive;\n }\n }\n}\n","/**\n * Phase 3 — section extraction.\n *\n * Walks `BlockNode[]` left-to-right and produces a flat array of\n * `SectionInfo` per ADR-003 H-7. Each section aggregates a heading\n * and all `BlockNode` descendants up to (but not including) the\n * next equal-or-shallower heading. Top-of-document content with no\n * preceding heading becomes a synthetic preamble section\n * (`level: 0, heading_path: [], heading_text: \"\"`).\n *\n * Also exports `markdownToSectionBlocks(content)` — a minimal markdown\n * → `BlockNode[]` lifter used by the indexer and the migration-time\n * backfill (since v1 storage only carries the raw markdown content,\n * not a parsed `BlockNode[]`). The lifter emits `heading` and\n * `paragraph` variants only, which is sufficient for section identity\n * (anchor + heading_path). Fenced code blocks are kept as paragraphs\n * so their body bytes participate in the anchor exactly as written.\n *\n * Pure module — no fs / gray-matter / chokidar / path imports.\n * Enforced by `scripts/lint-adapters.sh`.\n */\n\nimport type { BlockNode, SectionInfo } from \"../types.js\";\nimport { extractHeadings } from \"../chunker/headings.js\";\nimport { computeAnchor } from \"./anchor.js\";\n\n/**\n * Walk `blocks` left-to-right and return the section list. The list\n * order is document order (preamble first if present, then sections\n * in source order). `parent_index` points into this array; `ord` is\n * the sibling index under the same parent (assigned in a second pass).\n *\n * Algorithm (per plan):\n * - Maintain a stack of open sections, each at some level.\n * - On each heading:\n * pop while top.level >= heading.level\n * new section parent_index = top-of-stack (or null)\n * push it\n * - On each non-heading block:\n * if stack is empty, lazily open a synthetic preamble (level 0).\n * append to the current top-of-stack section's plain-text body.\n *\n * `plain_text_body` is built by joining each contained block's plain\n * text with `\"\\n\"`, identical to how `computeAnchor` consumes blocks.\n * This keeps the body bytes deterministic and lets the anchor be\n * computed directly from `(heading_text, blocks_in_section)` without\n * a second walk.\n */\nexport function extractSections(blocks: readonly BlockNode[]): SectionInfo[] {\n // Working representation: each section owns the BlockNode[] it\n // accumulates, plus its level + heading_text + heading_path +\n // parent_index. Anchors are computed at the end.\n interface Working {\n level: 0 | 1 | 2 | 3 | 4 | 5 | 6;\n heading_text: string;\n heading_path: string[];\n parent_index: number | null;\n blocks: BlockNode[];\n }\n\n const out: Working[] = [];\n // Stack tracks indices into `out` (so we can update parent_index\n // and append blocks). Each entry is an index whose section is\n // currently \"open\".\n const stack: number[] = [];\n\n const stackTop = (): number | null =>\n stack.length === 0 ? null : (stack[stack.length - 1] ?? null);\n\n const ensurePreamble = (): number => {\n // The preamble exists iff there's a level-0 section at index 0.\n if (out.length > 0 && out[0]!.level === 0) return 0;\n // No preamble yet — open one. It must be the FIRST entry in `out`.\n if (out.length > 0) {\n // Defensive: if non-heading content appears after some headings\n // have been opened, this branch is unreachable (the heading\n // would be on the stack already). The check is here only to\n // guarantee preamble-at-index-0 if anyone calls ensurePreamble\n // mid-walk.\n throw new Error(\n \"Internal invariant: ensurePreamble called after sections exist; section walker is buggy.\",\n );\n }\n out.push({\n level: 0,\n heading_text: \"\",\n heading_path: [],\n parent_index: null,\n blocks: [],\n });\n stack.push(0);\n return 0;\n };\n\n for (const block of blocks) {\n if (block.kind === \"heading\") {\n // Pop open sections whose level >= this heading's level.\n // The synthetic preamble (level 0) is also popped on the first\n // heading we encounter — preambles live at the document root\n // alongside top-level headings, NOT as their parent. (Without\n // this special case, a `0 >= 1` check would be false and the\n // first H1 would be threaded under the preamble.)\n while (stack.length > 0) {\n const topIdx = stack[stack.length - 1]!;\n const top = out[topIdx]!;\n if (top.level >= block.level || top.level === 0) {\n stack.pop();\n } else {\n break;\n }\n }\n const parentIdx = stackTop();\n const parentPath = parentIdx === null ? [] : out[parentIdx]!.heading_path;\n const headingText = block.text;\n out.push({\n level: block.level,\n heading_text: headingText,\n heading_path: [...parentPath, headingText],\n parent_index: parentIdx,\n blocks: [],\n });\n stack.push(out.length - 1);\n continue;\n }\n // Non-heading block (paragraph / code / list / section / etc).\n // If nothing is open yet, lazily open the synthetic preamble.\n if (stack.length === 0) {\n ensurePreamble();\n }\n const topIdx = stackTop()!;\n out[topIdx]!.blocks.push(block);\n }\n\n // Second pass: assign `ord` per (parent_index) sibling group.\n // `ord` is the index in document order among sections sharing the\n // same `parent_index`.\n const ords: number[] = new Array(out.length).fill(0);\n const seenPerParent = new Map<number | null, number>();\n for (let i = 0; i < out.length; i++) {\n const parent = out[i]!.parent_index;\n const next = seenPerParent.get(parent) ?? 0;\n ords[i] = next;\n seenPerParent.set(parent, next + 1);\n }\n\n // Materialize SectionInfo[] with anchors + ord + plain_text_body.\n return out.map((w, i) => {\n const plainBody = w.blocks.map(blockToPlainTextLocal).join(\"\\n\");\n const anchor = computeAnchor(w.heading_text, w.blocks);\n return {\n anchor,\n heading_path: w.heading_path,\n heading_text: w.heading_text,\n level: w.level,\n parent_index: w.parent_index,\n ord: ords[i]!,\n plain_text_body: plainBody,\n };\n });\n}\n\n/**\n * Local plain-text helper for body byte reconstruction inside\n * `extractSections`. Mirrors `blockToPlainText` from `./anchor.ts` but\n * keeps the function inline to avoid a circular-import path. The two\n * helpers MUST emit byte-identical output for the same `BlockNode` —\n * the `markdownToSectionBlocks` round-trip test in `extract.test.ts`\n * verifies this indirectly (anchor equivalence).\n *\n * `section` variant deliberately not handled here — the section walker\n * never emits a nested `section` block into `Working.blocks`; that\n * variant exists only as the canonical OUTPUT shape returned from\n * `get_outline` (Phase 3 slice 03-02), not as input to extraction.\n */\nfunction blockToPlainTextLocal(block: BlockNode): string {\n switch (block.kind) {\n case \"paragraph\":\n return block.text;\n case \"heading\":\n return \"#\".repeat(block.level) + \" \" + block.text;\n case \"code\":\n return \"```\" + (block.lang ?? \"\") + \"\\n\" + block.text + \"\\n```\";\n case \"list\": {\n const marker = block.ordered ? \"1.\" : \"-\";\n return block.items.map((item) => marker + \" \" + item).join(\"\\n\");\n }\n case \"section\":\n // See JSDoc — should not appear as input, but render defensively.\n return (\n \"#\".repeat(Math.max(1, block.level)) +\n \" \" +\n (block.heading_path[block.heading_path.length - 1] ?? \"\") +\n \"\\n\" +\n block.blocks.map(blockToPlainTextLocal).join(\"\\n\")\n );\n default: {\n const _exhaustive: never = block;\n return _exhaustive;\n }\n }\n}\n\n/**\n * Lift raw markdown into a minimal `BlockNode[]` of `heading` +\n * `paragraph` variants — enough for section identity. Used by the\n * indexer and the migration-time backfill.\n *\n * Why this lifter exists: v1 storage holds `notes.content` (raw\n * markdown) but no parsed `BlockNode[]`. Phase 3 needs sections\n * extracted from that markdown. A full markdown→BlockNode parser is\n * out of scope for this slice (and would duplicate Phase 1 adapter\n * work). This minimal lifter is sufficient because anchors only\n * depend on heading_text + plain_text_body, and the body bytes are\n * preserved verbatim regardless of how they're labeled.\n *\n * Algorithm:\n * 1. Run `extractHeadings(content)` to get every ATX heading's\n * level + text + startOffset (already fenced-code-aware).\n * 2. Slice the content between heading start offsets:\n * - The slice from 0 to the first heading's start is the preamble\n * body (emitted as a single `paragraph` block IF non-empty).\n * - Each heading + the slice between its line and the next\n * heading's line becomes a `heading` block followed by a\n * `paragraph` block carrying the body bytes (verbatim,\n * with the heading line itself stripped).\n *\n * Body slices are kept verbatim (including blank lines and code\n * fences). The indexer's anchor calculation depends on byte\n * stability — we do NOT trim trailing whitespace, normalize\n * line endings, or collapse blanks. NFC normalization happens\n * inside `computeAnchor`.\n *\n * Pure — no fs / gray-matter / chokidar imports.\n */\nexport function markdownToSectionBlocks(content: string): BlockNode[] {\n if (content.length === 0) return [];\n const headings = extractHeadings(content);\n\n const out: BlockNode[] = [];\n\n // Preamble: bytes from 0 to first heading's startOffset (or end of\n // content if no headings).\n const firstHeadingStart = headings.length === 0 ? content.length : headings[0]!.startOffset;\n if (firstHeadingStart > 0) {\n const preamble = content.slice(0, firstHeadingStart);\n if (preamble.length > 0) {\n // Strip a single trailing newline so the paragraph block doesn't\n // carry the separator into its body bytes. (Preserves stability\n // when the body is \"intro text\\n\" before \"# H1\" — the heading's\n // own line begins exactly at firstHeadingStart, so the slice\n // includes the newline between intro and #.)\n out.push({ kind: \"paragraph\", text: stripTrailingNewline(preamble) });\n }\n }\n\n for (let i = 0; i < headings.length; i++) {\n const h = headings[i]!;\n const next = headings[i + 1];\n const headingLineEnd = nextLineEnd(content, h.startOffset);\n const headingBodyStart = headingLineEnd;\n const headingBodyEnd = next ? next.startOffset : content.length;\n // Cast to the strict heading-level type — extractHeadings only\n // emits 1..6 per the ATX regex, so the runtime guarantee holds.\n const level = h.level as 1 | 2 | 3 | 4 | 5 | 6;\n out.push({ kind: \"heading\", level, text: h.text });\n if (headingBodyEnd > headingBodyStart) {\n const body = content.slice(headingBodyStart, headingBodyEnd);\n const trimmed = stripTrailingNewline(body);\n // Skip an empty body to keep the BlockNode list tight — sections\n // with no body still get a valid anchor (sha256 of \"<heading>\\n\").\n if (trimmed.length > 0) {\n out.push({ kind: \"paragraph\", text: trimmed });\n }\n }\n }\n\n return out;\n}\n\nfunction nextLineEnd(content: string, start: number): number {\n // Find the first 0x0A at or after `start`. Returns the index AFTER\n // the newline (so the next line begins there), or content.length if\n // no newline is found.\n const idx = content.indexOf(\"\\n\", start);\n if (idx === -1) return content.length;\n return idx + 1;\n}\n\nfunction stripTrailingNewline(s: string): string {\n if (s.endsWith(\"\\r\\n\")) return s.slice(0, -2);\n if (s.endsWith(\"\\n\")) return s.slice(0, -1);\n return s;\n}\n","/**\n * Phase 3 — one-time section backfill (M2 fix in plan 03-01).\n *\n * Wired into migration 010 (`src/db/schema.ts:runMigration010`) so an\n * existing v1 user vault gets `sections` rows populated immediately on\n * upgrade — WITHOUT requiring a content edit / catchup pass.\n *\n * Approach (per 03-01-DEVIATIONS.md §D1): re-derive sections from each\n * note's `content` column via `markdownToSectionBlocks` →\n * `extractSections`, NOT from `chunks.heading_path` (which only carries\n * the immediate-predecessor heading as a markdown string, insufficient\n * to reconstruct a full section tree). This keeps the\n * anchor-equivalence guarantee trivially: backfill and a fresh re-index\n * run the SAME pipeline against the SAME `notes.content` bytes.\n *\n * Pure of fs / gray-matter / chokidar / path imports. Reads + writes\n * only via the supplied `BetterSqlite3.Database` handle.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\nimport type { BlockNode, ChunkRow, InsertSectionRow, SectionInfo } from \"../types.js\";\nimport { extractSections, markdownToSectionBlocks } from \"./extract.js\";\n\n/**\n * Walk every note in the DB and ensure it has a corresponding set of\n * `sections` rows. Idempotent: if a note already has sections, skip\n * it. Returns the number of notes for which sections were newly\n * populated (for migration log / test assertions).\n *\n * Called from migration 010 inside the migration transaction; safe to\n * call again from tests against an in-memory DB.\n */\nexport function backfillSectionsFromChunks(db: BetterSqlite3.Database): number {\n // Inline these queries (rather than going through SectionsQueries /\n // NotesQueries / ChunksQueries) so the migration runner doesn't\n // depend on the high-level query namespaces. The schema is\n // guaranteed to exist at this point (migration 010 step A ran first).\n const notesRows = db\n .prepare<[], { id: number; content: string }>(\"SELECT id, content FROM notes\")\n .all();\n\n const existingCount = db.prepare<[number], { c: number }>(\n \"SELECT COUNT(*) AS c FROM sections WHERE note_id = ?\",\n );\n const getChunks = db.prepare<[number], ChunkRow>(\n \"SELECT * FROM chunks WHERE note_id = ? ORDER BY id ASC\",\n );\n // INSERT OR IGNORE: section identity is (note_id, heading_path, anchor) per\n // ADR-032 (revised). A collision needs same anchor AND same heading_path —\n // i.e. byte-identical content in the same context. A plain INSERT on a true\n // collision would roll back the whole migration transaction and crash every\n // CLI command (see ISSUE-migration-010-duplicate-anchor.md). `OR IGNORE`\n // makes the first sibling win; later same-identity siblings collapse into it\n // for parent-linkage via lookupExistingSection. Differently-placed\n // byte-identical sections (different heading_path) persist as distinct rows.\n const insertSection = db.prepare(`\n INSERT OR IGNORE INTO sections\n (note_id, anchor, heading_path, heading_text, level,\n parent_id, ord, chunk_id_first, chunk_id_last, created_at)\n VALUES\n (@note_id, @anchor, @heading_path, @heading_text, @level,\n @parent_id, @ord, @chunk_id_first, @chunk_id_last, @created_at)\n `);\n\n const lookupExistingSection = db.prepare<[number, string, string], { id: number }>(\n \"SELECT id FROM sections WHERE note_id = ? AND heading_path = ? AND anchor = ?\",\n );\n\n let backfilled = 0;\n const now = Date.now();\n\n for (const note of notesRows) {\n // Skip notes that already have sections (idempotency / safety).\n const existing = existingCount.get(note.id);\n if (existing && existing.c > 0) continue;\n\n if (!note.content || note.content.length === 0) {\n // Empty notes get no sections — keep storage tight.\n continue;\n }\n\n const blocks: BlockNode[] = markdownToSectionBlocks(note.content);\n const sectionInfos: SectionInfo[] = extractSections(blocks);\n if (sectionInfos.length === 0) continue;\n\n // Walk this note's chunks once and bin them into the section list\n // by `start_offset`. Sections own a [chunk_id_first, chunk_id_last]\n // range; we compute it by mapping each chunk's start offset to its\n // owning heading region.\n const chunks = getChunks.all(note.id);\n const chunkRanges = computeChunkRangesForSections(note.content, sectionInfos, chunks);\n\n // Insert in two passes so parent_id can reference the newly-minted\n // section IDs. Per-index → ID map populated as we go. Slots for\n // duplicate-anchor siblings reuse the surviving row's id so any\n // subsequent child still resolves its parent_id correctly.\n const insertedIds: Array<number | null> = [];\n for (let i = 0; i < sectionInfos.length; i++) {\n const s = sectionInfos[i]!;\n const parentId = s.parent_index === null ? null : (insertedIds[s.parent_index] ?? null);\n const range = chunkRanges[i] ?? { first: null, last: null };\n const row: InsertSectionRow & { created_at: number } = {\n note_id: note.id,\n anchor: s.anchor,\n heading_path: JSON.stringify(s.heading_path),\n heading_text: s.heading_text,\n level: s.level,\n parent_id: parentId,\n ord: s.ord,\n chunk_id_first: range.first,\n chunk_id_last: range.last,\n created_at: now,\n };\n const info = insertSection.run(row);\n if (info.changes > 0) {\n // Row inserted normally.\n insertedIds.push(Number(info.lastInsertRowid));\n } else {\n // Collision on UNIQUE(note_id, heading_path, anchor): a byte-identical\n // sibling in the SAME context already won the slot. Look up by the full\n // identity (heading_path stored JSON-stringified, matching the row) so\n // any later child still has a parent_id to resolve against. Acceptable\n // for a one-time migration backfill; the next full re-index rebuilds.\n const existing = lookupExistingSection.get(\n note.id,\n JSON.stringify(s.heading_path),\n s.anchor,\n );\n insertedIds.push(existing ? Number(existing.id) : null);\n }\n }\n backfilled++;\n }\n\n return backfilled;\n}\n\n/**\n * Map each section to its [chunk_id_first, chunk_id_last] range.\n *\n * Algorithm: re-derive each section's character offset window in the\n * source `content` by re-running `extractHeadingsLite` on the same\n * bytes, then place each chunk into the section whose offset window\n * contains the chunk's `start_offset`.\n *\n * Sections with no chunks (e.g. a heading followed by another heading\n * with no body) get `{ first: null, last: null }`. The chunker drops\n * heading-only spans, so this is the common case for documents with\n * empty subsections.\n */\nfunction computeChunkRangesForSections(\n content: string,\n sections: SectionInfo[],\n chunks: ChunkRow[],\n): Array<{ first: number | null; last: number | null }> {\n // We need each section's character range in the source bytes. The\n // simplest correct construction: re-walk the same heading list the\n // lifter uses, and produce a (sectionIndex → [start, end]) map.\n //\n // We import the SAME heading extractor used by markdownToSectionBlocks\n // to guarantee identical offset semantics. (No fs/gray-matter — pure.)\n // To avoid a circular import we lazy-require here via the named\n // export.\n const ranges = computeSectionOffsetRanges(content, sections);\n const out: Array<{ first: number | null; last: number | null }> = sections.map(() => ({\n first: null,\n last: null,\n }));\n\n for (const chunk of chunks) {\n const offset = chunk.start_offset;\n // Find the section whose [start, end) range contains this offset.\n // Walk in reverse so the innermost (latest, deepest) section wins.\n let chosenIdx: number | null = null;\n for (let i = ranges.length - 1; i >= 0; i--) {\n const r = ranges[i];\n if (!r) continue;\n if (offset >= r.start && offset < r.end) {\n chosenIdx = i;\n break;\n }\n }\n if (chosenIdx === null) continue;\n const slot = out[chosenIdx]!;\n if (slot.first === null || chunk.id < slot.first) slot.first = chunk.id;\n if (slot.last === null || chunk.id > slot.last) slot.last = chunk.id;\n }\n\n return out;\n}\n\n/**\n * Compute the [start, end) byte range for each section in `content`,\n * matching the slicing semantics of `markdownToSectionBlocks`.\n *\n * - Preamble (level 0) range is [0, firstHeading.startOffset).\n * - Each heading section range is [heading.startOffset, nextSibling.startOffset)\n * where nextSibling is the next heading at an equal-or-shallower level\n * (or content.length if none).\n *\n * `sections` is provided so the function can assign ranges in a way that\n * matches the section walker's output order (preamble first if present,\n * then headings in source order).\n *\n * The implementation re-extracts headings from `content` directly so it\n * doesn't depend on the section walker's internal state. This means\n * `extractHeadings` from src/chunker/headings.ts is the canonical\n * heading source — both for the lifter AND for this offset map.\n */\nfunction computeSectionOffsetRanges(\n content: string,\n sections: SectionInfo[],\n): Array<{ start: number; end: number }> {\n // Local import — avoids a circular path through schema.ts.\n // (sections/backfill.ts → chunker/headings.ts is a clean dependency.)\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n // Inline import to keep this helper self-contained; not type-imported\n // because we need the runtime call. ESM `import` at module scope is\n // the correct form below.\n const headings = headingExtractor(content);\n\n const ranges: Array<{ start: number; end: number }> = [];\n let cursor = 0; // walks the sections array\n\n // Preamble (if present) is sections[0] with level 0.\n const hasPreamble =\n sections.length > 0 && sections[0]!.level === 0 && sections[0]!.heading_text === \"\";\n const firstHeadingOffset = headings.length === 0 ? content.length : headings[0]!.startOffset;\n if (hasPreamble) {\n ranges.push({ start: 0, end: firstHeadingOffset });\n cursor = 1;\n }\n\n // For every heading section, find the next equal-or-shallower\n // heading in the source — that's the section's end offset.\n for (let h = 0; h < headings.length; h++) {\n const h0 = headings[h]!;\n let endOffset = content.length;\n for (let j = h + 1; j < headings.length; j++) {\n if (headings[j]!.level <= h0.level) {\n endOffset = headings[j]!.startOffset;\n break;\n }\n }\n ranges.push({ start: h0.startOffset, end: endOffset });\n cursor++;\n }\n\n // Defensive: if there's a length mismatch (shouldn't happen for valid\n // input), fall back to whole-document ranges for any tail entries.\n while (ranges.length < sections.length) {\n ranges.push({ start: 0, end: content.length });\n }\n return ranges;\n}\n\n// Lazy heading-extractor binding to keep `backfill.ts` free of static\n// type-side imports of the chunker module beyond what's needed for the\n// section walker. Kept as a function-import indirection so the type is\n// inferred from the call site and module-scope import-cycles stay\n// simple.\nimport { extractHeadings as headingExtractor } from \"../chunker/headings.js\";\n","/**\n * Phase 5 — chunk-fragment computation.\n *\n * Per ADR-005 §\"Decision: Chunk-level source_hashes (ChunkId)\" and\n * ADR-003 H-3 (NFC) + H-4 (LF) + Pitfall 8 (trim trailing whitespace):\n *\n * canonical = text.replace(/\\r\\n/g, \"\\n\").trimEnd().normalize(\"NFC\")\n * hash = \"sha256:\" + sha256_hex(canonical)\n * fragment = hash.slice(\"sha256:\".length, \"sha256:\".length + 7)\n *\n * `computeChunkHash` is the **single source of truth** for both\n * `chunks.chunk_id_fragment` (D-04) AND the brief\n * `source_hashes.recorded_hash` value. Scattered `createHash` calls\n * across call sites are an anti-pattern (RESEARCH §Pitfall 14).\n *\n * Pure function. No fs / gray-matter / chokidar / path imports. The\n * adapter-seam linter (`scripts/lint-adapters.sh`) enforces this.\n */\n\nimport { createHash } from \"node:crypto\";\n\n/**\n * Canonical chunk-hash. Drives BOTH `chunks.chunk_id_fragment` (D-04)\n * AND the brief `source_hashes.recorded_hash` value.\n *\n * Algorithm (ADR-003 H-3/H-4 + ADR-005 Pitfall 8):\n * 1. Normalize CRLF → LF (`\\r\\n` → `\\n`).\n * 2. Trim trailing whitespace (`trimEnd()`).\n * 3. Unicode NFC normalize.\n * 4. sha256_hex over the canonical UTF-8 bytes.\n *\n * Output format: `\"sha256:<hex>\"`. The `sha256:` prefix is part of the\n * versioned-API hash inclusion (ADR-003 H-6) — a future v3 hash flavour\n * switch (blake3 / xxhash) replaces the prefix in a single migration.\n */\nexport function computeChunkHash(text: string): string {\n const canonical = text.replace(/\\r\\n/g, \"\\n\").trimEnd().normalize(\"NFC\");\n return \"sha256:\" + createHash(\"sha256\").update(canonical, \"utf8\").digest(\"hex\");\n}\n\n/**\n * First 7 hex chars of `computeChunkHash(text)`. Public ChunkId\n * fragment per D-04.\n *\n * Collision risk at 7 hex chars (~268M combos) is acceptable at\n * document scope: worst-case thousands of chunks per doc; document\n * boundary is the disambiguator in the public ChunkId\n * (`<DocId>#chunk-<n>`).\n */\nexport function computeChunkIdFragment(text: string): string {\n return computeChunkHash(text).slice(\"sha256:\".length, \"sha256:\".length + 7);\n}\n","/**\n * SQL DDL strings and migrations.\n *\n * Migrations are inlined as TS constants — no external .sql files. This is\n * intentional: it keeps the build trivial (tsup doesn't need to copy assets)\n * and makes the migration list a single source of truth.\n *\n * To add a migration: append to `MIGRATIONS` with a monotonically increasing\n * `version`. The runner applies all migrations whose version > user_version\n * in order, then sets PRAGMA user_version to the highest version applied.\n */\n\nimport { backfillSectionsFromChunks } from \"../sections/backfill.js\";\nimport { computeChunkIdFragment } from \"../chunker/chunk-id.js\";\n\n/**\n * Context passed to every function-style migration. New optional fields can be\n * added here without rewriting existing migrations — they accept the whole\n * context as a single arg and ignore the bits they don't need.\n *\n * `vaultName` is plumbed in from the Database constructor (see database.ts).\n * Migration 008 (doc_uri backfill) requires it; earlier function-style\n * migrations (005) accept it and ignore it.\n */\nexport interface MigrationContext {\n readonly vaultName: string | undefined;\n}\n\n/**\n * A migration either ships static SQL or a function that runs imperative\n * steps against the DB. Function-style migrations are used when the steps\n * depend on the current schema state (e.g. discover all `embeddings_<dim>`\n * tables and rebuild each).\n */\nexport type Migration =\n | {\n version: number;\n description: string;\n sql: string;\n }\n | {\n version: number;\n description: string;\n run: (db: BetterSqlite3Database, ctx: MigrationContext) => void;\n };\n\n/** Section 3 of the spec — full initial schema. */\nexport const INITIAL_SCHEMA: string = `\n-- ── 3.1 Raw Layer ────────────────────────────────────────────────────────\n\n-- Migration 006 adds body_hash to this table (kept out of v1 schema so\n-- the migration chain has historical accuracy and frequent DB-rebuild\n-- tests do not trip over duplicate-column errors).\nCREATE TABLE IF NOT EXISTS notes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n path TEXT NOT NULL UNIQUE,\n content TEXT NOT NULL,\n frontmatter TEXT,\n title TEXT,\n hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n word_count INTEGER,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_notes_hash ON notes(hash);\nCREATE INDEX IF NOT EXISTS idx_notes_mtime ON notes(mtime);\n\nCREATE TABLE IF NOT EXISTS chunks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n idx INTEGER NOT NULL,\n text TEXT NOT NULL,\n heading_path TEXT,\n start_offset INTEGER NOT NULL,\n end_offset INTEGER NOT NULL,\n token_count INTEGER NOT NULL,\n UNIQUE (note_id, idx)\n);\nCREATE INDEX IF NOT EXISTS idx_chunks_note ON chunks(note_id);\n\n-- ── 3.2 Derived Layer ────────────────────────────────────────────────────\n\n-- Dimension 1024 matches qwen3-embedding (our default per Memory System spec).\n-- For future multi-model support with different dims, see roadmap Phase 7.\nCREATE VIRTUAL TABLE IF NOT EXISTS embeddings USING vec0(\n chunk_id INTEGER PRIMARY KEY,\n model_id INTEGER NOT NULL,\n vector FLOAT[1024]\n);\n\nCREATE TABLE IF NOT EXISTS models (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL UNIQUE,\n provider TEXT NOT NULL,\n dim INTEGER NOT NULL,\n created_at INTEGER NOT NULL,\n active INTEGER NOT NULL DEFAULT 1\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(\n text,\n content='chunks',\n content_rowid='id'\n);\n\n-- Triggers to keep chunks_fts in sync with chunks\nCREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN\n INSERT INTO chunks_fts(rowid, text) VALUES (new.id, new.text);\nEND;\nCREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN\n INSERT INTO chunks_fts(chunks_fts, rowid, text) VALUES('delete', old.id, old.text);\nEND;\nCREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN\n INSERT INTO chunks_fts(chunks_fts, rowid, text) VALUES('delete', old.id, old.text);\n INSERT INTO chunks_fts(rowid, text) VALUES (new.id, new.text);\nEND;\n\nCREATE TABLE IF NOT EXISTS wikilinks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_note INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n target_path TEXT NOT NULL,\n target_note INTEGER REFERENCES notes(id) ON DELETE SET NULL,\n link_text TEXT,\n anchor TEXT,\n line_number INTEGER,\n UNIQUE (source_note, target_path, anchor)\n);\nCREATE INDEX IF NOT EXISTS idx_wikilinks_source ON wikilinks(source_note);\nCREATE INDEX IF NOT EXISTS idx_wikilinks_target ON wikilinks(target_note);\n\n-- ── 3.3 Audit Layer ──────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS index_runs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL UNIQUE,\n vault_name TEXT NOT NULL,\n model_id INTEGER REFERENCES models(id),\n started_at INTEGER NOT NULL,\n finished_at INTEGER,\n trigger TEXT NOT NULL,\n notes_indexed INTEGER NOT NULL DEFAULT 0,\n chunks_created INTEGER NOT NULL DEFAULT 0,\n notes_updated INTEGER NOT NULL DEFAULT 0,\n notes_deleted INTEGER NOT NULL DEFAULT 0,\n error TEXT\n);\n\nCREATE TABLE IF NOT EXISTS write_audit (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n note_id INTEGER REFERENCES notes(id) ON DELETE SET NULL,\n op TEXT NOT NULL,\n previous_hash TEXT,\n new_hash TEXT,\n expected_hash TEXT,\n client_id TEXT,\n diff_summary TEXT,\n at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_write_audit_note ON write_audit(note_id);\n`;\n\n/**\n * Migration 002 — note_aliases table.\n *\n * Obsidian notes can declare `aliases: [\"short\", \"another\"]` in frontmatter.\n * A wikilink `[[short]]` should resolve to that note. We index aliases\n * separately so the wikilink resolver can do a fast lookup without\n * re-parsing every note's frontmatter.\n */\nconst MIGRATION_002_ALIASES = `\nCREATE TABLE IF NOT EXISTS note_aliases (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n alias TEXT NOT NULL,\n /* Aliases are case-insensitive matched in practice; we store original\n case for display but enforce a normalized key as UNIQUE per note. */\n alias_norm TEXT NOT NULL,\n UNIQUE (note_id, alias_norm)\n);\nCREATE INDEX IF NOT EXISTS idx_note_aliases_norm ON note_aliases(alias_norm);\n`;\n\n/**\n * Migration 003 — fix delete-cascade gaps in the wikilink + audit FKs.\n *\n * Original schema (v1) declared:\n * wikilinks.target_note REFERENCES notes(id) -- no action\n * write_audit.note_id REFERENCES notes(id) -- no action\n *\n * Both meant a `DELETE FROM notes` would FAIL whenever any other note still\n * linked to the deleted one, or when audit rows referenced it. That made\n * external/watcher/catchup deletes throw, and forced `delete_note` to\n * disable FKs entirely (leaving dangling `target_note` refs).\n *\n * The fix: rebuild both FKs.\n * - wikilinks.target_note → ON DELETE SET NULL (the link becomes broken,\n * correctly surfaced by find_broken_links)\n * - write_audit.note_id → ON DELETE SET NULL (audit history survives\n * the deletion, which is the whole point of audit)\n *\n * SQLite cannot ALTER a column's foreign-key action, so we rebuild each\n * table the standard way (create *_new, copy rows, drop, rename).\n */\nconst MIGRATION_003_FIX_DELETE_FKS = `\n-- 1) wikilinks: rebuild with ON DELETE SET NULL on target_note\nCREATE TABLE wikilinks_new (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_note INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n target_path TEXT NOT NULL,\n target_note INTEGER REFERENCES notes(id) ON DELETE SET NULL,\n link_text TEXT,\n anchor TEXT,\n line_number INTEGER,\n UNIQUE (source_note, target_path, anchor)\n);\nINSERT INTO wikilinks_new SELECT * FROM wikilinks;\nDROP TABLE wikilinks;\nALTER TABLE wikilinks_new RENAME TO wikilinks;\nCREATE INDEX IF NOT EXISTS idx_wikilinks_source ON wikilinks(source_note);\nCREATE INDEX IF NOT EXISTS idx_wikilinks_target ON wikilinks(target_note);\n\n-- 2) write_audit: rebuild with ON DELETE SET NULL on note_id\n-- note_id must allow NULL for this to work; the column was NOT NULL in v1.\n-- Existing audit rows that already reference vanished notes (residue from\n-- the pre-migration FK-OFF delete workaround) have their note_id healed\n-- to NULL during the copy — preserving audit history without re-introducing\n-- dangling refs.\nCREATE TABLE write_audit_new (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n note_id INTEGER REFERENCES notes(id) ON DELETE SET NULL,\n op TEXT NOT NULL,\n previous_hash TEXT,\n new_hash TEXT,\n expected_hash TEXT,\n client_id TEXT,\n diff_summary TEXT,\n at INTEGER NOT NULL\n);\nINSERT INTO write_audit_new (id, note_id, op, previous_hash, new_hash, expected_hash, client_id, diff_summary, at)\nSELECT\n wa.id,\n CASE WHEN n.id IS NULL THEN NULL ELSE wa.note_id END,\n wa.op, wa.previous_hash, wa.new_hash, wa.expected_hash, wa.client_id, wa.diff_summary, wa.at\nFROM write_audit wa\nLEFT JOIN notes n ON n.id = wa.note_id;\nDROP TABLE write_audit;\nALTER TABLE write_audit_new RENAME TO write_audit;\nCREATE INDEX IF NOT EXISTS idx_write_audit_note ON write_audit(note_id);\n`;\n\n/**\n * Migration 004 — variable embedding dimensions (Phase 7b).\n *\n * Original schema declared a single virtual table:\n * embeddings USING vec0(chunk_id, model_id, vector FLOAT[1024])\n * with the dim hard-wired to 1024 (qwen3-embedding default).\n *\n * Phase 7b lets multiple models with different output dimensions coexist\n * in the same vault DB (e.g. qwen3 @ 1024 + embeddinggemma @ 768). Because\n * sqlite-vec's vec0 requires a compile-time-fixed dimension per column,\n * we use one virtual table per dim: `embeddings_<dim>`.\n *\n * This migration:\n * 1) Creates `embeddings_1024` and `embeddings_768` up-front (the two\n * dims we know about today). Additional dims are materialized\n * on-demand by Database.ensureEmbeddingsTable(dim).\n * 2) Copies all rows from the legacy `embeddings` table into\n * `embeddings_1024` (since the legacy schema was 1024-only).\n * 3) Drops the legacy `embeddings` table.\n *\n * vec0 virtual tables do not support INSERT ... SELECT directly across\n * vec0 instances reliably across older sqlite-vec builds — we copy row\n * by row via a SELECT loop, materialised as a CTE-driven INSERT here.\n * For empty tables this is a no-op.\n */\nconst MIGRATION_004_VARIABLE_DIMS = `\nCREATE VIRTUAL TABLE IF NOT EXISTS embeddings_1024 USING vec0(\n chunk_id INTEGER PRIMARY KEY,\n model_id INTEGER NOT NULL,\n vector FLOAT[1024]\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS embeddings_768 USING vec0(\n chunk_id INTEGER PRIMARY KEY,\n model_id INTEGER NOT NULL,\n vector FLOAT[768]\n);\n\nINSERT INTO embeddings_1024 (chunk_id, model_id, vector)\n SELECT chunk_id, model_id, vector FROM embeddings;\n\nDROP TABLE embeddings;\n`;\n\n/**\n * Migration 005 — add `partition key` on `model_id` so two embedding models\n * with the same dim (e.g. qwen3 @ 1024 + bge-m3 @ 1024) can coexist for the\n * same chunks. Discovered as a bug during the Phase 7e eval run.\n *\n * sqlite-vec vec0 tables do not support ALTER COLUMN, so the only path is\n * rebuild-and-copy:\n * 1) For every existing `embeddings_<dim>` table:\n * a) Rename to `embeddings_<dim>__old`.\n * b) Create new `embeddings_<dim>` with `model_id partition key`.\n * c) Copy all rows back. The partition column accepts ordinary inserts.\n * d) Drop the `__old` table.\n *\n * We can't write this as a single static SQL string because the set of\n * dim-tables in any given DB is data-dependent (768 only exists if someone\n * registered a 768-dim model). The runner therefore calls a function-style\n * migration: see `Migration.run()` below.\n */\nfunction runMigration005(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n // Phase 7e bugfix: split per-dim tables into per-model tables so two models\n // with the same dim (e.g. qwen3 + bge-m3, both 1024) can coexist for the\n // same chunk_ids. New naming: `embeddings_m<modelId>_d<dim>`.\n //\n // The earlier partition-key approach was a dead end — sqlite-vec's\n // `partition key` is an internal index hint, NOT a composite PK; chunk_id\n // remains globally unique inside a vec0 table.\n //\n // Migration steps per legacy `embeddings_<dim>` table:\n // 1) Read all rows (grouped by model_id).\n // 2) DROP the legacy table.\n // 3) For each model_id with rows, CREATE `embeddings_m<modelId>_d<dim>`\n // and copy that model's rows back.\n const rows = db\n .prepare<\n [],\n { name: string }\n >(\"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'embeddings\\\\_%' ESCAPE '\\\\'\")\n .all();\n const legacyTables: { name: string; dim: number }[] = [];\n for (const r of rows) {\n // Match only the OLD per-dim shape (`embeddings_<dim>`), not anything\n // already in the new shape.\n const m = /^embeddings_(\\d+)$/.exec(r.name);\n if (m && m[1]) legacyTables.push({ name: r.name, dim: Number(m[1]) });\n }\n\n for (const { name, dim } of legacyTables) {\n const rows = db\n .prepare<\n [],\n { chunk_id: number; model_id: number; vector: Buffer }\n >(`SELECT chunk_id, model_id, vector FROM ${name}`)\n .all();\n\n db.exec(`DROP TABLE ${name}`);\n\n // Group rows by model_id so we materialise one new table per model.\n const byModel = new Map<number, typeof rows>();\n for (const row of rows) {\n let bucket = byModel.get(row.model_id);\n if (!bucket) {\n bucket = [];\n byModel.set(row.model_id, bucket);\n }\n bucket.push(row);\n }\n\n for (const [modelId, bucket] of byModel) {\n const newName = `embeddings_m${modelId}_d${dim}`;\n db.exec(\n `CREATE VIRTUAL TABLE ${newName} USING vec0(\n chunk_id INTEGER PRIMARY KEY,\n vector FLOAT[${dim}]\n )`,\n );\n const insert = db.prepare(`INSERT INTO ${newName} (chunk_id, vector) VALUES (?, ?)`);\n for (const row of bucket) {\n insert.run(BigInt(row.chunk_id), row.vector);\n }\n }\n }\n}\n\ntype BetterSqlite3Database = import(\"better-sqlite3\").Database;\n\n/**\n * Migration 006 — add `body_hash` to notes.\n *\n * Why: the existing `hash` column mixes content + frontmatter. Any\n * frontmatter-only change (e.g. `update_frontmatter` adding a tag) flips\n * the hash and forces the indexer to re-chunk + re-embed the entire\n * note. The body is unchanged — embeddings should stay untouched.\n *\n * `body_hash` = sha256(content) only — independent of frontmatter.\n * The indexer compares body_hash before deciding whether to re-embed:\n * - body_hash unchanged AND hash changed → frontmatter-only diff →\n * update note row + aliases, keep chunks/embeddings\n * - body_hash changed → full re-chunk + re-embed\n *\n * Existing rows have body_hash=NULL after this migration. The indexer\n * treats NULL as \"unknown — must recompute on next touch\" and fills it\n * in lazily during the next upsert. No backfill needed.\n */\nconst MIGRATION_006_BODY_HASH = `\nALTER TABLE notes ADD COLUMN body_hash TEXT;\nCREATE INDEX IF NOT EXISTS idx_notes_body_hash ON notes(body_hash);\n`;\n\n/**\n * Migration 007: doc_uri Strategy A — additive nullable column.\n *\n * Adds the v2 canonical identifier column to `notes`. Stored UN-ENCODED:\n * a raw forward-slash path with spaces / Unicode passed through (matches\n * the existing `path` column shape). Percent-encoding happens only at\n * formatDisplayUrl time per RESEARCH Pitfall 5.\n *\n * Indexer behavior: new writes populate doc_uri alongside path (plan 01-02\n * Task 04 wires this into NotesQueries.upsertByPath). Backfill of existing\n * rows is migration 008. Reads continue to use path as PK until phase 3 or\n * later flips read preference.\n *\n * Strategy A staging (RESEARCH §doc_uri Dual-Column Migration):\n * v7 = this migration (ADD COLUMN; nullable)\n * v8 = backfill (function-style; idempotent)\n * v9 = NOT NULL assertion + drop path PK — DEFERRED to phase 3+\n */\nconst MIGRATION_007_DOC_URI_ADD = `\nALTER TABLE notes ADD COLUMN doc_uri TEXT;\nCREATE INDEX IF NOT EXISTS idx_notes_doc_uri ON notes(doc_uri);\n`;\n\n/**\n * Migration 008: doc_uri Strategy A — backfill existing rows.\n *\n * For every notes row, derives:\n * doc_uri = 'obsidian-fs://' + ctx.vaultName + '/' + path\n *\n * Path is stored un-encoded (matches the existing `path` column shape).\n * Percent-encoding is a presentation concern handled by formatDisplayUrl\n * (per RESEARCH Pitfall 5).\n *\n * IDEMPOTENT: rows where doc_uri IS already NOT NULL are skipped. Re-running\n * the migration on a fully backfilled DB is a no-op. The runner is wrapped\n * in the existing SQLite transaction (database.ts:99) so failure rolls back.\n *\n * Requires `ctx.vaultName` (plumbed from VaultManager via Database constructor).\n * Throws clearly if vaultName is undefined — see RESEARCH §Pitfall 5 / A8.\n */\nfunction runMigration008(db: BetterSqlite3Database, ctx: MigrationContext): void {\n // Short-circuit: zero notes to backfill means we don't need vaultName at\n // all. This lets `:memory:` fresh DBs migrate cleanly without forcing\n // every test fixture to specify a vault name.\n const pending = db\n .prepare<[], { c: number }>(\"SELECT COUNT(*) AS c FROM notes WHERE doc_uri IS NULL\")\n .get();\n if (!pending || pending.c === 0) return;\n\n if (!ctx.vaultName) {\n throw new Error(\n \"runMigration008 requires vaultName context to backfill doc_uri on existing notes (Database constructor must be called with the vault name; check src/vault/manager.ts).\",\n );\n }\n const prefix = `obsidian-fs://${ctx.vaultName}/`;\n const update = db.prepare(`\n UPDATE notes\n SET doc_uri = @prefix || path\n WHERE doc_uri IS NULL\n `);\n update.run({ prefix });\n}\n\n/**\n * Migration 009 — audit discriminator for memory-sink writes (MEM-08, Plan 02-06).\n *\n * Adds an `is_memory_sink_write` column to `write_audit` so the audit log\n * can distinguish writes routed under a MemorySink (agent observations,\n * supersede records) from regular user writes. Existing v1.x rows migrate\n * with the default value 0 — they pre-date the memory namespace.\n *\n * A partial index on `(is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1`\n * keeps the common \"show me only memory writes\" filter fast without\n * widening the index footprint for user writes. Per RESEARCH §Q8: partial\n * indexes are the standard SQLite idiom for boolean discriminators where\n * one branch dominates volume.\n *\n * Function-style (not pure SQL) so the column-add is IDEMPOTENT: a test\n * fixture that rewinds `user_version` to replay earlier migrations against\n * a DB whose write_audit already carries the v9 column (because the\n * Database constructor migrated it to head on open) must not crash on a\n * duplicate-column error. The behavior of a clean v8→v9 upgrade is\n * identical to the pure-SQL form: ALTER ADD COLUMN with DEFAULT 0 +\n * partial index creation.\n */\nfunction runMigration009(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n const cols = db.prepare(\"PRAGMA table_info(write_audit)\").all() as Array<{\n name: string;\n }>;\n const hasColumn = cols.some((c) => c.name === \"is_memory_sink_write\");\n if (!hasColumn) {\n db.exec(\"ALTER TABLE write_audit ADD COLUMN is_memory_sink_write INTEGER NOT NULL DEFAULT 0\");\n }\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_write_audit_memory\n ON write_audit(is_memory_sink_write, at DESC)\n WHERE is_memory_sink_write = 1\n `);\n}\n\n/**\n * Migration 010 — Phase 3 (slice 03-01) sections infrastructure.\n *\n * Three ordered steps inside ONE transaction (per plan 03-01):\n * A) `sections` table + 3 indexes (DDL).\n * B) Denormalized `notes.status` column + UPDATE backfill from\n * `json_extract(frontmatter, '$.status')` + partial index\n * `notes_status WHERE status IS NOT NULL`.\n * C) Function-style call to `backfillSectionsFromChunks(db)` —\n * one-time backfill of `sections` rows for existing notes\n * (M2 fix from the plan-checker). Re-derives sections from each\n * note's `content` column, NOT from `chunks.heading_path` (see\n * 03-01-DEVIATIONS.md §D1 for why).\n *\n * Function-style so we can interleave SQL + a TS helper call inside the\n * same transaction. The runner is already inside `db.transaction(...)`\n * at `src/db/database.ts:114` — calling `db.exec` from here participates\n * in that outer transaction by default with better-sqlite3.\n *\n * IDEMPOTENCY: the v1 migration runner only runs migrations whose\n * version > `user_version` so this function executes at most once per\n * DB. As a defence-in-depth measure the steps are still individually\n * idempotent (column-add via PRAGMA introspection; `CREATE TABLE IF\n * NOT EXISTS`; backfill helper short-circuits when rows already exist\n * for a note).\n */\nfunction runMigration010(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n // ── Step A: sections table + 3 indexes ────────────────────────────\n // Use `IF NOT EXISTS` so a fixture replay against a DB whose v10\n // schema already exists does not crash. The composite indexes match\n // the plan's read patterns:\n // - sections_note_anchor: O(log) unique lookup by (note_id, anchor)\n // - sections_note_parent_ord: O(log) tree iteration in get_outline\n // - sections_chunk_range: O(log) chunk → section promotion\n db.exec(`\n CREATE TABLE IF NOT EXISTS sections (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n anchor TEXT NOT NULL,\n heading_path TEXT NOT NULL,\n heading_text TEXT NOT NULL,\n level INTEGER NOT NULL,\n parent_id INTEGER REFERENCES sections(id) ON DELETE CASCADE,\n ord INTEGER NOT NULL,\n chunk_id_first INTEGER REFERENCES chunks(id),\n chunk_id_last INTEGER REFERENCES chunks(id),\n created_at INTEGER NOT NULL\n );\n CREATE UNIQUE INDEX IF NOT EXISTS sections_note_anchor\n ON sections(note_id, anchor);\n CREATE INDEX IF NOT EXISTS sections_note_parent_ord\n ON sections(note_id, parent_id, ord);\n CREATE INDEX IF NOT EXISTS sections_chunk_range\n ON sections(note_id, chunk_id_first, chunk_id_last);\n `);\n\n // ── Step B: notes.status denormalized column (M4 fix) ─────────────\n // Idempotent column-add: check PRAGMA table_info first. `notes.status`\n // is read by 03-05's superseded SQL filter and maintained by the\n // indexer via `NotesQueries.setStatus(noteId, parsedProperties.status\n // ?? null)`.\n const cols = db.prepare(\"PRAGMA table_info(notes)\").all() as Array<{ name: string }>;\n const hasStatus = cols.some((c) => c.name === \"status\");\n if (!hasStatus) {\n db.exec(\"ALTER TABLE notes ADD COLUMN status TEXT\");\n }\n // Backfill `status` from existing JSON-stringified `notes.frontmatter`.\n // `notes.frontmatter` is stored as a JSON string (verified at\n // src/indexer/indexer.ts:168 — `JSON.stringify(parsed.frontmatter)`).\n // `json_extract` handles malformed JSON by returning NULL, so notes\n // with corrupt/missing frontmatter end up with `status: NULL` —\n // exactly the correct behavior.\n db.exec(`\n UPDATE notes\n SET status = json_extract(frontmatter, '$.status')\n WHERE frontmatter IS NOT NULL\n AND status IS NULL\n `);\n // Partial index: tiny footprint, only indexes rows with a non-null\n // status. Most notes have no status — the index stays small even on\n // large vaults.\n db.exec(`\n CREATE INDEX IF NOT EXISTS notes_status\n ON notes(status) WHERE status IS NOT NULL\n `);\n\n // ── Step C: section backfill (M2 fix) ─────────────────────────────\n // Re-derive sections from each note's `content` column. The helper\n // is co-located in `src/sections/backfill.ts` so the migration\n // module stays adapter-import-clean.\n backfillSectionsFromChunks(db);\n}\n\n/**\n * Migration 011 — Phase 4 / 04-01 / GRA-04 (D-01): `edges` table substrate.\n *\n * Lands the typed-edge graph storage that every Phase 4 surface\n * (`expand`, `cluster`, `search_hybrid({expand})`, the widened v1 graph\n * tools, bundle/dossier link entries) reads from. Mirrors the\n * established function-style backfill pattern from `runMigration008`\n * (lines 443–464) and the multi-step DDL+helper pattern from\n * `runMigration010` (lines 531–596).\n *\n * Three steps inside ONE transaction (the runner's outer transaction\n * from `database.ts:118`):\n *\n * A) DDL — `edges` table + 3 indexes. Idempotent (`IF NOT EXISTS`).\n * Columns match D-01 and `Edge.type` union from `src/types.ts:470`:\n * `(id, source_doc, target_doc, target_path, type, rel, anchor,\n * line_number)` with `UNIQUE(source_doc, target_doc, type,\n * anchor)` for `INSERT OR IGNORE` idempotency.\n * FKs: `source_doc REFERENCES notes(id) ON DELETE CASCADE`,\n * `target_doc REFERENCES notes(id) ON DELETE SET NULL`.\n * CHECK constraint on `type` mirrors `Edge.type` verbatim.\n *\n * B) Zero-row short-circuit (mirrors `runMigration008` lines 444–448):\n * if `wikilinks` is empty, skip backfill scan entirely. Keeps fresh\n * `:memory:` test fixtures fast and avoids needless work on\n * vaults that have no v1 wikilinks to migrate.\n *\n * C) Chunked backfill — copies every row from `wikilinks` into\n * `edges` with `type='wikilink'`. Chunked at 10,000 rows per\n * batch (per RESEARCH §Pattern 1 / Pitfall 5). better-sqlite3\n * is synchronous, so a multi-second backfill of a 100k+ wikilink\n * vault must not block the event loop in one statement —\n * chunking keeps each statement bounded. Pagination via\n * `wikilinks.id > @after_id` + `LIMIT @chunk` (Pattern 1).\n * `INSERT OR IGNORE` + the UNIQUE constraint make the backfill\n * idempotent across partial-migration replays.\n *\n * Storage cost: ~doubling on the wikilink subset until v3 cleanup\n * drops the `wikilinks` table. Acceptable per D-01.\n *\n * No `fs`, `path.join`, or `gray-matter` imports anywhere in this\n * function (adapter-seam discipline, ADR-002).\n */\nfunction runMigration011(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n // ── Step A: DDL ──────────────────────────────────────────────────────\n //\n // D-01 names UNIQUE(source_doc, target_doc, type, anchor) but `target_doc`\n // and `anchor` are both nullable, and SQLite's standard UNIQUE constraint\n // treats every NULL as distinct (per the SQL spec the codebase already\n // relies on at `notes(path)` etc.). Without further accommodation,\n // INSERT OR IGNORE would fail to dedupe broken edges (target_doc IS NULL,\n // anchor IS NULL — two rows with the same source+type would both insert).\n //\n // The fix: a UNIQUE INDEX with COALESCE on the nullable columns. This is\n // the standard SQLite idiom for \"treat NULL as equal for dedup\" and is\n // semantically identical to D-01's intent.\n //\n // COALESCE(target_doc, -1) — `-1` is safe because `notes.id` is\n // AUTOINCREMENT starting at 1; no real note id can collide.\n // COALESCE(anchor, '') — empty string acts as the \"no anchor\" key;\n // real anchors are non-empty strings (Obsidian wikilink syntax\n // `[[note#section]]` rejects empty `#`).\n //\n // INSERT OR IGNORE consults the unique index for conflict resolution\n // (`ON CONFLICT IGNORE` semantics propagate from any unique constraint\n // or unique index — per SQLite docs §\"INSERT ... OR IGNORE\").\n db.exec(`\n CREATE TABLE IF NOT EXISTS edges (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_doc INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,\n target_doc INTEGER REFERENCES notes(id) ON DELETE SET NULL,\n target_path TEXT,\n type TEXT NOT NULL CHECK (type IN ('wikilink','mention','frontmatter-ref','hyperlink')),\n rel TEXT,\n anchor TEXT,\n line_number INTEGER,\n link_text TEXT\n );\n CREATE UNIQUE INDEX IF NOT EXISTS idx_edges_unique\n ON edges(source_doc, COALESCE(target_doc, -1), type, COALESCE(anchor, ''));\n CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_doc);\n CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_doc);\n CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);\n `);\n\n // ── Step B: zero-row short-circuit ───────────────────────────────────\n // Mirrors runMigration008 lines 444–448. Fresh DBs have no wikilinks\n // to backfill; skip the scan entirely.\n const pending = db.prepare<[], { c: number }>(\"SELECT COUNT(*) AS c FROM wikilinks\").get();\n if (!pending || pending.c === 0) return;\n\n // ── Step C: chunked backfill from wikilinks → edges ──────────────────\n // Chunked at 10k rows (RESEARCH §Pattern 1). Pagination via\n // `wikilinks.id > @after_id ORDER BY id ASC LIMIT @chunk`. INSERT OR\n // IGNORE + UNIQUE(source_doc, target_doc, type, anchor) is idempotent\n // across replays.\n //\n // NOTE: wikilink rows can have `target_path` IS NOT NULL while\n // `target_note` IS NULL (broken wikilinks). Those land in `edges`\n // with `target_doc IS NULL` and `target_path` preserved — `edges`\n // mirrors the unresolved-target convention from `wikilinks`.\n const CHUNK = 10_000;\n const copy = db.prepare(`\n INSERT OR IGNORE INTO edges\n (source_doc, target_doc, target_path, type, rel, anchor, line_number, link_text)\n SELECT source_note, target_note, target_path, 'wikilink', NULL, anchor, line_number, link_text\n FROM wikilinks\n WHERE id > @after_id\n ORDER BY id ASC\n LIMIT @chunk\n `);\n // `nextLastIdAfter(@after_id, @chunk)` returns the wikilinks.id at\n // position @chunk-th row past @after_id, OR undefined if fewer than\n // @chunk rows remain — which signals the final partial chunk.\n const nextLast = db.prepare<[number, number], { id: number }>(\n \"SELECT id FROM wikilinks WHERE id > ? ORDER BY id ASC LIMIT 1 OFFSET ?\",\n );\n\n let lastId = 0;\n while (true) {\n copy.run({ after_id: lastId, chunk: CHUNK });\n const nxt = nextLast.get(lastId, CHUNK - 1);\n if (!nxt) break;\n lastId = nxt.id;\n }\n}\n\n/**\n * Migration 012 — Phase 4 / CR-01: widen `idx_edges_unique` so that\n * legitimate non-duplicate edges no longer collide on `INSERT OR IGNORE`.\n *\n * The original migration-011 unique index was\n * `(source_doc, COALESCE(target_doc, -1), type, COALESCE(anchor, ''))`\n * which silently dropped four classes of distinct rows:\n *\n * 1. Multiple broken wikilinks from the same source (different\n * `target_path` but both have `target_doc IS NULL` + `anchor IS NULL`).\n * 2. Multiple hyperlinks from the same source (`target_doc IS NULL`,\n * `anchor IS NULL` — only one survives per source note).\n * 3. Multiple `frontmatter-ref` edges from the same source to the same\n * target with different `rel` (e.g. `{owner: [[a]], assignee: [[a]]}`).\n * 4. Multi-line `mention` edges to the same target (line_number was not\n * a disambiguator).\n *\n * The widened key includes `target_path`, `rel`, and `line_number` (with\n * `COALESCE` defaults for nulls so SQLite's \"every NULL is distinct\"\n * default does not re-introduce the dedup-failure on the inverse axis).\n *\n * Three steps inside the runner's outer transaction:\n * A) DROP idx_edges_unique. SQLite cannot alter a unique-index\n * definition in place — drop + recreate is the only path.\n * B) CREATE the widened idx_edges_unique. If a re-run finds the wider\n * index already exists (e.g. partial-replay against a DB that was\n * hand-fixed), `IF NOT EXISTS` keeps the migration idempotent.\n * C) Re-run the wikilink backfill from migration 011 (broken-link rows\n * were lost during the narrow-key window between 011 and 012). The\n * backfill uses `INSERT OR IGNORE` against the now-widened key so\n * the rows that already survived stay untouched and the rows that\n * were silently dropped are re-inserted.\n *\n * Cross-table FKs on `edges` are untouched. CHECK constraint on\n * `edges.type` is untouched. Read paths (`getBacklinks`, `getForwardLinks`,\n * `getAllForNodes`) are untouched — they SELECT, never INSERT.\n *\n * Adapter-seam discipline: no `fs`, `path`, `gray-matter`, or `chokidar`\n * imports anywhere in this function.\n */\nfunction runMigration012(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n // ── Step A: drop the narrow index ─────────────────────────────────────\n db.exec(`DROP INDEX IF EXISTS idx_edges_unique`);\n\n // ── Step B: create the widened index ──────────────────────────────────\n //\n // COALESCE defaults:\n // target_doc → -1 (notes.id is AUTOINCREMENT from 1; -1 cannot\n // collide with a real note id)\n // target_path → '' (real target_path values are non-empty strings)\n // rel → '' (real rel values are non-empty per ADR-003)\n // anchor → '' (Obsidian wikilink `[[note#]]` rejects empty)\n // line_number → -1 (real line numbers are 1-based positive ints)\n db.exec(`\n CREATE UNIQUE INDEX IF NOT EXISTS idx_edges_unique\n ON edges(\n source_doc,\n COALESCE(target_doc, -1),\n COALESCE(target_path, ''),\n type,\n COALESCE(rel, ''),\n COALESCE(anchor, ''),\n COALESCE(line_number, -1)\n );\n `);\n\n // ── Step C: re-run the wikilink → edges backfill ──────────────────────\n //\n // Broken-wikilink rows were lost during the narrow-key window because\n // migration 011 used `INSERT OR IGNORE` against a key that collapsed\n // every `(source_note, target_path=*, anchor=NULL)` row to a single\n // edges row. The widened key now distinguishes broken targets by\n // `target_path`. Re-running the same chunked copy with the same\n // `INSERT OR IGNORE` guard is idempotent on the already-correct rows\n // and refills the gaps.\n //\n // Mirrors runMigration011 Step C verbatim (chunked at 10k rows,\n // pagination via wikilinks.id > @after_id ORDER BY id ASC LIMIT\n // @chunk). The zero-row short-circuit also mirrors 011 — fresh DBs\n // do not need the backfill scan.\n const pending = db.prepare<[], { c: number }>(\"SELECT COUNT(*) AS c FROM wikilinks\").get();\n if (!pending || pending.c === 0) return;\n\n const CHUNK = 10_000;\n const copy = db.prepare(`\n INSERT OR IGNORE INTO edges\n (source_doc, target_doc, target_path, type, rel, anchor, line_number, link_text)\n SELECT source_note, target_note, target_path, 'wikilink', NULL, anchor, line_number, link_text\n FROM wikilinks\n WHERE id > @after_id\n ORDER BY id ASC\n LIMIT @chunk\n `);\n const nextLast = db.prepare<[number, number], { id: number }>(\n \"SELECT id FROM wikilinks WHERE id > ? ORDER BY id ASC LIMIT 1 OFFSET ?\",\n );\n\n let lastId = 0;\n while (true) {\n copy.run({ after_id: lastId, chunk: CHUNK });\n const nxt = nextLast.get(lastId, CHUNK - 1);\n if (!nxt) break;\n lastId = nxt.id;\n }\n}\n\n/**\n * Migration 013 — Phase 5 / BRF-* / D-04..D-06 / D-09.\n *\n * Three additive substrates land at this version:\n *\n * A) `chunks.chunk_id_fragment TEXT NOT NULL DEFAULT ''` column +\n * chunked backfill (10k rows per batch, mirrors `runMigration008`).\n * Per D-04/D-05 the fragment is `sha256(NFC(LF-normalized,\n * trimEnd(text))).slice(0,7)`. The canonical computation lives in\n * `src/chunker/chunk-id.ts` so the migration and the chunker share\n * a single source of truth (anti-pattern: scattered createHash\n * calls — see RESEARCH §Pitfall 14).\n *\n * B) `brief_sources(brief_doc_id, chunk_id_fragment, chunk_doc_id,\n * recorded_hash)` reverse-index table per D-06 with\n * UNIQUE(brief_doc_id, chunk_id_fragment) and indexes on\n * `(chunk_doc_id)` and `(chunk_id_fragment)`. Populated on brief\n * write in slice 2 (Plan 05-02); rows deleted on brief\n * delete/supersede. Staleness check on a ChangeEvent for `doc_id D`\n * becomes O(log N) — `SELECT brief_doc_id FROM brief_sources WHERE\n * chunk_doc_id = D AND recorded_hash != <current chunk hash>`.\n *\n * C) `daemon_state(vault_name PRIMARY KEY, last_seen_doc_mtime)` per\n * D-09. Used by the staleness daemon (Plan 05-03) for the hybrid\n * replay strategy: startup full scan (correctness floor) + cursor\n * for steady-state diagnostic (\"is my daemon current?\").\n *\n * Step ordering inside the runner's outer transaction:\n * A.1 — DDL idempotency for `chunks.chunk_id_fragment` column-add\n * (PRAGMA introspection per `runMigration009:489-497`).\n * A.2 — Zero-row short-circuit (mirrors `runMigration008:447-450`)\n * on `COUNT(*) WHERE chunk_id_fragment = ''` so fresh DBs and\n * already-backfilled DBs both skip the scan.\n * A.3 — Chunked backfill at CHUNK = 10_000 (matches\n * `runMigration011:701`). Pagination via `id > @after_id ORDER\n * BY id ASC LIMIT 10000`. Each batch wraps a transaction so a\n * multi-second backfill on a 100k+ chunk vault does not freeze\n * the event loop (better-sqlite3 is synchronous).\n * B. — `CREATE TABLE IF NOT EXISTS brief_sources` + indexes.\n * C. — `CREATE TABLE IF NOT EXISTS daemon_state`.\n *\n * Adapter-seam discipline: no `fs`, `path`, `gray-matter`, or\n * `chokidar` imports anywhere in this function. The chunker helper\n * imported here is itself pure (`src/chunker/chunk-id.ts`).\n */\nfunction runMigration013(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n // ── Step A.1: chunks.chunk_id_fragment column-add (idempotent) ─────\n const cols = db.prepare(\"PRAGMA table_info(chunks)\").all() as Array<{\n name: string;\n }>;\n const hasColumn = cols.some((c) => c.name === \"chunk_id_fragment\");\n if (!hasColumn) {\n db.exec(\"ALTER TABLE chunks ADD COLUMN chunk_id_fragment TEXT NOT NULL DEFAULT ''\");\n }\n\n // ── Step A.2: zero-row short-circuit ──────────────────────────────\n // Skip the backfill scan entirely on fresh `:memory:` DBs and on\n // re-runs against an already-backfilled DB. Mirrors\n // runMigration008:447-450.\n const pending = db\n .prepare<[], { c: number }>(\"SELECT COUNT(*) AS c FROM chunks WHERE chunk_id_fragment = ''\")\n .get();\n if (pending && pending.c > 0) {\n // ── Step A.3: chunked backfill at 10k rows/batch ────────────────\n const CHUNK = 10_000;\n const update = db.prepare(\"UPDATE chunks SET chunk_id_fragment = ? WHERE id = ?\");\n const select = db.prepare<[number], { id: number; text: string }>(\n \"SELECT id, text FROM chunks WHERE id > ? AND chunk_id_fragment = '' ORDER BY id ASC LIMIT 10000\",\n );\n let afterId = 0;\n while (true) {\n const rows = select.all(afterId);\n if (rows.length === 0) break;\n const tx = db.transaction((batch: { id: number; text: string }[]) => {\n for (const row of batch) {\n update.run(computeChunkIdFragment(row.text), row.id);\n }\n });\n tx(rows);\n const last = rows[rows.length - 1];\n if (!last) break;\n afterId = last.id;\n if (rows.length < CHUNK) break;\n }\n }\n\n // ── Step B: brief_sources reverse-index table + indexes ───────────\n db.exec(`\n CREATE TABLE IF NOT EXISTS brief_sources (\n brief_doc_id TEXT NOT NULL,\n chunk_id_fragment TEXT NOT NULL,\n chunk_doc_id TEXT NOT NULL,\n recorded_hash TEXT NOT NULL,\n UNIQUE(brief_doc_id, chunk_id_fragment)\n );\n CREATE INDEX IF NOT EXISTS idx_brief_sources_chunk_doc\n ON brief_sources(chunk_doc_id);\n CREATE INDEX IF NOT EXISTS idx_brief_sources_fragment\n ON brief_sources(chunk_id_fragment);\n `);\n\n // ── Step C: daemon_state single-row-per-vault state ───────────────\n db.exec(`\n CREATE TABLE IF NOT EXISTS daemon_state (\n vault_name TEXT PRIMARY KEY,\n last_seen_doc_mtime INTEGER NOT NULL\n );\n `);\n}\n\n/**\n * Migration 014 — Phase 6 / Q-AUD: contract_audit table.\n *\n * DDL-only (no backfill — contract_audit is greenfield). Mirrors the\n * additive substrate pattern from `runMigration013` (Phase 5, brief_sources).\n *\n * Rationale (Q-AUD): orchestration steps cannot live in `write_audit`\n * because `write_audit.note_id INTEGER NOT NULL` foreign-key constraint\n * blocks rows that don't correspond to a vault note (orchestration rows\n * may reference DocIds, peer-MCP outputs, or load errors with no\n * note_id). Same wall Phase 5 daemon hit per RESEARCH §Don't Hand-Roll.\n *\n * Stores only `{kind, contract, verb, step_alias, vault, ts, error_message}`\n * — never step output payloads (Security pattern §I; Invariant C-5 in\n * ADR-006). Peer-MCP outputs may contain sensitive data; we explicitly\n * do not capture them.\n *\n * Adapter-seam discipline: no `fs`, `path`, `gray-matter`, or `chokidar`\n * imports anywhere in this function.\n */\nfunction runMigration014(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS contract_audit (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL,\n contract TEXT,\n verb TEXT,\n step_alias TEXT,\n vault TEXT,\n ts INTEGER NOT NULL,\n error_message TEXT\n );\n CREATE INDEX IF NOT EXISTS idx_contract_audit_kind_ts\n ON contract_audit(kind, ts);\n CREATE INDEX IF NOT EXISTS idx_contract_audit_verb\n ON contract_audit(verb);\n `);\n}\n\n/**\n * Migration 015 — section identity becomes (note_id, heading_path, anchor).\n *\n * Per ADR-032 (revised): a section's identity is its content PLUS its\n * location/context, not content alone. The original UNIQUE(note_id, anchor)\n * collapsed two byte-identical sibling sections into one row even when they\n * sat under different parent headings (e.g. `# Q1 > ## Risks \"TBD\"` and\n * `# Q2 > ## Risks \"TBD\"`) — discarding the context that distinguishes them.\n *\n * `anchor` stays a pure content hash (ADR-003 H-7 unchanged; brief\n * `source_hashes` per D-05 unaffected). We only widen the UNIQUE key to add\n * `heading_path` (the ancestor chain), so differently-placed sections persist\n * as distinct rows. Genuinely-duplicated content in the SAME context\n * (verbatim repeat under the same parent) still collapses — acceptable.\n *\n * Migration is index-only: drop the old unique index, create the new one.\n * Existing DBs may have already-collapsed rows from the old behavior; this\n * migration cannot resurrect siblings dropped before it ran, but the next\n * `index --full` regenerates them correctly. No data is lost or rewritten.\n *\n * Adapter-seam discipline: no fs/path/gray-matter/chokidar imports.\n */\nfunction runMigration015(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n db.exec(\n \"DROP INDEX IF EXISTS sections_note_anchor; \" +\n \"CREATE UNIQUE INDEX IF NOT EXISTS sections_note_headingpath_anchor \" +\n \"ON sections(note_id, heading_path, anchor);\",\n );\n}\n\n/**\n * Migration 016 — notes.rendered_source_hash (ADR-033).\n *\n * Marks a note whose indexed content came from the Obsidian plugin's RENDERED\n * Datacore/Dataview output rather than the raw file. The value is the source\n * file's hash at render time, so the watcher/CLI can detect when a rendered\n * overlay has gone stale (source changed since the render) and fall back to\n * raw re-indexing. NULL (the default for every existing + raw-indexed row)\n * means \"raw-indexed\" — fully backwards-compatible, no row rewrite.\n *\n * Idempotent: PRAGMA-guard the column-add so a replay against a DB that\n * already has the column is a no-op.\n *\n * Adapter-seam discipline: no fs/path/gray-matter/chokidar imports.\n */\nfunction runMigration016(db: BetterSqlite3Database, _ctx: MigrationContext): void {\n const cols = db.prepare(\"PRAGMA table_info(notes)\").all() as Array<{ name: string }>;\n if (!cols.some((c) => c.name === \"rendered_source_hash\")) {\n db.exec(\"ALTER TABLE notes ADD COLUMN rendered_source_hash TEXT\");\n }\n}\n\nexport const MIGRATIONS: readonly Migration[] = [\n {\n version: 1,\n description: \"initial schema\",\n sql: INITIAL_SCHEMA,\n },\n {\n version: 2,\n description: \"note aliases for wikilink resolution\",\n sql: MIGRATION_002_ALIASES,\n },\n {\n version: 3,\n description: \"fix delete-cascade gaps in wikilinks + write_audit FKs\",\n sql: MIGRATION_003_FIX_DELETE_FKS,\n },\n {\n version: 4,\n description: \"variable embedding dimensions (split embeddings table per dim)\",\n sql: MIGRATION_004_VARIABLE_DIMS,\n },\n {\n version: 5,\n description: \"add partition key on model_id (two models per dim can coexist)\",\n run: runMigration005,\n },\n {\n version: 6,\n description: \"add body_hash for frontmatter-only-change short-circuit\",\n sql: MIGRATION_006_BODY_HASH,\n },\n {\n version: 7,\n description: \"add doc_uri column to notes (Strategy A, additive)\",\n sql: MIGRATION_007_DOC_URI_ADD,\n },\n {\n version: 8,\n description: \"backfill doc_uri from <vault-name>/path\",\n run: runMigration008,\n },\n {\n version: 9,\n description:\n \"audit discriminator — is_memory_sink_write column + partial index (MEM-08, Plan 02-06)\",\n run: runMigration009,\n },\n {\n version: 10,\n description:\n \"sections table + notes.status denormalization + one-time section backfill (Phase 3 / 03-01)\",\n run: runMigration010,\n },\n {\n version: 11,\n description: \"edges table + backfill from wikilinks (Phase 4 / 04-01 / GRA-04)\",\n run: runMigration011,\n },\n {\n version: 12,\n description:\n \"widen idx_edges_unique to include target_path/rel/line_number; re-run wikilink backfill (CR-01)\",\n run: runMigration012,\n },\n {\n version: 13,\n description:\n \"chunks.chunk_id_fragment + brief_sources + daemon_state (Phase 5 / BRF-* / D-04..D-06 / D-09)\",\n run: runMigration013,\n },\n {\n version: 14,\n description: \"contract_audit table — Phase 6 / CON-* / Q-AUD\",\n run: runMigration014,\n },\n {\n version: 15,\n description:\n \"section identity = (note_id, heading_path, anchor) — context-aware, no longer collapse byte-identical siblings in different contexts (ADR-032 revised)\",\n run: runMigration015,\n },\n {\n version: 16,\n description:\n \"notes.rendered_source_hash — overlay marker for plugin-rendered Datacore content (ADR-033)\",\n run: runMigration016,\n },\n];\n","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { NoteRow } from \"../../types.js\";\n\n/**\n * Default cap on `listByPathPrefix` row count. Sized to cover any\n * realistic v2.0.0 sink (sinks hold tens of documents at most). The\n * cap is exposed as a constant so consumers (e.g. `memory-stats`\n * Resource at `src/memory/resources/memory-stats.ts`) can detect\n * when the cap was hit and emit `truncated: true` (IN-03 closure).\n */\nexport const LIST_BY_PATH_PREFIX_DEFAULT_LIMIT = 10_000;\n\nexport interface UpsertNoteInput {\n path: string;\n content: string;\n frontmatter: string | null;\n title: string;\n hash: string;\n /** Body-only SHA-256. Used by indexer's frontmatter-only-change\n * short-circuit (migration 006). */\n bodyHash: string;\n mtime: number;\n wordCount: number;\n /**\n * v2 canonical identifier (plan 01-02 Task 04). When provided, written\n * verbatim into the `doc_uri` column. When omitted but `vaultName` IS\n * provided, the writer synthesizes `obsidian-fs://<vaultName>/<path>`\n * un-encoded. When both are omitted, the column is left NULL — the\n * v8 backfill catches it on the next migration replay.\n *\n * UPDATE semantics: an undefined `docUri` on an existing row PRESERVES\n * the existing value via SQL COALESCE — callers can safely omit the\n * field on edit-style upserts without clobbering data.\n */\n docUri?: string;\n /**\n * Vault name used only to synthesize a default `docUri` when the caller\n * hasn't precomputed one. Indexer / write-path callers that already know\n * the vault SHOULD pass this so new rows ship with doc_uri populated.\n */\n vaultName?: string;\n}\n\nexport class NotesQueries {\n private readonly _selectByPath: BetterSqlite3.Statement<[string], NoteRow>;\n private readonly _selectById: BetterSqlite3.Statement<[number], NoteRow>;\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _update: BetterSqlite3.Statement;\n private readonly _delete: BetterSqlite3.Statement<[string]>;\n private readonly _listAll: BetterSqlite3.Statement<[number, number], NoteRow>;\n private readonly _count: BetterSqlite3.Statement<[], { c: number }>;\n /** Phase 3 / 03-01 (M4): denormalized `notes.status` accessors. */\n private readonly _getStatus: BetterSqlite3.Statement<[number], { status: string | null }>;\n private readonly _setStatus: BetterSqlite3.Statement;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._selectByPath = db.prepare<[string], NoteRow>(\"SELECT * FROM notes WHERE path = ?\");\n this._selectById = db.prepare<[number], NoteRow>(\"SELECT * FROM notes WHERE id = ?\");\n this._insert = db.prepare(`\n INSERT INTO notes (path, content, frontmatter, title, hash, body_hash, doc_uri, mtime, word_count, created_at, updated_at)\n VALUES (@path, @content, @frontmatter, @title, @hash, @body_hash, @doc_uri, @mtime, @word_count, @now, @now)\n `);\n // doc_uri uses COALESCE(@doc_uri, doc_uri) so that a caller passing\n // undefined / null PRESERVES the existing value instead of clobbering it.\n // See UpsertNoteInput.docUri TSDoc and plan 01-02 W3 caveat.\n this._update = db.prepare(`\n UPDATE notes\n SET content = @content,\n frontmatter = @frontmatter,\n title = @title,\n hash = @hash,\n body_hash = @body_hash,\n doc_uri = COALESCE(@doc_uri, doc_uri),\n mtime = @mtime,\n word_count = @word_count,\n updated_at = @now\n WHERE id = @id\n `);\n this._delete = db.prepare(\"DELETE FROM notes WHERE path = ?\");\n this._listAll = db.prepare<[number, number], NoteRow>(\n \"SELECT * FROM notes ORDER BY id LIMIT ? OFFSET ?\",\n );\n this._count = db.prepare<[], { c: number }>(\"SELECT COUNT(*) AS c FROM notes\");\n // Phase 3 / 03-01 (M4): denormalized `notes.status` column accessors.\n // Prepared statements MUST be created AFTER migration v10 added the\n // column. The Database constructor runs migrate() before instantiating\n // any query class (`src/db/database.ts:57`), so this ordering holds.\n this._getStatus = db.prepare<[number], { status: string | null }>(\n \"SELECT status FROM notes WHERE id = ?\",\n );\n this._setStatus = db.prepare(\"UPDATE notes SET status = @status WHERE id = @id\");\n }\n\n upsertByPath(input: UpsertNoteInput): { id: number; isNew: boolean } {\n const existing = this._selectByPath.get(input.path);\n const now = Date.now();\n // doc_uri resolution: explicit > synthesized-from-vaultName > NULL.\n // NULL is acceptable during the Phase 1 dual-column window — migration\n // 008 backfills it on the next replay, and Phase 3+ flips reads.\n const docUri: string | null =\n input.docUri ??\n (input.vaultName !== undefined ? `obsidian-fs://${input.vaultName}/${input.path}` : null);\n if (existing) {\n if (existing.hash === input.hash) {\n return { id: existing.id, isNew: false };\n }\n this._update.run({\n id: existing.id,\n content: input.content,\n frontmatter: input.frontmatter,\n title: input.title,\n hash: input.hash,\n body_hash: input.bodyHash,\n // Pass null when the caller didn't compute one — COALESCE in the\n // UPDATE statement keeps the existing doc_uri intact.\n doc_uri: docUri,\n mtime: input.mtime,\n word_count: input.wordCount,\n now,\n });\n return { id: existing.id, isNew: false };\n }\n const info = this._insert.run({\n path: input.path,\n content: input.content,\n frontmatter: input.frontmatter,\n title: input.title,\n hash: input.hash,\n body_hash: input.bodyHash,\n doc_uri: docUri,\n mtime: input.mtime,\n word_count: input.wordCount,\n now,\n });\n return { id: Number(info.lastInsertRowid), isNew: true };\n }\n\n getById(id: number): NoteRow | null {\n return this._selectById.get(id) ?? null;\n }\n\n getByPath(path: string): NoteRow | null {\n return this._selectByPath.get(path) ?? null;\n }\n\n deleteByPath(path: string): boolean {\n const info = this._delete.run(path);\n return info.changes > 0;\n }\n\n listAll(limit = 1000, offset = 0): NoteRow[] {\n return this._listAll.all(limit, offset);\n }\n\n countAll(): number {\n const row = this._count.get();\n return row?.c ?? 0;\n }\n\n /**\n * Plan 02-06 (MEM-09): count rows whose `path` begins with the given\n * prefix. Used by the `memory-stats` MCP Resource to count documents\n * inside a `MemorySink` (the sink's `resolveToRelativePath` is the\n * prefix, with trailing slash). The path is bound as a parameter; the\n * `prefix` value MUST end with `/` to keep the match well-defined.\n */\n countByPathPrefix(prefix: string): number {\n const row = this.db\n .prepare<\n [string],\n { c: number }\n >(\"SELECT COUNT(*) AS c FROM notes WHERE path LIKE ? ESCAPE '\\\\'\")\n .get(escapeLikePrefix(prefix) + \"%\");\n return row?.c ?? 0;\n }\n\n /**\n * Plan 02-06 (MEM-09): list rows whose `path` begins with the given\n * prefix. Used by the `memory-stats` MCP Resource to aggregate\n * `by_type` / `by_status` counts from the stored frontmatter JSON.\n * Default limit is `LIST_BY_PATH_PREFIX_DEFAULT_LIMIT` (10_000) —\n * sinks are user-scoped and typically hold tens of documents in\n * v2.0.0; the cap exists only as a hedge against pathological sinks.\n * Callers that need to detect cap-hit (e.g. memory-stats `truncated`\n * marker, IN-03) compare `rows.length === LIST_BY_PATH_PREFIX_DEFAULT_LIMIT`.\n */\n listByPathPrefix(prefix: string, limit = LIST_BY_PATH_PREFIX_DEFAULT_LIMIT): NoteRow[] {\n return this.db\n .prepare<\n [string, number],\n NoteRow\n >(\"SELECT * FROM notes WHERE path LIKE ? ESCAPE '\\\\' ORDER BY path LIMIT ?\")\n .all(escapeLikePrefix(prefix) + \"%\", limit);\n }\n\n /**\n * Phase 3 / 03-01 (M4): read the denormalized `notes.status` column.\n * Returns `null` for unknown note IDs or notes with no status. Reads\n * the column directly (avoids re-parsing the JSON frontmatter blob).\n *\n * Maintained in sync with `notes.frontmatter` by the indexer — every\n * write that touches `notes.frontmatter` MUST call `setStatus(...)`\n * immediately after so the column doesn't drift.\n */\n getStatus(noteId: number): string | null {\n const row = this._getStatus.get(noteId);\n return row?.status ?? null;\n }\n\n /**\n * Phase 3 / 03-01 (M4): write the denormalized `notes.status` column.\n * `null` clears the column (frontmatter removed the status key).\n * Returns the number of rows affected (0 for unknown note IDs).\n */\n setStatus(noteId: number, status: string | null): number {\n const info = this._setStatus.run({ id: noteId, status });\n return info.changes;\n }\n\n /**\n * Phase 3 / 03-05 (M4): return the subset of `chunkIds` whose owning\n * note has `notes.status = 'superseded'`. Used by `searchOneVault` to\n * filter the vec0 ANN candidate list at the SQL level after the kNN\n * search (vec0 virtual tables do not support inline JOINs the way\n * FTS5 does).\n *\n * Uses the `notes_status` partial index (migration 010) — superseded\n * notes are rare, so the index is tiny and lookups are cheap.\n *\n * The query parameterizes a variable-length IN clause; we generate\n * the placeholders inline rather than re-preparing the statement\n * because the chunk-id list varies per call. better-sqlite3's\n * `pluck()` returns a flat array of scalar column values when the\n * SELECT projects a single column — we lean on that to avoid an\n * extra map step.\n */\n getSupersededChunkIds(chunkIds: readonly number[]): Set<number> {\n if (chunkIds.length === 0) return new Set<number>();\n // Inline placeholders — chunkIds are int primary keys from our own\n // DB, never user input, so injection risk is zero. Cap the list\n // size defensively at 999 (SQLite's default SQLITE_MAX_VARIABLE_NUMBER\n // floor) — callers asking for more should batch.\n const ids = chunkIds.slice(0, 999);\n const placeholders = ids.map(() => \"?\").join(\",\");\n const sql = `SELECT chunks.id AS chunkId\n FROM chunks\n JOIN notes ON notes.id = chunks.note_id\n WHERE chunks.id IN (${placeholders})\n AND notes.status = 'superseded'`;\n const stmt = this.db.prepare<number[], { chunkId: number }>(sql);\n // better-sqlite3 spread-args want a tuple type; widen via `as` so the\n // variable-length IN list survives strict-mode argument typing.\n const rows = (stmt.all as (...args: number[]) => { chunkId: number }[])(...ids);\n return new Set(rows.map((r) => r.chunkId));\n }\n}\n\n/**\n * Backslash-escape SQLite LIKE wildcards in a vault-relative path prefix\n * so a sink `resolveToRelativePath` containing `%` / `_` / `\\` matches\n * literally. Sinks normally use plain folder names (\"_memory/\"), but\n * defending against pathological inputs costs nothing.\n */\nfunction escapeLikePrefix(prefix: string): string {\n return prefix.replace(/\\\\/g, \"\\\\\\\\\").replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\");\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { ChunkRow } from \"../../types.js\";\nimport { computeChunkIdFragment } from \"../../chunker/chunk-id.js\";\n\nexport interface ChunkInput {\n idx: number;\n text: string;\n headingPath: string | null;\n startOffset: number;\n endOffset: number;\n tokenCount: number;\n /**\n * Phase 5 / D-04 / D-05: content-stable chunk identity fragment.\n * First 7 hex chars of `sha256(NFC(LF-normalized, trimEnd(text)))`.\n *\n * Optional at the type level so existing test fixtures and lightweight\n * call sites can omit it; when omitted, `insertBatch` computes it via\n * the canonical helper (`src/chunker/chunk-id.ts`). Production call\n * sites (indexer, single-indexer) pass an explicit value, which is the\n * preferred path — keeping the helper as the single source of truth\n * (RESEARCH §Pitfall 14: scattered createHash calls are forbidden).\n */\n chunkIdFragment?: string;\n}\n\nexport class ChunksQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deleteByNote: BetterSqlite3.Statement<[number]>;\n private readonly _getByNote: BetterSqlite3.Statement<[number], ChunkRow>;\n private readonly _getById: BetterSqlite3.Statement<[number], ChunkRow>;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._insert = db.prepare(`\n INSERT INTO chunks (note_id, idx, text, heading_path, start_offset, end_offset, token_count, chunk_id_fragment)\n VALUES (@note_id, @idx, @text, @heading_path, @start_offset, @end_offset, @token_count, @chunk_id_fragment)\n `);\n this._deleteByNote = db.prepare(\"DELETE FROM chunks WHERE note_id = ?\");\n this._getByNote = db.prepare<[number], ChunkRow>(\n \"SELECT * FROM chunks WHERE note_id = ? ORDER BY idx\",\n );\n this._getById = db.prepare<[number], ChunkRow>(\"SELECT * FROM chunks WHERE id = ?\");\n }\n\n insertBatch(noteId: number, chunks: ChunkInput[]): number[] {\n const ids: number[] = [];\n const tx = this.db.transaction((cs: ChunkInput[]) => {\n for (const c of cs) {\n const info = this._insert.run({\n note_id: noteId,\n idx: c.idx,\n text: c.text,\n heading_path: c.headingPath,\n start_offset: c.startOffset,\n end_offset: c.endOffset,\n token_count: c.tokenCount,\n // Phase 5 / D-04 / D-05: prefer the caller-supplied fragment\n // (production path: chunker computed it once). Fall back to\n // the canonical helper for legacy / test-only call sites that\n // pre-date the field. The helper is the single source of\n // truth — there is no other place in the codebase that\n // computes `chunk_id_fragment`.\n chunk_id_fragment: c.chunkIdFragment ?? computeChunkIdFragment(c.text),\n });\n ids.push(Number(info.lastInsertRowid));\n }\n });\n tx(chunks);\n return ids;\n }\n\n deleteByNote(noteId: number): number {\n return this._deleteByNote.run(noteId).changes;\n }\n\n getByNote(noteId: number): ChunkRow[] {\n return this._getByNote.all(noteId);\n }\n\n getById(id: number): ChunkRow | null {\n return this._getById.get(id) ?? null;\n }\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\n\nimport type { ModelsQueries } from \"./models.js\";\n\nexport interface EmbeddingInput {\n chunkId: number;\n modelId: number;\n vector: number[];\n}\n\nexport interface SemanticHit {\n chunkId: number;\n distance: number;\n}\n\ninterface ModelStatements {\n insert: BetterSqlite3.Statement;\n deleteByChunk: BetterSqlite3.Statement<[bigint]>;\n deleteAll: BetterSqlite3.Statement;\n search: BetterSqlite3.Statement<[string, number], { chunk_id: number; distance: number }>;\n}\n\n/**\n * sqlite-vec embedding store with one vec0 table per (modelId, dim).\n *\n * Distance metric: vec0 with `FLOAT[N]` uses L2 (Euclidean) distance by\n * default. For cosine similarity, normalize vectors to unit length before\n * insert and at query time — L2 on unit vectors is monotonically equivalent\n * to cosine distance.\n *\n * Layout history:\n * - v1..v3: one global `embeddings(FLOAT[1024])` table.\n * - v4 (Phase 7b): per-dim tables `embeddings_<dim>` so two models with\n * DIFFERENT dims could coexist.\n * - v5 (Phase 7e bugfix): per-MODEL tables `embeddings_m<modelId>_d<dim>`\n * so two models with the SAME dim (e.g. qwen3 + bge-m3, both 1024)\n * can ALSO coexist. The earlier `partition key` attempt turned out\n * not to give us a composite primary key.\n *\n * The caller always passes a `modelId`; the dim is looked up from the\n * `models` table — never inferred from the vector length. Unknown model\n * throws (no silent defaults).\n */\nexport class EmbeddingsQueries {\n private readonly stmtsByModel = new Map<number, ModelStatements>();\n\n constructor(\n private readonly db: BetterSqlite3.Database,\n private readonly models: ModelsQueries,\n ) {}\n\n private tableName(modelId: number, dim: number): string {\n return `embeddings_m${modelId}_d${dim}`;\n }\n\n /**\n * Ensure the vec0 table for this model exists. Idempotent. Called lazily\n * on first use of a model. Tables for an existing pre-v5 dataset are\n * materialized by migration 005.\n */\n ensureTableForModel(modelId: number, dim: number): void {\n if (!Number.isInteger(modelId) || modelId <= 0) {\n throw new Error(`Invalid modelId: ${modelId}`);\n }\n if (!Number.isInteger(dim) || dim <= 0) {\n throw new Error(`Invalid embedding dim: ${dim}`);\n }\n const table = this.tableName(modelId, dim);\n this.db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS ${table} USING vec0(\n chunk_id INTEGER PRIMARY KEY,\n vector FLOAT[${dim}]\n )`,\n );\n }\n\n private dimForModel(modelId: number): number {\n const row = this.models.getById(modelId);\n if (!row) {\n throw new Error(`EmbeddingsQueries: model_id ${modelId} not found in models table`);\n }\n return row.dim;\n }\n\n private getStmts(modelId: number): ModelStatements {\n const cached = this.stmtsByModel.get(modelId);\n if (cached) return cached;\n\n const dim = this.dimForModel(modelId);\n this.ensureTableForModel(modelId, dim);\n const table = this.tableName(modelId, dim);\n const stmts: ModelStatements = {\n insert: this.db.prepare(`INSERT INTO ${table} (chunk_id, vector) VALUES (?, ?)`),\n deleteByChunk: this.db.prepare(`DELETE FROM ${table} WHERE chunk_id = ?`),\n deleteAll: this.db.prepare(`DELETE FROM ${table}`),\n search: this.db.prepare<[string, number], { chunk_id: number; distance: number }>(\n `SELECT chunk_id, distance\n FROM ${table}\n WHERE vector MATCH ? AND k = ?\n ORDER BY distance`,\n ),\n };\n this.stmtsByModel.set(modelId, stmts);\n return stmts;\n }\n\n insertBatch(items: EmbeddingInput[]): void {\n if (items.length === 0) return;\n\n // Group by model_id so each batch hits one prepared statement.\n const byModel = new Map<number, EmbeddingInput[]>();\n for (const x of items) {\n let bucket = byModel.get(x.modelId);\n if (!bucket) {\n bucket = [];\n byModel.set(x.modelId, bucket);\n }\n bucket.push(x);\n }\n\n const tx = this.db.transaction(() => {\n for (const [modelId, xs] of byModel) {\n const stmts = this.getStmts(modelId);\n for (const x of xs) {\n // sqlite-vec vec0 INTEGER PK is strict — BigInt forces SQLite\n // INTEGER instead of REAL.\n stmts.insert.run(BigInt(x.chunkId), serializeVector(x.vector));\n }\n }\n });\n tx();\n }\n\n /**\n * Delete embeddings for a chunk across every registered model — the\n * caller doesn't track which models embedded the chunk.\n */\n deleteByChunk(chunkId: number): void {\n for (const modelId of this.registeredModelIds()) {\n const stmts = this.getStmts(modelId);\n stmts.deleteByChunk.run(BigInt(chunkId));\n }\n }\n\n /**\n * Wipe every embedding row for the given model. Cheap because each\n * model owns its own table — equivalent to `DELETE FROM table`.\n */\n deleteByModel(modelId: number): void {\n const stmts = this.getStmts(modelId);\n stmts.deleteAll.run();\n }\n\n searchSemantic(modelId: number, queryVector: number[], topK: number): SemanticHit[] {\n const dim = this.dimForModel(modelId);\n if (queryVector.length !== dim) {\n throw new Error(\n `searchSemantic: query vector length ${queryVector.length} ` +\n `does not match model ${modelId} dim ${dim}`,\n );\n }\n const stmts = this.getStmts(modelId);\n const rows = stmts.search.all(serializeVector(queryVector), topK);\n return rows.map((r) => ({ chunkId: r.chunk_id, distance: r.distance }));\n }\n\n /**\n * Every model_id with a materialized embeddings table. Read from the\n * model registry — every model that has ever been inserted-into has\n * its table created via `ensureTableForModel`.\n */\n private registeredModelIds(): number[] {\n return this.models.listAll().map((m) => m.id);\n }\n}\n\n/**\n * sqlite-vec accepts vectors as JSON arrays of numbers (text) or as raw\n * little-endian Float32 BLOBs. JSON is simplest and fast enough for our\n * scale; switch to Float32Array.buffer if profiling demands it.\n */\nfunction serializeVector(v: number[]): string {\n return JSON.stringify(v);\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\n\nexport interface WikilinkInput {\n targetPath: string;\n targetNoteId: number | null;\n linkText: string | null;\n anchor: string | null;\n lineNumber: number | null;\n}\n\nexport interface BacklinkRow {\n sourceNoteId: number;\n lineNumber: number | null;\n linkText: string | null;\n}\n\nexport interface ForwardLinkRow {\n targetPath: string;\n targetNoteId: number | null;\n anchor: string | null;\n linkText: string | null;\n}\n\nexport interface BrokenLinkRow {\n sourceNoteId: number;\n targetPath: string;\n}\n\nexport class WikilinksQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deleteByNote: BetterSqlite3.Statement<[number]>;\n private readonly _backlinks: BetterSqlite3.Statement<\n [number],\n { source_note: number; line_number: number | null; link_text: string | null }\n >;\n private readonly _forward: BetterSqlite3.Statement<\n [number],\n {\n target_path: string;\n target_note: number | null;\n anchor: string | null;\n link_text: string | null;\n }\n >;\n private readonly _broken: BetterSqlite3.Statement<\n [],\n { source_note: number; target_path: string }\n >;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._insert = db.prepare(`\n INSERT OR IGNORE INTO wikilinks\n (source_note, target_path, target_note, link_text, anchor, line_number)\n VALUES (@source_note, @target_path, @target_note, @link_text, @anchor, @line_number)\n `);\n this._deleteByNote = db.prepare(\"DELETE FROM wikilinks WHERE source_note = ?\");\n this._backlinks = db.prepare(\n `SELECT source_note, line_number, link_text\n FROM wikilinks\n WHERE target_note = ?`,\n );\n this._forward = db.prepare(\n `SELECT target_path, target_note, anchor, link_text\n FROM wikilinks\n WHERE source_note = ?`,\n );\n this._broken = db.prepare(\n `SELECT source_note, target_path\n FROM wikilinks\n WHERE target_note IS NULL`,\n );\n }\n\n insertBatch(sourceNoteId: number, links: WikilinkInput[]): void {\n const tx = this.db.transaction((xs: WikilinkInput[]) => {\n for (const x of xs) {\n this._insert.run({\n source_note: sourceNoteId,\n target_path: x.targetPath,\n target_note: x.targetNoteId,\n link_text: x.linkText,\n anchor: x.anchor,\n line_number: x.lineNumber,\n });\n }\n });\n tx(links);\n }\n\n deleteByNote(noteId: number): number {\n return this._deleteByNote.run(noteId).changes;\n }\n\n getBacklinks(noteId: number): BacklinkRow[] {\n return this._backlinks.all(noteId).map((r) => ({\n sourceNoteId: r.source_note,\n lineNumber: r.line_number,\n linkText: r.link_text,\n }));\n }\n\n getForwardLinks(noteId: number): ForwardLinkRow[] {\n return this._forward.all(noteId).map((r) => ({\n targetPath: r.target_path,\n targetNoteId: r.target_note,\n anchor: r.anchor,\n linkText: r.link_text,\n }));\n }\n\n resolveBrokenLinks(): BrokenLinkRow[] {\n return this._broken.all().map((r) => ({\n sourceNoteId: r.source_note,\n targetPath: r.target_path,\n }));\n }\n}\n","/**\n * EdgesQueries — Phase 4 / 04-01 / GRA-04 (D-01) typed-edge substrate.\n *\n * Mirrors `src/db/queries/wikilinks.ts` verbatim in structure. Phase 4\n * promotes the v1 wikilink-only graph to a typed-edge graph (the four\n * `Edge.type` literals in `src/types.ts:470`):\n * `wikilink | mention | frontmatter-ref | hyperlink`.\n *\n * v2.0.0 keeps `wikilinks` in place (read-deprecated; Plan 04-02 stops\n * writing to it). All reads from this point forward go through\n * `vault.db.edges.*`; the v1 graph tools (`list_backlinks` /\n * `list_forward_links` / `findBrokenLinks`) are switched in Task 2 of\n * this plan.\n *\n * UPSERT discipline mirrors `wikilinks.ts:52` — `INSERT OR IGNORE`\n * against the widened UNIQUE index (migration 012) over\n * `(source_doc, COALESCE(target_doc, -1), COALESCE(target_path, ''),\n * type, COALESCE(rel, ''), COALESCE(anchor, ''),\n * COALESCE(line_number, -1))` makes re-extraction idempotent.\n *\n * The narrow key originally shipped by migration 011 (just\n * `source_doc, target_doc, type, anchor`) silently dropped legitimate\n * non-duplicate rows: multiple broken wikilinks from the same source,\n * multiple hyperlinks from the same source, multiple `frontmatter-ref`\n * edges with different `rel`, and multi-line mentions all collided.\n * Migration 012 widens the key to include the disambiguators and\n * re-runs the wikilink backfill to recover rows lost during the\n * narrow-key window.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\n\nimport type { Edge } from \"../../types.js\";\n\n/**\n * Edge.type union re-exported as `EdgeType` for ergonomic use at the\n * query namespace + barrel layer. The canonical definition stays in\n * `src/types.ts:470` (ADR-003); this is a strict re-export so any\n * future widening propagates without touching downstream call sites.\n */\nexport type EdgeType = Edge[\"type\"];\n\nexport interface EdgeInput {\n /** Resolved target note id, or `null` for unresolved targets. */\n targetNoteId: number | null;\n /**\n * Raw target string for unresolved edges (dangling wikilinks,\n * hyperlink URLs, frontmatter-ref strings that don't match a known\n * doc). Mirrors `wikilinks.target_path`. May be `null` only when\n * `targetNoteId` is set.\n */\n targetPath: string | null;\n type: EdgeType;\n /** ADR-003 `Edge.rel` — optional adapter-specific sub-classifier. */\n rel: string | null;\n /** Section anchor for wikilinks (`[[target#section]]`). */\n anchor: string | null;\n lineNumber: number | null;\n /**\n * Optional display text from the source (e.g., wikilink alias\n * `[[target|display text]]`). Carried through from the v1\n * `wikilinks.link_text` column so the graph-tool result shape is\n * preserved post-04-01 read switch.\n */\n linkText: string | null;\n}\n\nexport interface EdgeBacklinkRow {\n sourceNoteId: number;\n type: EdgeType;\n anchor: string | null;\n lineNumber: number | null;\n linkText: string | null;\n}\n\nexport interface EdgeForwardLinkRow {\n targetPath: string | null;\n targetNoteId: number | null;\n type: EdgeType;\n anchor: string | null;\n lineNumber: number | null;\n linkText: string | null;\n}\n\nexport interface EdgeBrokenLinkRow {\n sourceNoteId: number;\n targetPath: string | null;\n type: EdgeType;\n lineNumber: number | null;\n}\n\n/**\n * Row shape returned by `getAllForNodes` — Phase 4 / 04-05 / GRA-02.\n *\n * Carries the full edge metadata needed by `cluster()` to build an\n * undirected graphology graph (source + target DocIds), collapse\n * parallel edges by `(min(src,tgt), max(src,tgt))`, skip self-loops,\n * and pass the result into Louvain.\n */\nexport interface EdgeRowFull {\n sourceDoc: number;\n targetDoc: number;\n type: EdgeType;\n anchor: string | null;\n lineNumber: number | null;\n}\n\nexport class EdgesQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deleteByNote: BetterSqlite3.Statement<[number]>;\n private readonly _backlinks: BetterSqlite3.Statement<\n [number],\n {\n source_doc: number;\n type: EdgeType;\n anchor: string | null;\n line_number: number | null;\n link_text: string | null;\n }\n >;\n private readonly _forward: BetterSqlite3.Statement<\n [number],\n {\n target_doc: number | null;\n target_path: string | null;\n type: EdgeType;\n anchor: string | null;\n line_number: number | null;\n link_text: string | null;\n }\n >;\n private readonly _broken: BetterSqlite3.Statement<\n [],\n {\n source_doc: number;\n target_path: string | null;\n type: EdgeType;\n line_number: number | null;\n }\n >;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._insert = db.prepare(`\n INSERT OR IGNORE INTO edges\n (source_doc, target_doc, target_path, type, rel, anchor, line_number, link_text)\n VALUES (@source_doc, @target_doc, @target_path, @type, @rel, @anchor, @line_number, @link_text)\n `);\n this._deleteByNote = db.prepare(\"DELETE FROM edges WHERE source_doc = ?\");\n this._backlinks = db.prepare(\n `SELECT source_doc, type, anchor, line_number, link_text\n FROM edges\n WHERE target_doc = ?`,\n );\n this._forward = db.prepare(\n `SELECT target_doc, target_path, type, anchor, line_number, link_text\n FROM edges\n WHERE source_doc = ?`,\n );\n this._broken = db.prepare(\n `SELECT source_doc, target_path, type, line_number\n FROM edges\n WHERE target_doc IS NULL`,\n );\n }\n\n insertBatch(sourceNoteId: number, edges: EdgeInput[]): void {\n const tx = this.db.transaction((xs: EdgeInput[]) => {\n for (const x of xs) {\n this._insert.run({\n source_doc: sourceNoteId,\n target_doc: x.targetNoteId,\n target_path: x.targetPath,\n type: x.type,\n rel: x.rel,\n anchor: x.anchor,\n line_number: x.lineNumber,\n link_text: x.linkText,\n });\n }\n });\n tx(edges);\n }\n\n deleteByNote(noteId: number): number {\n return this._deleteByNote.run(noteId).changes;\n }\n\n /**\n * Get inbound edges where `target_doc = noteId`.\n *\n * Phase 4 / 04-03 (GRA-01 / D-08): the optional `edgeTypes` filter\n * narrows the result to rows matching one of the listed types. The\n * filter is passed through as parameterized placeholders in an\n * `IN (?, ?, …)` clause; `EdgeType` is a closed Zod-validated union\n * (4 strings), so SQL injection is not a vector. When `edgeTypes` is\n * `undefined` or empty, the unfiltered prepared statement is used (no\n * per-call prepare cost — matches the v1 behavior).\n */\n getBacklinks(noteId: number, edgeTypes?: readonly EdgeType[]): EdgeBacklinkRow[] {\n if (!edgeTypes || edgeTypes.length === 0) {\n return this._backlinks.all(noteId).map((r) => ({\n sourceNoteId: r.source_doc,\n type: r.type,\n anchor: r.anchor,\n lineNumber: r.line_number,\n linkText: r.link_text,\n }));\n }\n // Dynamic IN-clause; EdgeType is a closed union, so the placeholder\n // count is bounded and the parameters are bound — no string concat\n // of user data. T-04-03-04 mitigation.\n const placeholders = edgeTypes.map(() => \"?\").join(\", \");\n const stmt = this.db.prepare<\n [number, ...EdgeType[]],\n {\n source_doc: number;\n type: EdgeType;\n anchor: string | null;\n line_number: number | null;\n link_text: string | null;\n }\n >(\n `SELECT source_doc, type, anchor, line_number, link_text\n FROM edges\n WHERE target_doc = ? AND type IN (${placeholders})`,\n );\n return stmt.all(noteId, ...edgeTypes).map((r) => ({\n sourceNoteId: r.source_doc,\n type: r.type,\n anchor: r.anchor,\n lineNumber: r.line_number,\n linkText: r.link_text,\n }));\n }\n\n /**\n * Get outbound edges where `source_doc = noteId`.\n *\n * Phase 4 / 04-03 (GRA-01 / D-08): optional `edgeTypes` filter — see\n * `getBacklinks` for the SQL injection / closed-union rationale.\n * Hyperlink rows return `target_doc=null` + raw URL in `target_path`;\n * callers iterating for BFS traversal SKIP those (Phase 4 BFS only\n * traverses resolved edges).\n */\n getForwardLinks(noteId: number, edgeTypes?: readonly EdgeType[]): EdgeForwardLinkRow[] {\n if (!edgeTypes || edgeTypes.length === 0) {\n return this._forward.all(noteId).map((r) => ({\n targetPath: r.target_path,\n targetNoteId: r.target_doc,\n type: r.type,\n anchor: r.anchor,\n lineNumber: r.line_number,\n linkText: r.link_text,\n }));\n }\n const placeholders = edgeTypes.map(() => \"?\").join(\", \");\n const stmt = this.db.prepare<\n [number, ...EdgeType[]],\n {\n target_doc: number | null;\n target_path: string | null;\n type: EdgeType;\n anchor: string | null;\n line_number: number | null;\n link_text: string | null;\n }\n >(\n `SELECT target_doc, target_path, type, anchor, line_number, link_text\n FROM edges\n WHERE source_doc = ? AND type IN (${placeholders})`,\n );\n return stmt.all(noteId, ...edgeTypes).map((r) => ({\n targetPath: r.target_path,\n targetNoteId: r.target_doc,\n type: r.type,\n anchor: r.anchor,\n lineNumber: r.line_number,\n linkText: r.link_text,\n }));\n }\n\n resolveBrokenLinks(): EdgeBrokenLinkRow[] {\n return this._broken.all().map((r) => ({\n sourceNoteId: r.source_doc,\n targetPath: r.target_path,\n type: r.type,\n lineNumber: r.line_number,\n }));\n }\n\n /**\n * Phase 4 / 04-05 / GRA-02 — return ALL resolved edges whose BOTH\n * endpoints (`source_doc` AND `target_doc`) lie inside the input\n * `noteIds` set. Unresolved edges (`target_doc IS NULL`) are excluded\n * — `cluster()` operates only on the resolved-DocId graph.\n *\n * The implementation uses a dynamic `IN (?, ?, …)` clause on both the\n * source and target columns; the placeholders are integer noteIds, so\n * there is no SQL-injection vector (the input type is `number[]`, not\n * caller-supplied strings). The statement is NOT cached because the\n * placeholder count varies per call and this method is invoked at most\n * once per `cluster()` call.\n *\n * Self-loops are not filtered here because the `edges` table does not\n * store them (the indexer skips `source === target`); `cluster()`\n * defensively filters at graph-build time anyway (Plan 04-05 task 2).\n *\n * Empty input → empty output (no SQL executed). Single-node input →\n * empty output (no in-set edge possible because target ∉ {noteId}).\n */\n getAllForNodes(noteIds: readonly number[]): EdgeRowFull[] {\n if (noteIds.length === 0) return [];\n const placeholders = noteIds.map(() => \"?\").join(\", \");\n const sql = `\n SELECT source_doc, target_doc, type, anchor, line_number\n FROM edges\n WHERE source_doc IN (${placeholders})\n AND target_doc IN (${placeholders})\n AND target_doc IS NOT NULL\n `;\n const stmt = this.db.prepare<\n number[],\n {\n source_doc: number;\n target_doc: number;\n type: EdgeType;\n anchor: string | null;\n line_number: number | null;\n }\n >(sql);\n return stmt.all(...noteIds, ...noteIds).map((r) => ({\n sourceDoc: r.source_doc,\n targetDoc: r.target_doc,\n type: r.type,\n anchor: r.anchor,\n lineNumber: r.line_number,\n }));\n }\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { IndexRunRow, WriteAuditRow } from \"../types.js\";\n\nexport interface StartRunInput {\n runId: string;\n vaultName: string;\n modelId: number | null;\n trigger: string;\n}\n\nexport interface FinishRunStats {\n notesIndexed: number;\n chunksCreated: number;\n notesUpdated: number;\n notesDeleted: number;\n error?: string;\n}\n\nexport interface RecordWriteInput {\n noteId: number;\n op: \"create\" | \"update\" | \"delete\";\n previousHash: string | null;\n newHash: string | null;\n expectedHash: string | null;\n clientId: string | null;\n diffSummary: string | null;\n /**\n * Plan 02-06 (MEM-08): true iff this write was routed under a MemorySink\n * (agent observation / supersede), false for regular user writes. Stored\n * as INTEGER 1/0 via migration 009's `is_memory_sink_write` column.\n * Defaults to false when omitted — preserves Phase 1 call sites that\n * have not yet been threaded with the sink-derived flag.\n */\n isMemorySinkWrite?: boolean;\n}\n\nexport interface ListWritesFilter {\n noteId?: number;\n op?: string;\n since?: number;\n limit?: number;\n /**\n * Plan 02-06 (MEM-08): filter to memory-sink writes only (`true`) or\n * non-memory writes only (`false`). Omit to include all rows (default,\n * preserves Phase 1 v1 audit_log behavior). Uses the partial index\n * `idx_write_audit_memory` for the `true` branch.\n */\n isMemorySinkWrite?: boolean;\n}\n\nexport class AuditQueries {\n private readonly _startRun: BetterSqlite3.Statement;\n private readonly _finishRun: BetterSqlite3.Statement;\n private readonly _listRuns: BetterSqlite3.Statement<[number], IndexRunRow>;\n private readonly _recordWrite: BetterSqlite3.Statement;\n private readonly _isIndexing: BetterSqlite3.Statement<[], { c: number }>;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._startRun = db.prepare(`\n INSERT INTO index_runs (run_id, vault_name, model_id, started_at, trigger)\n VALUES (@run_id, @vault_name, @model_id, @started_at, @trigger)\n `);\n this._finishRun = db.prepare(`\n UPDATE index_runs\n SET finished_at = @finished_at,\n notes_indexed = @notes_indexed,\n chunks_created = @chunks_created,\n notes_updated = @notes_updated,\n notes_deleted = @notes_deleted,\n error = @error\n WHERE run_id = @run_id\n `);\n this._listRuns = db.prepare<[number], IndexRunRow>(\n \"SELECT * FROM index_runs ORDER BY id DESC LIMIT ?\",\n );\n // True iff there is at least one unfinished run in the audit log.\n // Used by the search layer to avoid surfacing chunks from a vault\n // whose embeddings are mid-flight (see search/scope.ts).\n this._isIndexing = db.prepare<[], { c: number }>(\n \"SELECT COUNT(*) AS c FROM index_runs WHERE finished_at IS NULL\",\n );\n this._recordWrite = db.prepare(`\n INSERT INTO write_audit (note_id, op, previous_hash, new_hash, expected_hash, client_id, diff_summary, at, is_memory_sink_write)\n VALUES (@note_id, @op, @previous_hash, @new_hash, @expected_hash, @client_id, @diff_summary, @at, @is_memory_sink_write)\n `);\n }\n\n startRun(input: StartRunInput): number {\n const info = this._startRun.run({\n run_id: input.runId,\n vault_name: input.vaultName,\n model_id: input.modelId,\n started_at: Date.now(),\n trigger: input.trigger,\n });\n return Number(info.lastInsertRowid);\n }\n\n finishRun(runId: string, stats: FinishRunStats): void {\n this._finishRun.run({\n run_id: runId,\n finished_at: Date.now(),\n notes_indexed: stats.notesIndexed,\n chunks_created: stats.chunksCreated,\n notes_updated: stats.notesUpdated,\n notes_deleted: stats.notesDeleted,\n error: stats.error ?? null,\n });\n }\n\n listRuns(limit = 50): IndexRunRow[] {\n return this._listRuns.all(limit);\n }\n\n /** True iff at least one index_runs row in this vault has finished_at IS NULL. */\n isIndexing(): boolean {\n return (this._isIndexing.get()?.c ?? 0) > 0;\n }\n\n recordWrite(input: RecordWriteInput): void {\n this._recordWrite.run({\n note_id: input.noteId,\n op: input.op,\n previous_hash: input.previousHash,\n new_hash: input.newHash,\n expected_hash: input.expectedHash,\n client_id: input.clientId,\n diff_summary: input.diffSummary,\n at: Date.now(),\n // Phase 1 call sites that have not been threaded with the flag default\n // to 0 (non-memory write) — backwards-compatible with migration 009's\n // ALTER default. Memory-routed writes (record_observation, supersede)\n // pass `isMemorySinkWrite: true`.\n is_memory_sink_write: input.isMemorySinkWrite ? 1 : 0,\n });\n }\n\n listWrites(filter: ListWritesFilter = {}): WriteAuditRow[] {\n const where: string[] = [];\n const params: (string | number)[] = [];\n if (filter.noteId !== undefined) {\n where.push(\"note_id = ?\");\n params.push(filter.noteId);\n }\n if (filter.op !== undefined) {\n where.push(\"op = ?\");\n params.push(filter.op);\n }\n if (filter.since !== undefined) {\n where.push(\"at >= ?\");\n params.push(filter.since);\n }\n if (filter.isMemorySinkWrite !== undefined) {\n where.push(\"is_memory_sink_write = ?\");\n params.push(filter.isMemorySinkWrite ? 1 : 0);\n }\n const limit = filter.limit ?? 100;\n const whereSql = where.length > 0 ? `WHERE ${where.join(\" AND \")}` : \"\";\n const sql = `SELECT * FROM write_audit ${whereSql} ORDER BY id DESC LIMIT ?`;\n params.push(limit);\n return this.db.prepare<typeof params, WriteAuditRow>(sql).all(...params);\n }\n\n /**\n * Plan 02-06 (MEM-09): epoch-ms timestamp of the most recent memory-sink\n * write to a note whose path begins with `pathPrefix`, or `null` if no\n * such row exists. Backed by the `idx_write_audit_memory` partial index\n * (migration 009).\n *\n * Looks up via the `notes.path` value joined to `write_audit.note_id`.\n * Returns null when the note row was hard-deleted (FK SET NULL) or\n * when no audit row matches.\n */\n lastMemoryWriteAtForPathPrefix(pathPrefix: string): number | null {\n const row = this.db\n .prepare<[string], { at: number }>(\n `SELECT wa.at AS at\n FROM write_audit AS wa\n JOIN notes AS n ON n.id = wa.note_id\n WHERE wa.is_memory_sink_write = 1\n AND n.path LIKE ? ESCAPE '\\\\'\n ORDER BY wa.at DESC\n LIMIT 1`,\n )\n .get(escapeAuditLikePrefix(pathPrefix) + \"%\");\n return row?.at ?? null;\n }\n}\n\n/** Mirror of notes.ts `escapeLikePrefix` — local copy to avoid a cross-file dep. */\nfunction escapeAuditLikePrefix(prefix: string): string {\n return prefix.replace(/\\\\/g, \"\\\\\\\\\").replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\");\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { ModelRow } from \"../../types.js\";\n\nexport interface UpsertModelInput {\n name: string;\n provider: string;\n dim: number;\n /** When true (default), newly-inserted rows are marked active=1, matching\n * the historical contract: the first model to index a vault is the\n * active one. Set to false to register a shadow / secondary model\n * without disturbing the currently-active one. Existing rows keep\n * their active flag — upsert never flips active. */\n active?: boolean;\n}\n\nexport class ModelsQueries {\n private readonly _selectByName: BetterSqlite3.Statement<[string], ModelRow>;\n private readonly _selectActive: BetterSqlite3.Statement<[], ModelRow>;\n private readonly _selectById: BetterSqlite3.Statement<[number], ModelRow>;\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deactivateAll: BetterSqlite3.Statement;\n private readonly _activate: BetterSqlite3.Statement<[number]>;\n private readonly _listAll: BetterSqlite3.Statement<[], ModelRow>;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._selectByName = db.prepare<[string], ModelRow>(\"SELECT * FROM models WHERE name = ?\");\n this._selectActive = db.prepare<[], ModelRow>(\n \"SELECT * FROM models WHERE active = 1 ORDER BY id DESC LIMIT 1\",\n );\n this._selectById = db.prepare<[number], ModelRow>(\"SELECT * FROM models WHERE id = ?\");\n this._insert = db.prepare(`\n INSERT INTO models (name, provider, dim, created_at, active)\n VALUES (@name, @provider, @dim, @created_at, @active)\n `);\n this._deactivateAll = db.prepare(\"UPDATE models SET active = 0\");\n this._activate = db.prepare<[number]>(\"UPDATE models SET active = 1 WHERE id = ?\");\n this._listAll = db.prepare<[], ModelRow>(\"SELECT * FROM models ORDER BY id\");\n }\n\n upsert(input: UpsertModelInput): ModelRow {\n const existing = this._selectByName.get(input.name);\n if (existing) return existing;\n const info = this._insert.run({\n name: input.name,\n provider: input.provider,\n dim: input.dim,\n created_at: Date.now(),\n active: input.active === false ? 0 : 1,\n });\n const row = this._selectById.get(Number(info.lastInsertRowid));\n if (!row) {\n throw new Error(\"models.upsert: row vanished after insert\");\n }\n return row;\n }\n\n getById(modelId: number): ModelRow | null {\n return this._selectById.get(modelId) ?? null;\n }\n\n getByName(name: string): ModelRow | null {\n return this._selectByName.get(name) ?? null;\n }\n\n getActive(): ModelRow | null {\n return this._selectActive.get() ?? null;\n }\n\n setActive(modelId: number): void {\n const tx = this.db.transaction(() => {\n this._deactivateAll.run();\n this._activate.run(modelId);\n });\n tx();\n }\n\n listAll(): ModelRow[] {\n return this._listAll.all();\n }\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\n\nexport interface BM25Hit {\n chunkId: number;\n /**\n * Positive relevance score (higher = better). SQLite FTS5 `bm25()` returns a\n * negative number — we flip the sign for downstream consumers so the score\n * is monotonically increasing in \"goodness of match\".\n */\n score: number;\n /** Optional snippet of the chunk with the query terms highlighted. */\n snippet?: string;\n}\n\ninterface BM25Row {\n chunkId: number;\n score: number;\n}\n\ninterface BM25RowWithSnippet extends BM25Row {\n snippet: string;\n}\n\n/**\n * Full-text BM25 search over `chunks_fts` (the FTS5 virtual table mirroring\n * `chunks.text` — see `INITIAL_SCHEMA`). The triggers on `chunks` keep the\n * index in sync automatically, so consumers only need to insert chunks the\n * usual way and can search here.\n */\nexport class FtsQueries {\n private readonly _search: BetterSqlite3.Statement<[string, number], BM25Row>;\n private readonly _searchWithSnippet: BetterSqlite3.Statement<\n [string, number],\n BM25RowWithSnippet\n >;\n /**\n * Phase 3 / 03-05 (M4 fix): same FTS5 BM25 search but JOINed against\n * `chunks → notes` so the candidate list excludes any chunk whose\n * owning note has `notes.status = 'superseded'`.\n *\n * Filter runs at the SQL level so the v1-default path (which passes\n * `excludeSuperseded = false` from `searchOneVault`) is byte-identical\n * to v1, and the new default-hide path performs zero per-candidate\n * frontmatter parses. The `notes_status` partial index from migration\n * 010 keeps the JOIN cheap (only rows with a non-null status are\n * indexed).\n */\n private readonly _searchExclSup: BetterSqlite3.Statement<[string, number], BM25Row>;\n\n constructor(db: BetterSqlite3.Database) {\n this._search = db.prepare<[string, number], BM25Row>(\n `SELECT rowid AS chunkId, bm25(chunks_fts) AS score\n FROM chunks_fts\n WHERE chunks_fts MATCH ?\n ORDER BY bm25(chunks_fts) ASC\n LIMIT ?`,\n );\n this._searchWithSnippet = db.prepare<[string, number], BM25RowWithSnippet>(\n `SELECT\n rowid AS chunkId,\n bm25(chunks_fts) AS score,\n snippet(chunks_fts, 0, '<mark>', '</mark>', '...', 64) AS snippet\n FROM chunks_fts\n WHERE chunks_fts MATCH ?\n ORDER BY bm25(chunks_fts) ASC\n LIMIT ?`,\n );\n // 03-05 M4: SQL-level superseded filter. The JOIN against\n // `chunks → notes` references the denormalized `notes.status` column\n // (migration 010 part B) so we never re-parse the JSON frontmatter\n // blob for filtering. `notes.status IS NULL` covers notes with no\n // frontmatter status (the common case) — those are NOT superseded.\n this._searchExclSup = db.prepare<[string, number], BM25Row>(\n `SELECT chunks_fts.rowid AS chunkId, bm25(chunks_fts) AS score\n FROM chunks_fts\n JOIN chunks ON chunks.id = chunks_fts.rowid\n JOIN notes ON notes.id = chunks.note_id\n WHERE chunks_fts MATCH ?\n AND (notes.status IS NULL OR notes.status != 'superseded')\n ORDER BY bm25(chunks_fts) ASC\n LIMIT ?`,\n );\n }\n\n /**\n * Run BM25 over `chunks_fts`.\n *\n * @param query user query (sanitized internally)\n * @param topK max rows to return\n * @param withSnippet when true, include FTS5 `snippet(...)` output\n * (mutually exclusive with excludeSuperseded —\n * snippets are debug/UI only, not the search path)\n * @param excludeSuperseded when true (03-05 M4), JOIN-and-filter against\n * `notes.status` so candidates from superseded\n * docs never reach the caller. v1-default path\n * passes `false` and stays byte-identical.\n */\n search(query: string, topK: number, withSnippet = false, excludeSuperseded = false): BM25Hit[] {\n const sanitized = FtsQueries.sanitize(query);\n if (sanitized.length === 0) return [];\n\n if (withSnippet) {\n // Snippet path is debug/UI only — keep it on the v1 statement so\n // 03-05 doesn't need to prepare a third statement just for the\n // rarely-used branch. If a future caller needs `snippet + exclude\n // superseded`, prepare a fourth statement here.\n const rows = this._searchWithSnippet.all(sanitized, topK);\n return rows.map((r) => ({\n chunkId: r.chunkId,\n score: -r.score,\n snippet: r.snippet,\n }));\n }\n const stmt = excludeSuperseded ? this._searchExclSup : this._search;\n const rows = stmt.all(sanitized, topK);\n return rows.map((r) => ({ chunkId: r.chunkId, score: -r.score }));\n }\n\n /**\n * Conservative sanitizer for FTS5 MATCH input.\n *\n * Strategy: strip characters that have special FTS5 meaning when the user\n * likely didn't intend them, while preserving advanced syntax for users\n * who know what they're doing (AND/OR/NOT, NEAR, trailing `*` prefix).\n *\n * - Double quotes are removed unless balanced (unbalanced quote → phrase\n * parse error). We strip them all unconditionally to keep this simple\n * and predictable — phrase queries can be re-introduced by callers that\n * construct queries programmatically.\n * - Parentheses are kept only when balanced; otherwise stripped.\n * - Colons (column filters) are stripped — `chunks_fts` only has one\n * column, so column filters are never useful and cause errors.\n * - Tokens containing FTS5-reserved punctuation that doesn't have a sane\n * meaning here (`-`, `/`, `?`, `.`, `!`) are wrapped in double quotes so\n * FTS5 treats them as literal phrases. This is what makes natural\n * queries like \"LAG-EPIX\", \"Netzwerk/Personen\", or \"Wer ist X?\" work.\n * See the v0.6.0 retrieval eval (vault note `_research/vault-memory-eval.md`)\n * for the discovered crash triggers.\n * - Leading operator tokens at fragment boundaries are dropped (FTS5\n * errors on a trailing `AND`/`OR`).\n * - Whitespace is normalized.\n *\n * If the cleaned result is empty, returns \"\".\n */\n static sanitize(userQuery: string): string {\n let s = userQuery.replace(/\"/g, \" \").replace(/:/g, \" \");\n\n // Balance parens — if mismatched, strip all parens.\n let depth = 0;\n let balanced = true;\n for (const ch of s) {\n if (ch === \"(\") depth++;\n else if (ch === \")\") {\n depth--;\n if (depth < 0) {\n balanced = false;\n break;\n }\n }\n }\n if (!balanced || depth !== 0) {\n s = s.replace(/[()]/g, \" \");\n }\n\n // Normalize whitespace.\n s = s.replace(/\\s+/g, \" \").trim();\n if (s.length === 0) return \"\";\n\n // Drop trailing operator tokens that would error.\n const trailingOpRe = /\\s+(AND|OR|NOT|NEAR)$/;\n while (trailingOpRe.test(s)) {\n s = s.replace(trailingOpRe, \"\");\n }\n // Drop leading operator tokens.\n s = s.replace(/^(AND|OR|NOT|NEAR)\\s+/, \"\");\n s = s.trim();\n if (s.length === 0) return \"\";\n\n // Phrase-wrap any token that contains FTS5-meaningful punctuation. Keep\n // operator keywords (AND/OR/NOT/NEAR) and lone wildcards (*) untouched\n // so power-user syntax still works. Tokens that *contain* a wildcard\n // alongside other content (e.g. \"foo*bar\") are phrase-wrapped — the\n // prefix-match semantics only fire on a token-trailing star anyway.\n //\n // The character class matches: hyphen, slash, dot, question mark,\n // exclamation, backslash. Asterisks are handled separately below.\n const needsPhrase = /[-/.?!\\\\]/;\n const isOperator = /^(AND|OR|NOT|NEAR)$/;\n const isPrefixStar = /^[^*\\s]+\\*$/; // \"word*\" — leave alone.\n\n const tokens = s.split(/\\s+/).map((t) => {\n if (t.length === 0) return t;\n if (isOperator.test(t)) return t;\n if (isPrefixStar.test(t)) return t;\n if (needsPhrase.test(t)) return `\"${t}\"`;\n return t;\n });\n\n return tokens.filter((t) => t.length > 0).join(\" \");\n }\n}\n","/**\n * AliasesQueries — note_aliases CRUD + lookup by alias.\n *\n * Case-insensitive matching: `alias_norm` is `alias.trim().toLowerCase()`.\n * Stored separately from the raw alias so display retains the original.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\n\nexport interface AliasResolveHit {\n note_id: number;\n path: string;\n alias: string; // original-case\n}\n\nexport interface AliasListAllRow {\n note_id: number;\n path: string;\n alias: string;\n alias_norm: string;\n}\n\nexport class AliasesQueries {\n private readonly setStmt: BetterSqlite3.Statement<[number, string, string]>;\n private readonly deleteStmt: BetterSqlite3.Statement<[number]>;\n private readonly listForNoteStmt: BetterSqlite3.Statement<[number]>;\n private readonly resolveStmt: BetterSqlite3.Statement<[string]>;\n private readonly listAllStmt: BetterSqlite3.Statement<[]>;\n\n constructor(db: BetterSqlite3.Database) {\n this.setStmt = db.prepare(\n `INSERT OR IGNORE INTO note_aliases (note_id, alias, alias_norm)\n VALUES (?, ?, ?)`,\n );\n this.deleteStmt = db.prepare(`DELETE FROM note_aliases WHERE note_id = ?`);\n this.listForNoteStmt = db.prepare(\n `SELECT alias FROM note_aliases WHERE note_id = ? ORDER BY id ASC`,\n );\n this.resolveStmt = db.prepare(\n `SELECT na.note_id AS note_id, n.path AS path, na.alias AS alias\n FROM note_aliases na\n JOIN notes n ON n.id = na.note_id\n WHERE na.alias_norm = ?\n ORDER BY length(n.path) ASC\n LIMIT 1`,\n );\n // Phase 4 / 04-02 / GRA-04 (D-03): the mention extractor needs the\n // full alias inventory once per indexer run to build the candidate\n // regex. Ordered by alias_norm for deterministic regex alternation\n // (mitigates T-04-02-04 — see plan threat model).\n this.listAllStmt = db.prepare(\n `SELECT na.note_id AS note_id, n.path AS path,\n na.alias AS alias, na.alias_norm AS alias_norm\n FROM note_aliases na\n JOIN notes n ON n.id = na.note_id\n ORDER BY na.alias_norm ASC`,\n );\n }\n\n /**\n * Phase 4 / 04-02 / GRA-04 (D-03): full alias inventory for the\n * mention extractor's per-run candidate set. Result is sorted by\n * `alias_norm` ASC so regex alternation ordering is deterministic\n * across runs (T-04-02-04 mitigation).\n */\n listAll(): AliasListAllRow[] {\n return this.listAllStmt.all() as AliasListAllRow[];\n }\n\n /**\n * Replace all aliases for a note with the given list (atomic).\n * Empty list → clears all aliases for the note.\n */\n setForNote(noteId: number, aliases: readonly string[]): void {\n this.deleteStmt.run(noteId);\n for (const a of aliases) {\n const trimmed = a.trim();\n if (trimmed.length === 0) continue;\n this.setStmt.run(noteId, trimmed, AliasesQueries.normalize(trimmed));\n }\n }\n\n /**\n * Find the note that owns the given alias (case-insensitive).\n * If multiple notes claim the same alias, the one with the shortest\n * path wins (mirrors Obsidian's heuristic).\n */\n resolve(alias: string): AliasResolveHit | null {\n const norm = AliasesQueries.normalize(alias);\n if (norm.length === 0) return null;\n return (this.resolveStmt.get(norm) as AliasResolveHit | undefined) ?? null;\n }\n\n listForNote(noteId: number): string[] {\n const rows = this.listForNoteStmt.all(noteId) as Array<{ alias: string }>;\n return rows.map((r) => r.alias);\n }\n\n static normalize(alias: string): string {\n return alias.trim().toLowerCase();\n }\n}\n","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { InsertSectionRow, SectionRow } from \"../../types.js\";\n\n/**\n * Phase 3 — `sections` table query namespace (migration 010).\n *\n * Mirrors the `ChunksQueries` (`src/db/queries/chunks.ts`) shape:\n * - Prepared statements held as private fields.\n * - `insertMany` batches in a single transaction for amortized cost.\n * - `deleteByNote` matches the chunker's re-index pattern.\n *\n * `parent_id` is the FK pointer derived at insert time from\n * `SectionInfo.parent_index` (an array index) — the caller maps\n * indices → IDs after each row gets its `lastInsertRowid`.\n *\n * `heading_path` is stored as JSON-stringified text (so callers see\n * the storage shape explicitly at the call site).\n */\nexport class SectionsQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deleteByNote: BetterSqlite3.Statement<[number]>;\n private readonly _getByNote: BetterSqlite3.Statement<[number], SectionRow>;\n private readonly _getByAnchor: BetterSqlite3.Statement<[number, string], SectionRow>;\n private readonly _getByIdentity: BetterSqlite3.Statement<[number, string, string], SectionRow>;\n private readonly _findContainingChunk: BetterSqlite3.Statement<\n [number, number, number],\n SectionRow\n >;\n private readonly _countByNote: BetterSqlite3.Statement<[number], { c: number }>;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n // INSERT OR IGNORE: section identity is (note_id, heading_path, anchor)\n // per ADR-032 (revised). Two sibling sections only collide when they are\n // byte-identical (same anchor) AND in the same context (same heading_path)\n // — i.e. genuinely duplicated content in one place. Differently-placed\n // byte-identical sections (e.g. `Q1 > Risks` vs `Q2 > Risks` with the same\n // body) have different heading_path → distinct rows. `OR IGNORE` makes the\n // first sibling win on a true same-context collision; callers needing the\n // surviving id for parent linkage use `insertOneResolving`. Mirrors\n // src/sections/backfill.ts.\n this._insert = db.prepare(`\n INSERT OR IGNORE INTO sections\n (note_id, anchor, heading_path, heading_text, level,\n parent_id, ord, chunk_id_first, chunk_id_last, created_at)\n VALUES\n (@note_id, @anchor, @heading_path, @heading_text, @level,\n @parent_id, @ord, @chunk_id_first, @chunk_id_last, @created_at)\n `);\n this._deleteByNote = db.prepare(\"DELETE FROM sections WHERE note_id = ?\");\n this._getByNote = db.prepare<[number], SectionRow>(\n // parent_id ASC NULLS FIRST lets callers build the tree top-down\n // in one pass. SQLite NULLs sort first by default for ASC.\n \"SELECT * FROM sections WHERE note_id = ? ORDER BY parent_id IS NULL DESC, parent_id ASC, ord ASC\",\n );\n this._getByAnchor = db.prepare<[number, string], SectionRow>(\n \"SELECT * FROM sections WHERE note_id = ? AND anchor = ?\",\n );\n // Collision resolution keys on the FULL identity (note_id, heading_path,\n // anchor) so insertOneResolving finds the exact surviving row, not just\n // any same-anchor sibling in a different context.\n this._getByIdentity = db.prepare<[number, string, string], SectionRow>(\n \"SELECT * FROM sections WHERE note_id = ? AND heading_path = ? AND anchor = ?\",\n );\n this._findContainingChunk = db.prepare<[number, number], SectionRow>(\n // `chunk_id` is monotonically increasing per note; chunk_id_first\n // and chunk_id_last carve disjoint ranges (or both NULL for a\n // heading with no body content). We require both range bounds\n // to be NON-NULL — sections with NULL ranges contain zero chunks.\n `SELECT * FROM sections\n WHERE note_id = ?\n AND chunk_id_first IS NOT NULL\n AND chunk_id_last IS NOT NULL\n AND chunk_id_first <= ?\n AND chunk_id_last >= ?\n ORDER BY (chunk_id_last - chunk_id_first) ASC\n LIMIT 1`,\n );\n this._countByNote = db.prepare<[number], { c: number }>(\n \"SELECT COUNT(*) AS c FROM sections WHERE note_id = ?\",\n );\n }\n\n /**\n * Batch insert. Returns the new `id` for each row in the same order\n * as the input. The transaction wraps the whole batch so a mid-batch\n * failure rolls back cleanly.\n */\n insertMany(rows: InsertSectionRow[]): number[] {\n if (rows.length === 0) return [];\n const ids: number[] = [];\n const now = Date.now();\n const tx = this.db.transaction((rs: InsertSectionRow[]) => {\n for (const r of rs) {\n const info = this._insert.run({\n note_id: r.note_id,\n anchor: r.anchor,\n heading_path: r.heading_path,\n heading_text: r.heading_text,\n level: r.level,\n parent_id: r.parent_id,\n ord: r.ord,\n chunk_id_first: r.chunk_id_first,\n chunk_id_last: r.chunk_id_last,\n created_at: now,\n });\n ids.push(Number(info.lastInsertRowid));\n }\n });\n tx(rows);\n return ids;\n }\n\n /**\n * Insert one section, collision-safe. Returns the id of the row that now\n * owns the identity (note_id, heading_path, anchor): the freshly inserted\n * row, or — when a same-context byte-identical sibling already won the\n * unique slot — that surviving row's id (so callers can resolve parent_id\n * linkage). Per ADR-032 (revised), a collision now requires BOTH same anchor\n * AND same heading_path, so differently-placed identical sections persist as\n * distinct rows. Mirrors src/sections/backfill.ts. The live indexer uses\n * this instead of `insertMany` so duplicate sibling headings can't abort the\n * whole index run (see ISSUE-indexer-duplicate-anchor.md).\n */\n insertOneResolving(r: InsertSectionRow): number | null {\n const info = this._insert.run({\n note_id: r.note_id,\n anchor: r.anchor,\n heading_path: r.heading_path,\n heading_text: r.heading_text,\n level: r.level,\n parent_id: r.parent_id,\n ord: r.ord,\n chunk_id_first: r.chunk_id_first,\n chunk_id_last: r.chunk_id_last,\n created_at: Date.now(),\n });\n if (info.changes > 0) return Number(info.lastInsertRowid);\n // Collision on UNIQUE(note_id, heading_path, anchor): reuse the surviving\n // row's id. Look up by the full identity so we get the exact row, not a\n // same-anchor sibling that lives under a different heading_path.\n const existing = this._getByIdentity.get(r.note_id, r.heading_path, r.anchor);\n return existing ? Number(existing.id) : null;\n }\n\n deleteByNote(noteId: number): number {\n return this._deleteByNote.run(noteId).changes;\n }\n\n /**\n * Returns all sections for the note in tree order: top-level rows\n * (parent_id IS NULL) first, then deeper rows; within the same\n * parent, ord ASC.\n */\n getByNote(noteId: number): SectionRow[] {\n return this._getByNote.all(noteId);\n }\n\n getByAnchor(noteId: number, anchor: string): SectionRow | null {\n return this._getByAnchor.get(noteId, anchor) ?? null;\n }\n\n /**\n * Return the most-specific section whose chunk range contains\n * `chunkId`. \"Most specific\" = smallest range (innermost section).\n */\n findContainingChunk(noteId: number, chunkId: number): SectionRow | null {\n return this._findContainingChunk.get(noteId, chunkId, chunkId) ?? null;\n }\n\n countByNote(noteId: number): number {\n return this._countByNote.get(noteId)?.c ?? 0;\n }\n}\n","/**\n * BriefSourcesQueries — Phase 5 / BRF-* / D-06 reverse-index substrate.\n *\n * Mirrors `src/db/queries/wikilinks.ts` verbatim in structure. Populated\n * when a brief is written via `compile_brief` (one row per chunk in the\n * brief's `source_hashes` map) and removed when the brief is\n * deleted/superseded.\n *\n * UPSERT discipline: `INSERT OR IGNORE` against\n * `UNIQUE(brief_doc_id, chunk_id_fragment)` makes re-population on a\n * partial-state recompile idempotent.\n *\n * Staleness check on a `ChangeEvent` for `doc_id D`:\n * SELECT brief_doc_id FROM brief_sources\n * WHERE chunk_doc_id = D\n * AND recorded_hash != <current chunk hash>\n * → O(log N) lookup instead of O(B·S) scan of every brief's\n * `source_hashes` property.\n *\n * Adapter-seam discipline: no `fs`/`path`/`gray-matter`/`chokidar`\n * imports. `scripts/lint-adapters.sh` enforces.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\n\nexport interface BriefSourceInput {\n /** First 7 hex chars of the chunk's content hash (D-04 / D-05). */\n chunkIdFragment: string;\n /** DocId of the document containing the cited chunk. */\n chunkDocId: string;\n /** Full hash recorded at brief-compile time (`\"sha256:<hex>\"`). */\n recordedHash: string;\n}\n\nexport interface BriefSourceRow {\n briefDocId: string;\n chunkIdFragment: string;\n chunkDocId: string;\n recordedHash: string;\n}\n\nexport class BriefSourcesQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _deleteByBrief: BetterSqlite3.Statement<[string]>;\n private readonly _listBriefDocIds: BetterSqlite3.Statement<[], { brief_doc_id: string }>;\n private readonly _briefsForChunkDoc: BetterSqlite3.Statement<\n [string],\n {\n brief_doc_id: string;\n chunk_id_fragment: string;\n chunk_doc_id: string;\n recorded_hash: string;\n }\n >;\n private readonly _sourcesForBrief: BetterSqlite3.Statement<\n [string],\n {\n brief_doc_id: string;\n chunk_id_fragment: string;\n chunk_doc_id: string;\n recorded_hash: string;\n }\n >;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._insert = db.prepare(`\n INSERT OR IGNORE INTO brief_sources\n (brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash)\n VALUES (@brief_doc_id, @chunk_id_fragment, @chunk_doc_id, @recorded_hash)\n `);\n this._deleteByBrief = db.prepare(\"DELETE FROM brief_sources WHERE brief_doc_id = ?\");\n this._listBriefDocIds = db.prepare(\"SELECT DISTINCT brief_doc_id FROM brief_sources\");\n this._briefsForChunkDoc = db.prepare(\n `SELECT brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash\n FROM brief_sources\n WHERE chunk_doc_id = ?`,\n );\n this._sourcesForBrief = db.prepare(\n `SELECT brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash\n FROM brief_sources\n WHERE brief_doc_id = ?`,\n );\n }\n\n /**\n * Batch insert. Idempotent: `INSERT OR IGNORE` against the UNIQUE\n * `(brief_doc_id, chunk_id_fragment)` constraint means re-running the\n * same batch is a no-op. Mirrors `WikilinksQueries.insertBatch`\n * (`wikilinks.ts:74-87`).\n */\n insertBatch(briefDocId: string, sources: BriefSourceInput[]): void {\n const tx = this.db.transaction((xs: BriefSourceInput[]) => {\n for (const x of xs) {\n this._insert.run({\n brief_doc_id: briefDocId,\n chunk_id_fragment: x.chunkIdFragment,\n chunk_doc_id: x.chunkDocId,\n recorded_hash: x.recordedHash,\n });\n }\n });\n tx(sources);\n }\n\n deleteByBrief(briefDocId: string): number {\n return this._deleteByBrief.run(briefDocId).changes;\n }\n\n listBriefDocIds(): string[] {\n return this._listBriefDocIds.all().map((r) => r.brief_doc_id);\n }\n\n briefsForChunkDoc(chunkDocId: string): BriefSourceRow[] {\n return this._briefsForChunkDoc.all(chunkDocId).map((r) => ({\n briefDocId: r.brief_doc_id,\n chunkIdFragment: r.chunk_id_fragment,\n chunkDocId: r.chunk_doc_id,\n recordedHash: r.recorded_hash,\n }));\n }\n\n sourcesForBrief(briefDocId: string): BriefSourceRow[] {\n return this._sourcesForBrief.all(briefDocId).map((r) => ({\n briefDocId: r.brief_doc_id,\n chunkIdFragment: r.chunk_id_fragment,\n chunkDocId: r.chunk_doc_id,\n recordedHash: r.recorded_hash,\n }));\n }\n}\n","/**\n * DaemonStateQueries — Phase 5 / D-09 staleness daemon cursor.\n *\n * Single-row-per-vault state (`vault_name TEXT PRIMARY KEY`). Used by\n * the staleness daemon (Plan 05-03) for the hybrid replay strategy:\n *\n * 1. Startup full scan (correctness floor).\n * 2. Read `last_seen_doc_mtime` cursor (steady-state diagnostic).\n * 3. After processing each ChangeEvent, bump cursor.\n *\n * The cursor is a **diagnostic hint** — never the sole correctness\n * guarantee. The startup scan is the floor regardless of cursor value.\n * Departure from the recommended \"mtime-only\" option per CONTEXT D-09.\n *\n * UPSERT idiom: `INSERT ... ON CONFLICT(vault_name) DO UPDATE SET`\n * keeps the single-row invariant via the PRIMARY KEY.\n *\n * Adapter-seam discipline: no `fs`/`path`/`gray-matter`/`chokidar`\n * imports. `scripts/lint-adapters.sh` enforces.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\n\nexport class DaemonStateQueries {\n private readonly _getCursor: BetterSqlite3.Statement<[string], { last_seen_doc_mtime: number }>;\n private readonly _setCursor: BetterSqlite3.Statement;\n\n constructor(private readonly db: BetterSqlite3.Database) {\n this._getCursor = db.prepare(\n \"SELECT last_seen_doc_mtime FROM daemon_state WHERE vault_name = ?\",\n );\n this._setCursor = db.prepare(`\n INSERT INTO daemon_state (vault_name, last_seen_doc_mtime)\n VALUES (@vault_name, @mtime)\n ON CONFLICT(vault_name) DO UPDATE SET last_seen_doc_mtime = @mtime\n `);\n }\n\n /**\n * Returns the cursor for `vaultName`, or `null` if no row exists yet\n * (fresh vault, daemon has never run). Callers treat `null` as\n * \"perform the startup full scan\" — the cursor is a steady-state\n * efficiency hint, never a correctness floor.\n */\n getCursor(vaultName: string): number | null {\n const row = this._getCursor.get(vaultName);\n return row?.last_seen_doc_mtime ?? null;\n }\n\n setCursor(vaultName: string, mtime: number): void {\n this._setCursor.run({ vault_name: vaultName, mtime });\n }\n}\n","/**\n * ContractAuditQueries — Phase 6 / Q-AUD orchestration audit substrate.\n *\n * Mirrors `src/db/queries/audit.ts` and `src/db/queries/brief_sources.ts`\n * (Phase 5) in structure. Populated by `src/contracts/audit.ts` writers\n * (`recordContractStep` / `recordContractLoadError`) — one row per step,\n * no batch insert (orchestration writes step-by-step).\n *\n * Column shape (migration 014):\n * id INTEGER PRIMARY KEY AUTOINCREMENT\n * kind TEXT NOT NULL -- 'contract_step' | 'contract_load_error'\n * contract TEXT -- nullable (load errors have no contract context)\n * verb TEXT -- nullable\n * step_alias TEXT -- nullable\n * vault TEXT -- nullable\n * ts INTEGER NOT NULL -- epoch ms\n * error_message TEXT -- nullable\n *\n * Security pattern (ADR-006 Invariant C-5): rows store ONLY the columns\n * above — NEVER step output payloads. Peer-MCP outputs may contain\n * sensitive data; we explicitly do not capture them. The\n * `ContractAuditRow` input type does not declare an `output` field, so\n * TypeScript strict-mode rejects any attempt to add one at the call\n * site (`src/contracts/audit.ts`).\n *\n * Adapter-seam discipline: no `fs`/`path`/`gray-matter`/`chokidar`\n * imports. `scripts/lint-adapters.sh` enforces.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\n\nexport interface ContractAuditRow {\n kind: \"contract_step\" | \"contract_load_error\";\n contract?: string;\n verb?: string;\n stepAlias?: string;\n vault?: string;\n ts: number;\n errorMessage?: string;\n}\n\nexport interface ListByKindOptions {\n limit?: number;\n vault?: string;\n}\n\nexport interface VerbUsageRow {\n verb: string;\n invocation_count: number;\n last_seen: number;\n}\n\ninterface ContractAuditDbRow {\n id: number;\n kind: string;\n contract: string | null;\n verb: string | null;\n step_alias: string | null;\n vault: string | null;\n ts: number;\n error_message: string | null;\n}\n\nexport class ContractAuditQueries {\n private readonly _insert: BetterSqlite3.Statement;\n private readonly _listByKindAll: BetterSqlite3.Statement<[string, number], ContractAuditDbRow>;\n private readonly _listByKindAndVault: BetterSqlite3.Statement<\n [string, string, number],\n ContractAuditDbRow\n >;\n // Q-AUD: `kind = 'contract_step'` is a CONSTANT filter (D-A2b semantics) —\n // the aggregator counts ONLY step rows, never load_error rows.\n private readonly _aggregate: BetterSqlite3.Statement<\n [string],\n { verb: string; invocation_count: number; last_seen: number }\n >;\n\n constructor(db: BetterSqlite3.Database) {\n this._insert = db.prepare(`\n INSERT INTO contract_audit\n (kind, contract, verb, step_alias, vault, ts, error_message)\n VALUES\n (@kind, @contract, @verb, @step_alias, @vault, @ts, @error_message)\n `);\n this._listByKindAll = db.prepare<[string, number], ContractAuditDbRow>(\n \"SELECT * FROM contract_audit WHERE kind = ? ORDER BY ts DESC LIMIT ?\",\n );\n this._listByKindAndVault = db.prepare<[string, string, number], ContractAuditDbRow>(\n \"SELECT * FROM contract_audit WHERE kind = ? AND vault = ? ORDER BY ts DESC LIMIT ?\",\n );\n this._aggregate = db.prepare<\n [string],\n { verb: string; invocation_count: number; last_seen: number }\n >(\n `SELECT verb, COUNT(*) AS invocation_count, MAX(ts) AS last_seen\n FROM contract_audit\n WHERE kind = 'contract_step' AND vault = ? AND verb IS NOT NULL\n GROUP BY verb\n ORDER BY invocation_count DESC`,\n );\n }\n\n insert(row: ContractAuditRow): void {\n this._insert.run({\n kind: row.kind,\n contract: row.contract ?? null,\n verb: row.verb ?? null,\n step_alias: row.stepAlias ?? null,\n vault: row.vault ?? null,\n ts: row.ts,\n error_message: row.errorMessage ?? null,\n });\n }\n\n listByKind(kind: string, opts: ListByKindOptions = {}): ContractAuditRow[] {\n const limit = opts.limit ?? 100;\n const rows: ContractAuditDbRow[] =\n opts.vault !== undefined\n ? this._listByKindAndVault.all(kind, opts.vault, limit)\n : this._listByKindAll.all(kind, limit);\n return rows.map(toContractAuditRow);\n }\n\n aggregateVerbUsage(vault: string): VerbUsageRow[] {\n return this._aggregate.all(vault);\n }\n}\n\nfunction toContractAuditRow(row: ContractAuditDbRow): ContractAuditRow {\n const out: ContractAuditRow = {\n kind: row.kind as ContractAuditRow[\"kind\"],\n ts: row.ts,\n };\n if (row.contract !== null) out.contract = row.contract;\n if (row.verb !== null) out.verb = row.verb;\n if (row.step_alias !== null) out.stepAlias = row.step_alias;\n if (row.vault !== null) out.vault = row.vault;\n if (row.error_message !== null) out.errorMessage = row.error_message;\n return out;\n}\n","import BetterSqlite3 from \"better-sqlite3\";\nimport * as sqliteVec from \"sqlite-vec\";\n\nimport { MIGRATIONS, type MigrationContext } from \"./schema.js\";\nimport { NotesQueries } from \"./queries/notes.js\";\nimport { ChunksQueries } from \"./queries/chunks.js\";\nimport { EmbeddingsQueries } from \"./queries/embeddings.js\";\nimport { WikilinksQueries } from \"./queries/wikilinks.js\";\nimport { EdgesQueries } from \"./queries/edges.js\";\nimport { AuditQueries } from \"./queries/audit.js\";\nimport { ModelsQueries } from \"./queries/models.js\";\nimport { FtsQueries } from \"./queries/fts.js\";\nimport { AliasesQueries } from \"./queries/aliases.js\";\nimport { SectionsQueries } from \"./queries/sections.js\";\nimport { BriefSourcesQueries } from \"./queries/brief_sources.js\";\nimport { DaemonStateQueries } from \"./queries/daemon_state.js\";\nimport { ContractAuditQueries } from \"./queries/contract-audit.js\";\n\n/**\n * SQLite wrapper for a single vault.\n *\n * One Database instance corresponds to one vault DB file (or `:memory:` for tests).\n * Construction is synchronous; the static `open()` is provided for symmetry\n * with future async hooks (e.g. migration backups) — it currently just wraps\n * the constructor + migrate().\n */\nexport class Database {\n readonly handle: BetterSqlite3.Database;\n\n readonly notes: NotesQueries;\n readonly chunks: ChunksQueries;\n readonly embeddings: EmbeddingsQueries;\n readonly wikilinks: WikilinksQueries;\n /** Phase 4 / 04-01 / GRA-04: typed-edge substrate (`vault.db.edges`). */\n readonly edges: EdgesQueries;\n readonly audit: AuditQueries;\n readonly models: ModelsQueries;\n readonly fts: FtsQueries;\n readonly aliases: AliasesQueries;\n /** Phase 3 / 03-01: materialized `sections` table query namespace. */\n readonly sections: SectionsQueries;\n /** Phase 5 / BRF-* / D-06: brief→chunk reverse-index query namespace. */\n readonly briefSources: BriefSourcesQueries;\n /** Phase 5 / D-09: staleness-daemon cursor query namespace. */\n readonly daemonState: DaemonStateQueries;\n /** Phase 6 / Q-AUD: task-contract orchestration audit query namespace. */\n readonly contractAudit: ContractAuditQueries;\n\n /**\n * Name of the vault this DB belongs to, or `undefined` for `:memory:` /\n * unrecognised paths. Threaded into function-style migrations as\n * `MigrationContext.vaultName` so migration 008 can derive\n * `obsidian-fs://<vaultName>/<path>` (RESEARCH §doc_uri Dual-Column Migration,\n * plan 01-02).\n */\n readonly vaultName: string | undefined;\n\n constructor(dbPath: string, vaultName?: string) {\n this.vaultName = vaultName ?? deriveVaultNameFromPath(dbPath);\n this.handle = new BetterSqlite3(dbPath);\n // WAL is invalid for :memory: databases — skip it there.\n if (dbPath !== \":memory:\") {\n this.handle.pragma(\"journal_mode = WAL\");\n }\n this.handle.pragma(\"foreign_keys = ON\");\n this.handle.pragma(\"synchronous = NORMAL\");\n\n loadSqliteVec(this.handle);\n\n // Apply schema BEFORE preparing statements — query classes prepare against\n // tables that must already exist.\n this.migrateInternal();\n\n this.notes = new NotesQueries(this.handle);\n this.chunks = new ChunksQueries(this.handle);\n // models must be constructed before embeddings — embeddings looks up\n // dim via models.getById() for routing to the correct embeddings_<dim>\n // virtual table.\n this.models = new ModelsQueries(this.handle);\n this.embeddings = new EmbeddingsQueries(this.handle, this.models);\n this.wikilinks = new WikilinksQueries(this.handle);\n // Phase 4 / 04-01 / GRA-04 (D-01): edges substrate. Only prepares\n // statements; construction order is independent of other namespaces.\n this.edges = new EdgesQueries(this.handle);\n this.audit = new AuditQueries(this.handle);\n this.fts = new FtsQueries(this.handle);\n this.aliases = new AliasesQueries(this.handle);\n this.sections = new SectionsQueries(this.handle);\n // Phase 5 / BRF-* / D-06 + D-09: brief reverse-index + daemon\n // cursor. Construction is independent — only prepares statements\n // against tables already created by migration 013.\n this.briefSources = new BriefSourcesQueries(this.handle);\n this.daemonState = new DaemonStateQueries(this.handle);\n // Phase 6 / Q-AUD: contract orchestration audit. Construction is\n // independent — only prepares statements against the table already\n // created by migration 014.\n this.contractAudit = new ContractAuditQueries(this.handle);\n }\n\n static async open(dbPath: string, vaultName?: string): Promise<Database> {\n return new Database(dbPath, vaultName);\n }\n\n close(): void {\n this.handle.close();\n }\n\n getSchemaVersion(): number {\n const row = this.handle.pragma(\"user_version\") as Array<{\n user_version: number;\n }>;\n return row[0]?.user_version ?? 0;\n }\n\n /**\n * Idempotent: applies pending migrations and bumps PRAGMA user_version.\n * Called automatically during construction; safe to call again.\n */\n migrate(): void {\n this.migrateInternal();\n }\n\n private migrateInternal(): void {\n const current = this.getSchemaVersion();\n const pending = MIGRATIONS.filter((m) => m.version > current).sort(\n (a, b) => a.version - b.version,\n );\n if (pending.length === 0) return;\n\n // SQLite's recommended table-rebuild pattern (CREATE *_new, INSERT,\n // DROP, RENAME) trips foreign-key checks mid-transaction even when\n // the data itself is consistent. The official guidance is to disable\n // FKs around the migration and verify with PRAGMA foreign_key_check\n // afterwards. PRAGMA foreign_keys cannot be toggled inside an active\n // transaction, so the toggle wraps the transactional batch.\n const fkWasOn = (this.handle.pragma(\"foreign_keys\", { simple: true }) as number) === 1;\n if (fkWasOn) this.handle.pragma(\"foreign_keys = OFF\");\n\n let highest = current;\n const ctx: MigrationContext = { vaultName: this.vaultName };\n try {\n const tx = this.handle.transaction(() => {\n for (const m of pending) {\n if (\"sql\" in m) {\n this.handle.exec(m.sql);\n } else {\n m.run(this.handle, ctx);\n }\n highest = m.version;\n }\n });\n tx();\n // Verify referential integrity post-migration. Any violation raises\n // a sqlite-error here; the migration is already committed, but at\n // least we know about the inconsistency.\n const violations = this.handle.pragma(\"foreign_key_check\") as unknown[];\n if (violations.length > 0) {\n throw new Error(\n `Migration to v${highest} produced foreign-key violations: ${JSON.stringify(violations)}`,\n );\n }\n // PRAGMA cannot be bound; safe because `highest` is a number we control.\n this.handle.pragma(`user_version = ${highest}`);\n } finally {\n if (fkWasOn) this.handle.pragma(\"foreign_keys = ON\");\n }\n }\n\n transaction<T>(fn: () => T): T {\n return this.handle.transaction(fn)();\n }\n}\n\n/**\n * Best-effort vault-name derivation from the dbPath. Standard layout is\n * `<homedir>/.vault-memory/vaults/<name>.db` (see VaultManager.dbPathFor).\n * Returns `undefined` for `:memory:`, empty strings, or any path whose\n * basename doesn't match `<name>.db`. Callers can override by passing an\n * explicit `vaultName` to the Database constructor (the normal path —\n * VaultManager always passes `vault.config.name`).\n */\nfunction deriveVaultNameFromPath(dbPath: string): string | undefined {\n if (!dbPath || dbPath === \":memory:\") return undefined;\n // basename: split on POSIX or Windows separator\n const segs = dbPath.split(/[\\\\/]/);\n const base = segs[segs.length - 1];\n if (!base) return undefined;\n if (!base.endsWith(\".db\")) return undefined;\n const name = base.slice(0, -3);\n if (!name) return undefined;\n return name;\n}\n\nfunction loadSqliteVec(db: BetterSqlite3.Database): void {\n try {\n sqliteVec.load(db);\n } catch (err) {\n const arch = process.arch;\n const platform = process.platform;\n const msg =\n `Failed to load sqlite-vec extension (platform=${platform}, arch=${arch}). ` +\n `Ensure the matching prebuilt binary (sqlite-vec-${platform}-${arch}) is installed. ` +\n `On Apple Silicon, install sqlite-vec-darwin-arm64.`;\n throw new Error(`${msg}\\nOriginal: ${(err as Error).message}`);\n }\n}\n","export { Database } from \"./database.js\";\nexport { INITIAL_SCHEMA, MIGRATIONS } from \"./schema.js\";\nexport type { Migration } from \"./schema.js\";\nexport type { IndexRunRow, WriteAuditRow } from \"./types.js\";\n\nexport { NotesQueries } from \"./queries/notes.js\";\nexport type { UpsertNoteInput } from \"./queries/notes.js\";\n\nexport { ChunksQueries } from \"./queries/chunks.js\";\nexport type { ChunkInput } from \"./queries/chunks.js\";\n\nexport { EmbeddingsQueries } from \"./queries/embeddings.js\";\nexport type { EmbeddingInput, SemanticHit } from \"./queries/embeddings.js\";\n\nexport { WikilinksQueries } from \"./queries/wikilinks.js\";\nexport type {\n WikilinkInput,\n BacklinkRow,\n ForwardLinkRow,\n BrokenLinkRow,\n} from \"./queries/wikilinks.js\";\n\nexport { AuditQueries } from \"./queries/audit.js\";\nexport type {\n StartRunInput,\n FinishRunStats,\n RecordWriteInput,\n ListWritesFilter,\n} from \"./queries/audit.js\";\n\nexport { ModelsQueries } from \"./queries/models.js\";\nexport type { UpsertModelInput } from \"./queries/models.js\";\n\nexport { FtsQueries } from \"./queries/fts.js\";\nexport type { BM25Hit } from \"./queries/fts.js\";\n\nexport { AliasesQueries } from \"./queries/aliases.js\";\nexport type { AliasResolveHit } from \"./queries/aliases.js\";\n","/**\n * Vault Manager — holds one Database per configured vault.\n *\n * Responsibilities:\n * - Open DBs on demand under ~/.vault-memory/vaults/<name>.db\n * - Apply migrations on first open\n * - Provide resolved Vault objects (config + db handle) to consumers\n * - Clean shutdown\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { mkdir } from \"node:fs/promises\";\nimport { Database } from \"../db/index.js\";\nimport type { VaultConfig } from \"../types.js\";\n\nexport interface Vault {\n readonly config: VaultConfig;\n readonly db: Database;\n readonly dbPath: string;\n}\n\nexport class VaultManager {\n private readonly vaults = new Map<string, Vault>();\n\n static dbDirectory(): string {\n return join(homedir(), \".vault-memory\", \"vaults\");\n }\n\n static dbPathFor(vaultName: string): string {\n return join(VaultManager.dbDirectory(), `${vaultName}.db`);\n }\n\n /**\n * Initialize all vaults from config. Creates DB files if missing, runs\n * migrations. Idempotent — safe to call multiple times.\n */\n async loadAll(configs: readonly VaultConfig[]): Promise<void> {\n await mkdir(VaultManager.dbDirectory(), { recursive: true });\n\n for (const cfg of configs) {\n if (this.vaults.has(cfg.name)) continue;\n\n const dbPath = VaultManager.dbPathFor(cfg.name);\n // Pass vault name explicitly so migration 008 (doc_uri backfill) can\n // derive `obsidian-fs://<vaultName>/<path>` without parsing dbPath.\n const db = new Database(dbPath, cfg.name);\n db.migrate();\n\n this.vaults.set(cfg.name, { config: cfg, db, dbPath });\n }\n }\n\n get(name: string): Vault | null {\n return this.vaults.get(name) ?? null;\n }\n\n /**\n * Get a vault or throw with a helpful message.\n */\n require(name: string): Vault {\n const v = this.vaults.get(name);\n if (!v) {\n const known = [...this.vaults.keys()].join(\", \") || \"(none)\";\n throw new Error(`Unknown vault: \"${name}\". Configured vaults: ${known}`);\n }\n return v;\n }\n\n list(): Vault[] {\n return [...this.vaults.values()];\n }\n\n closeAll(): void {\n for (const v of this.vaults.values()) {\n v.db.close();\n }\n this.vaults.clear();\n }\n}\n","export { VaultManager } from \"./manager.js\";\nexport type { Vault } from \"./manager.js\";\n","/**\n * Exponential backoff retry helper.\n *\n * Retries an async function with exponential backoff + jitter.\n * By default retries on any thrown error; callers can opt out via `shouldRetry`.\n */\n\nexport interface RetryOptions {\n retries: number;\n baseDelayMs?: number;\n maxDelayMs?: number;\n shouldRetry?: (error: unknown) => boolean;\n}\n\nconst DEFAULT_BASE_DELAY_MS = 100;\nconst DEFAULT_MAX_DELAY_MS = 5000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction computeDelay(attempt: number, baseDelayMs: number, maxDelayMs: number): number {\n const exp = baseDelayMs * Math.pow(2, attempt);\n const jitter = Math.floor(Math.random() * 100);\n return Math.min(exp + jitter, maxDelayMs);\n}\n\nexport async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions): Promise<T> {\n const retries = options.retries;\n const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;\n const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const shouldRetry = options.shouldRetry ?? (() => true);\n\n let lastError: unknown;\n // attempts = retries + 1 total invocations (initial + retries)\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err;\n if (attempt === retries) break;\n if (!shouldRetry(err)) break;\n const delay = computeDelay(attempt, baseDelayMs, maxDelayMs);\n await sleep(delay);\n }\n }\n throw lastError;\n}\n","/**\n * Ollama HTTP client for embedding generation.\n *\n * Talks to a local (or remote) Ollama server's REST API:\n * - POST /api/embed — generate embeddings\n * - GET /api/tags — list loaded models\n *\n * Splits large batches, retries transient failures with exponential backoff,\n * and enforces per-request timeouts via AbortController.\n */\n\nimport { z } from \"zod\";\nimport type { EmbedRequest, EmbedResponse, OllamaClientOptions } from \"../types.js\";\nimport { withRetry } from \"./retry.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\nconst DEFAULT_ENDPOINT = \"http://localhost:11434\";\nconst DEFAULT_BATCH_SIZE = 10;\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_RETRIES = 3;\n\nconst EmbedResponseSchema = z.object({\n embeddings: z.array(z.array(z.number())),\n model: z.string().optional(),\n});\n\nconst TagsResponseSchema = z.object({\n models: z.array(\n z.object({\n name: z.string(),\n }),\n ),\n});\n\n/**\n * Phase 5 / D-10 tier 2 — `/api/chat` REST response.\n *\n * Mirrors `EmbedResponseSchema` shape: Zod-validated at the HTTP boundary,\n * thrown errors percolate up through `withRetry` so the same isRetryable\n * predicate (network + 5xx + AbortError) drives the same retry policy.\n *\n * Optional fields (`done`, `total_duration`, `eval_count`) appear on\n * non-streaming responses and are kept as opaque metadata; the brief\n * compile path only consults `message.content` + `model`.\n */\nconst ChatResponseSchema = z.object({\n model: z.string(),\n message: z.object({\n role: z.literal(\"assistant\"),\n content: z.string(),\n }),\n done: z.boolean().optional(),\n total_duration: z.number().optional(),\n eval_count: z.number().optional(),\n});\n\n/**\n * Chat-message role union. Same shape Ollama and the MCP Sampling spec\n * use; the brief LLM ladder builds tier-2 requests with one `system`\n * message and one `user` message.\n */\nexport interface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\n/**\n * Chat request shape (POST `/api/chat`). Mirrors the v1 `embed()`\n * request shape: pure data, no internal client state. `stream: false`\n * is set inside `chat()` (we do not expose streaming on this surface).\n *\n * `options.num_predict` maps to Ollama's max-tokens equivalent and is\n * how the LLM ladder forwards `max_tokens` from `compile_brief`.\n */\nexport interface ChatRequest {\n model: string;\n messages: ChatMessage[];\n options?: {\n num_predict?: number;\n temperature?: number;\n };\n}\n\nexport interface ChatResponse {\n model: string;\n message: ChatMessage;\n}\n\n/**\n * Error thrown for non-2xx HTTP responses. Retried automatically for 5xx.\n */\nexport class OllamaHttpError extends Error {\n public readonly status: number;\n constructor(status: number, message: string) {\n super(message);\n this.name = \"OllamaHttpError\";\n this.status = status;\n }\n}\n\nfunction isRetryable(err: unknown): boolean {\n if (err instanceof OllamaHttpError) {\n return err.status >= 500 && err.status < 600;\n }\n // AbortError (timeout) — retry\n if (err instanceof Error && err.name === \"AbortError\") return true;\n // Network errors (TypeError from fetch on connection failures)\n if (err instanceof TypeError) return true;\n return false;\n}\n\nfunction stripTag(name: string): string {\n const idx = name.indexOf(\":\");\n return idx === -1 ? name : name.slice(0, idx);\n}\n\nexport class OllamaClient {\n private readonly endpoint: string;\n private readonly batchSize: number;\n private readonly timeoutMs: number;\n private readonly retries: number;\n\n constructor(options: OllamaClientOptions = {}) {\n this.endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/+$/, \"\");\n this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.retries = options.retries ?? DEFAULT_RETRIES;\n }\n\n /**\n * Generate embeddings for the request's texts.\n *\n * If `texts.length > batchSize`, splits into multiple parallel HTTP requests\n * and concatenates the resulting vectors in order.\n */\n async embed(request: EmbedRequest): Promise<EmbedResponse> {\n const { model, texts } = request;\n if (texts.length === 0) {\n return { vectors: [], dim: 0, model };\n }\n\n const batches: string[][] = [];\n for (let i = 0; i < texts.length; i += this.batchSize) {\n batches.push(texts.slice(i, i + this.batchSize));\n }\n\n const results = await Promise.all(batches.map((batch) => this.embedBatch(model, batch)));\n\n const vectors: number[][] = [];\n let confirmedModel = model;\n for (const res of results) {\n vectors.push(...res.embeddings);\n if (res.model !== undefined) confirmedModel = res.model;\n }\n\n const first = vectors[0];\n if (first === undefined) {\n // Shouldn't happen — texts was non-empty\n return { vectors, dim: 0, model: confirmedModel };\n }\n const dim = first.length;\n\n return { vectors, dim, model: confirmedModel };\n }\n\n private async embedBatch(\n model: string,\n texts: string[],\n ): Promise<{ embeddings: number[][]; model?: string }> {\n return withRetry(\n async () => {\n const body = JSON.stringify({ model, input: texts });\n const response = await this.fetchWithTimeout(`${this.endpoint}/api/embed`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new OllamaHttpError(\n response.status,\n `Ollama /api/embed returned ${response.status}: ${text}`,\n );\n }\n\n const json: unknown = await response.json();\n const parsed = EmbedResponseSchema.parse(json);\n return { embeddings: parsed.embeddings, model: parsed.model };\n },\n { retries: this.retries, shouldRetry: isRetryable },\n );\n }\n\n /**\n * Phase 5 / D-10 tier 2 — synchronous chat completion via `/api/chat`.\n *\n * Single round-trip, non-streaming (`stream: false`). Mirrors the\n * `embed()` shape verbatim: `withRetry` wrapper, `fetchWithTimeout`,\n * `OllamaHttpError` on non-2xx after retry exhaustion, `isRetryable`\n * predicate (5xx + AbortError + network errors).\n *\n * The LLM ladder (`src/brief/llm-ladder.ts`) is the only production\n * caller; we keep the method on the same class so the shared retry /\n * timeout / endpoint config are honored without re-plumbing.\n */\n async chat(request: ChatRequest): Promise<ChatResponse> {\n return withRetry(\n async () => {\n const body = JSON.stringify({\n model: request.model,\n messages: request.messages,\n stream: false,\n options: request.options,\n });\n const response = await this.fetchWithTimeout(`${this.endpoint}/api/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new OllamaHttpError(\n response.status,\n `Ollama /api/chat returned ${response.status}: ${text}`,\n );\n }\n\n const json: unknown = await response.json();\n const parsed = ChatResponseSchema.parse(json);\n return { model: parsed.model, message: parsed.message };\n },\n { retries: this.retries, shouldRetry: isRetryable },\n );\n }\n\n /**\n * Check Ollama server liveness and return loaded model names.\n */\n async healthCheck(): Promise<{ ok: boolean; models?: string[]; error?: string }> {\n try {\n const response = await this.fetchWithTimeout(`${this.endpoint}/api/tags`, { method: \"GET\" });\n if (!response.ok) {\n return {\n ok: false,\n error: `HTTP ${response.status}`,\n };\n }\n const json: unknown = await response.json();\n const parsed = TagsResponseSchema.parse(json);\n return { ok: true, models: parsed.models.map((m) => m.name) };\n } catch (err) {\n const message = errorMessage(err);\n return { ok: false, error: message };\n }\n }\n\n /**\n * True iff `modelName` is loaded on the server.\n *\n * Matches both fully-qualified names (\"qwen3-embedding:latest\") and\n * tag-less names (\"qwen3-embedding\"): each is matched against the other\n * after stripping the `:tag` suffix.\n */\n async modelExists(modelName: string): Promise<boolean> {\n const health = await this.healthCheck();\n if (!health.ok || health.models === undefined) return false;\n const wantBase = stripTag(modelName);\n for (const name of health.models) {\n if (name === modelName) return true;\n if (stripTag(name) === wantBase) return true;\n }\n return false;\n }\n\n private async fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timer);\n }\n }\n}\n","export { OllamaClient, OllamaHttpError } from \"./client.js\";\nexport type { ChatMessage, ChatRequest, ChatResponse } from \"./client.js\";\nexport { withRetry } from \"./retry.js\";\nexport type { RetryOptions } from \"./retry.js\";\n","/**\n * Adapter Registry — the single minting point for branded DocIds and\n * the lookup surface for `SourceConnector` / `DeliveryAdapter` /\n * `ChangeFeed` triples (ADR-002 §Registry).\n *\n * # Branded-DocId minting (ADP-05, RESEARCH §Pattern 2)\n *\n * `DocId` is a nominal type — `string & { readonly __brand: \"DocId\" }` —\n * so raw `string` values cannot be assigned to a `DocId` parameter at\n * compile time. The brand-cast escape hatch lives ONLY inside the\n * IIFE below; this file is the SOLE module that performs it, and the\n * unsafe `mint` closure cannot leak across module boundaries (RESEARCH\n * §Pattern 2 lines 336–352). Only the validating `parseDocId` is\n * exported. The negative test `tests/types/docid-brand.test-d.ts`\n * proves the brand at compile time.\n *\n * `SourceHandle` follows the same pattern (`<scheme>://<authority>`,\n * no resource path).\n *\n * # Registry shape (ADR-002 lines 256–267)\n *\n * Three independent maps — sources, deliveries, change-feeds — keyed\n * by `SourceHandle`. The registry does NOT enforce a one-to-one\n * relationship between the three roles for a given handle; an adapter\n * may register for only one or two roles. The conformance suite\n * (Plans 01-03..05) asserts the obsidian-fs adapter registers all\n * three roles under the same handle.\n *\n * # Resolver semantics\n *\n * `resolveSource(handle)` mirrors `VaultManager.require()` — throws\n * with a helpful message on miss. Use the predicate-style accessor\n * (none exposed in Phase 1; add `hasSource(handle): boolean` later if\n * a use case appears) to avoid the throw.\n *\n * # Lifecycle\n *\n * The registry is constructed once at server bootstrap and lives for\n * the process lifetime. Adapters self-register at construction time;\n * the registry does NOT own adapter lifetimes (no `close()` cascade) —\n * each adapter's owner closes it directly.\n */\n\nimport type { DocId, SourceHandle } from \"../types.js\";\nimport type { SourceConnector } from \"./source/types.js\";\nimport type { DeliveryAdapter } from \"./delivery/types.js\";\nimport type { ChangeFeed } from \"./change-feed/types.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// DocId minting — IIFE-closed per RESEARCH §Pattern 2\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Canonical DocId shape: `<scheme>://<authority>/<resource>`.\n *\n * - scheme: lowercase ASCII, alphanumeric + dashes, starts with letter\n * - authority: one or more non-slash chars\n * - resource: one or more chars\n *\n * Examples that PASS: `obsidian-fs://my-vault/notes/foo.md`,\n * `notion-api://workspace-abc/page-123`.\n * Examples that FAIL: `not-a-uri` (no scheme), `OBSIDIAN://X/y`\n * (uppercase), `123://x/y` (digit-leading scheme),\n * `obsidian://` (empty authority + resource),\n * `obsidian-fs:/foo` (missing slash).\n */\nexport const DOC_ID_PATTERN = /^[a-z][a-z0-9-]*:\\/\\/[^/]+\\/.+$/;\n\n/**\n * Bare `<scheme>://<authority>` — no resource path, no trailing slash.\n * Used to name an adapter triple in the registry. Same scheme rules as\n * `DOC_ID_PATTERN`; authority is one or more non-slash chars.\n */\nconst SOURCE_HANDLE_PATTERN = /^[a-z][a-z0-9-]*:\\/\\/[^/]+$/;\n\nconst { parseDocId } = (() => {\n // `mint` is the ONLY unsafe brand cast in the codebase; closed inside\n // this IIFE so it cannot escape. Per RESEARCH §Pattern 2. We do NOT\n // return it — only the validating `parse` is exported.\n const mint = (s: string): DocId => s as DocId;\n const parse = (s: string): DocId => {\n if (!DOC_ID_PATTERN.test(s)) {\n throw new Error(\n `Invalid DocId: ${JSON.stringify(s)}. ` +\n `Expected <scheme>://<authority>/<resource> ` +\n `(scheme: lowercase letter + alnum/dashes; authority: non-slash; resource: non-empty).`,\n );\n }\n return mint(s);\n };\n return { parseDocId: parse };\n})();\n\nexport { parseDocId };\n\n/**\n * Construct a DocId from its components and validate via `parseDocId`.\n * Convenience helper so callers do not concatenate by hand.\n */\nexport function formatDocId(scheme: string, authority: string, resource: string): DocId {\n return parseDocId(`${scheme}://${authority}/${resource}`);\n}\n\n/**\n * Split a canonical `DocId` into its three components. Pure split —\n * defensively re-validates via `parseDocId` so a stale brand-cast cannot\n * leak malformed input through. Re-uses the SAME `DOC_ID_PATTERN` as\n * `parseDocId`; there is no second regex (single source of truth per\n * ADR-001 §I-6 canonical-serialization).\n *\n * The split is intentionally a pure string operation (`indexOf(\"://\")` +\n * `indexOf(\"/\")`) rather than a regex capture-group, because the\n * resource portion can contain `/`-separated segments that a single\n * capture group would have to greedy-match — the explicit split keeps\n * the behavior obviously correct and avoids regex-engine surprises with\n * unicode or extreme inputs.\n *\n * Used by Phase 2's `MemorySinkRegistry.findSinkContaining(docId)` and\n * by any downstream tool that needs the scheme/authority/resource parts\n * without re-validating the DocId from scratch.\n *\n * @internal Perf note (IN-01): the defensive `parseDocId(docId)` call\n * regex-tests the input on every invocation. The DocId is branded and\n * valid by construction at every call site under typecheck, so the\n * regex test is purely defense against `as DocId` smuggling in test\n * code. `MemorySinkRegistry.findSinkContaining` calls this per\n * registered sink per validator call — a measurable cost emerges only\n * if (a) the sink count grows past tens, OR (b) validator calls hit a\n * tight loop. Neither is true in v2.0.0. If it becomes true, memoize\n * here rather than dropping the defense.\n */\nexport function decomposeDocId(docId: DocId): {\n scheme: string;\n authority: string;\n resource: string;\n} {\n // Defensive: assert canonical shape via the existing parser. Cheap\n // (one regex test) and means a stale brand-cast cannot smuggle a\n // malformed value through this helper.\n parseDocId(docId);\n const schemeEnd = docId.indexOf(\"://\");\n const scheme = docId.slice(0, schemeEnd);\n const rest = docId.slice(schemeEnd + 3);\n const authoritySlash = rest.indexOf(\"/\");\n const authority = rest.slice(0, authoritySlash);\n const resource = rest.slice(authoritySlash + 1);\n return { scheme, authority, resource };\n}\n\n/**\n * Validate and brand a `SourceHandle` — bare `<scheme>://<authority>`,\n * no resource path. Throws on malformed input.\n */\nexport function parseSourceHandle(s: string): SourceHandle {\n if (!SOURCE_HANDLE_PATTERN.test(s)) {\n throw new Error(\n `Invalid SourceHandle: ${JSON.stringify(s)}. ` +\n `Expected <scheme>://<authority> with no resource path or trailing slash.`,\n );\n }\n return s as SourceHandle;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AdapterRegistry — handle → adapter resolver triad\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Registry of adapter triples, keyed by `SourceHandle`. Mirrors\n * `VaultManager` shape (`src/vault/manager.ts:23–78`).\n *\n * Adapters self-register at construction time; the registry does not\n * own adapter lifetimes. Lookups throw with a helpful message on miss.\n */\nexport class AdapterRegistry {\n private readonly sources = new Map<SourceHandle, SourceConnector>();\n private readonly deliveries = new Map<SourceHandle, DeliveryAdapter>();\n private readonly changeFeeds = new Map<SourceHandle, ChangeFeed>();\n\n // ── source ────────────────────────────────────────────────────────────────\n\n /** Register a source. Overwrites any prior registration under the same handle. */\n registerSource(handle: SourceHandle, adapter: SourceConnector): void {\n this.sources.set(handle, adapter);\n }\n\n /** Resolve a source. Throws with a helpful message on miss. */\n resolveSource(handle: SourceHandle): SourceConnector {\n const a = this.sources.get(handle);\n if (!a) {\n const known = [...this.sources.keys()].join(\", \") || \"(none)\";\n throw new Error(`Unknown source handle: \"${handle}\". Registered sources: ${known}`);\n }\n return a;\n }\n\n /** List registered source handles. */\n listSources(): SourceHandle[] {\n return [...this.sources.keys()];\n }\n\n // ── delivery ──────────────────────────────────────────────────────────────\n\n registerDelivery(handle: SourceHandle, adapter: DeliveryAdapter): void {\n this.deliveries.set(handle, adapter);\n }\n\n resolveDelivery(handle: SourceHandle): DeliveryAdapter {\n const a = this.deliveries.get(handle);\n if (!a) {\n const known = [...this.deliveries.keys()].join(\", \") || \"(none)\";\n throw new Error(`Unknown delivery handle: \"${handle}\". Registered deliveries: ${known}`);\n }\n return a;\n }\n\n listDeliveries(): SourceHandle[] {\n return [...this.deliveries.keys()];\n }\n\n // ── change-feed ───────────────────────────────────────────────────────────\n\n registerChangeFeed(handle: SourceHandle, feed: ChangeFeed): void {\n this.changeFeeds.set(handle, feed);\n }\n\n resolveChangeFeed(handle: SourceHandle): ChangeFeed {\n const f = this.changeFeeds.get(handle);\n if (!f) {\n const known = [...this.changeFeeds.keys()].join(\", \") || \"(none)\";\n throw new Error(`Unknown change-feed handle: \"${handle}\". Registered feeds: ${known}`);\n }\n return f;\n }\n\n listChangeFeeds(): SourceHandle[] {\n return [...this.changeFeeds.keys()];\n }\n}\n","/**\n * Graph operations — high-level edge queries for MCP tool handlers.\n *\n * ── Phase 4 / 04-01 / GRA-04 (D-01, D-04): switch reads to edges table ──\n *\n * Reads route through `vault.db.edges`; writes stay on\n * `vault.db.wikilinks` until Plan 04-02 lands the unified extractor.\n * The `type` field on result rows is strictly additive: pre-backfill no\n * row existed, post-backfill every row is `type='wikilink'`, and Plan\n * 04-02 starts producing the other three types in the same column.\n *\n * Default behavior is unchanged from v1: with no edge-type filter the\n * tools return all rows from `edges` for the given doc, which — after\n * the migration 011 backfill — equals the v1 behavior plus the new\n * edge types once the indexer populates them.\n *\n * Thin layer above `vault.db.edges`. Returns enriched results with\n * source/target paths and titles so callers don't need to re-query\n * notes.\n */\n\nimport type { EdgeType } from \"../db/queries/edges.js\";\nimport type { Vault } from \"../vault/index.js\";\n\nexport interface BacklinkResult {\n sourcePath: string;\n sourceTitle: string;\n lineNumber: number | null;\n linkText: string | null;\n /**\n * Phase 4 / 04-01 (D-04) — additive edge type. Post-backfill every\n * row is `'wikilink'`; Plan 04-02 widens to the other three\n * `Edge.type` literals once the indexer populates them.\n *\n * `linkText` is NOT yet carried on the edges table (Plan 04-02\n * adds it). For now `linkText` stays `null` on reads from\n * `vault.db.edges.*`; the existing field shape is preserved so\n * downstream callers don't break.\n */\n type: EdgeType;\n}\n\nexport interface ForwardLinkResult {\n targetPath: string;\n resolved: boolean;\n targetTitle: string | null;\n anchor: string | null;\n linkText: string | null;\n /** Phase 4 / 04-01 (D-04) — additive edge type. */\n type: EdgeType;\n}\n\nexport interface BrokenLinkResult {\n sourcePath: string;\n sourceTitle: string;\n targetPath: string;\n lineNumber: number | null;\n /** Phase 4 / 04-01 (D-04) — additive edge type. */\n type: EdgeType;\n}\n\n/**\n * Get all notes that link TO a given note.\n *\n * @throws if `notePath` does not resolve to a known note.\n */\nexport function listBacklinks(vault: Vault, notePath: string): BacklinkResult[] {\n const note = vault.db.notes.getByPath(notePath);\n if (!note) {\n throw new Error(`Note not found: ${notePath}`);\n }\n\n // ── Phase 4 / 04-01 / GRA-04 (D-01, D-04): switch reads to edges table ──\n //\n // Post-backfill every row has type='wikilink'; Plan 04-02 starts\n // producing the other three types.\n const rows = vault.db.edges.getBacklinks(note.id);\n const results: BacklinkResult[] = [];\n for (const row of rows) {\n const src = vault.db.notes.getById(row.sourceNoteId);\n if (!src) continue; // FK should prevent this, but be defensive.\n results.push({\n sourcePath: src.path,\n sourceTitle: src.title,\n lineNumber: row.lineNumber,\n linkText: row.linkText,\n type: row.type,\n });\n }\n return results;\n}\n\n/**\n * Get all forward links FROM a given note.\n *\n * @param includeBroken include unresolved links (default: true)\n * @throws if `notePath` does not resolve to a known note.\n */\nexport function listForwardLinks(\n vault: Vault,\n notePath: string,\n includeBroken: boolean = true,\n): ForwardLinkResult[] {\n const note = vault.db.notes.getByPath(notePath);\n if (!note) {\n throw new Error(`Note not found: ${notePath}`);\n }\n\n // ── Phase 4 / 04-01 / GRA-04 (D-01, D-04): switch reads to edges table ──\n const rows = vault.db.edges.getForwardLinks(note.id);\n const results: ForwardLinkResult[] = [];\n for (const row of rows) {\n const resolved = row.targetNoteId !== null;\n if (!resolved && !includeBroken) continue;\n\n let targetTitle: string | null = null;\n if (resolved && row.targetNoteId !== null) {\n const target = vault.db.notes.getById(row.targetNoteId);\n targetTitle = target?.title ?? null;\n }\n\n results.push({\n // For hyperlink / external edges the target is a URL string; for\n // wikilinks it's the original path. Either way `target_path` on\n // the edges row preserves the v1 wikilinks.target_path shape.\n // When `target_path` is NULL (resolved internal-edge with no\n // raw target string), surface the empty string — preserves the\n // existing `targetPath: string` contract.\n targetPath: row.targetPath ?? \"\",\n resolved,\n targetTitle,\n anchor: row.anchor,\n linkText: row.linkText,\n type: row.type,\n });\n }\n return results;\n}\n\n/**\n * List all broken links in the vault (where `target_doc IS NULL`).\n *\n * ── Phase 4 / 04-01 / GRA-04 (D-01, D-04): switch reads to edges table ──\n *\n * Reads route through `vault.db.edges.resolveBrokenLinks()`, which now\n * carries `line_number` directly (unlike the prior v1\n * `vault.db.wikilinks.resolveBrokenLinks()` which omitted it). Existing\n * call sites that observed `lineNumber === null` continue to receive\n * `null` for any pre-04-01 row that didn't capture a line; new rows\n * (post-04-02 unified extractor) will carry real line numbers.\n */\nexport function findBrokenLinks(vault: Vault): BrokenLinkResult[] {\n const rows = vault.db.edges.resolveBrokenLinks();\n if (rows.length === 0) return [];\n\n const noteCache = new Map<number, { path: string; title: string }>();\n\n const results: BrokenLinkResult[] = [];\n for (const row of rows) {\n let src = noteCache.get(row.sourceNoteId);\n if (!src) {\n const n = vault.db.notes.getById(row.sourceNoteId);\n if (!n) continue;\n src = { path: n.path, title: n.title };\n noteCache.set(row.sourceNoteId, src);\n }\n\n results.push({\n sourcePath: src.path,\n sourceTitle: src.title,\n // `target_path` is NULLABLE on the edges row — only broken\n // wikilinks (and external hyperlinks) carry a raw target.\n targetPath: row.targetPath ?? \"\",\n // v1 behavior: findBrokenLinks always returned `lineNumber: null`\n // (the v1 wikilinks.resolveBrokenLinks query omitted the column).\n // Plan 04-01 preserves that contract to keep the result shape\n // byte-identical; Plan 04-02 may surface `row.lineNumber` directly\n // once the unified extractor lands.\n lineNumber: null,\n type: row.type,\n });\n }\n return results;\n}\n\n// Re-export EdgeType so consumers of the graph barrel can type-check\n// against the same union as the underlying edges table.\nexport type { EdgeType };\n","/**\n * Citation Packet — the Phase 3 ASM-05 packet shape, pinned at the\n * Phase 2 floor.\n *\n * D-01 mandates an 8-field packet:\n *\n * { doc_id, source_handle, title, heading_path, mtime, hash,\n * display_url, properties }\n *\n * Two notes on field names:\n *\n * - `source_handle` on the packet maps to `Document.source` on the\n * canonical content type (`src/types.ts`). The mapper transcribes\n * accordingly — the packet uses the more explicit name because\n * consumers (recall callers, Phase 3 assembly tools) see the packet\n * surface, not the internal `Document` shape.\n *\n * - `hash` on the packet IS the read-side `Document.hash` (canonical\n * content hash returned by `SourceConnector.readDocument`). This is\n * DISTINCT from the write-side `WriteSuccess.newHash` returned by\n * `record_observation` / `supersede`. Both are correct names in\n * their respective domains; the packet uses the read-side name\n * because citation packets are READ artifacts.\n *\n * `heading_path` is a packet-only D-01 field. The canonical `Document`\n * type does not carry one (Phase 3 may add it via the BlockNode tree);\n * the mapper accepts an optional `heading_path` on the input shape and\n * defaults to an empty array when not present. The mapper deep-copies\n * the array so caller mutations do not leak into the source `Document`.\n *\n * `properties` is shallow-copied for the same reason — a `{...obj}`\n * spread is sufficient because callers should never mutate the inner\n * property values, only add/remove keys at the top level.\n *\n * Phase 3 ASM-05 will import `CitationPacket` from this module to keep\n * the recall (Phase 2) and assembly (Phase 3) surfaces in lockstep.\n */\n\nimport type { DocId, Document, SourceHandle } from \"../types.js\";\n\n/**\n * D-01 packet shape — exactly 8 fields. Phase 3 may extend additively;\n * Phase 2 ships all 8 as the floor.\n */\nexport interface CitationPacket {\n /** Opaque, branded DocId — the document's identity. */\n doc_id: DocId;\n /** Adapter handle that produced this document. */\n source_handle: SourceHandle;\n /** Short human-readable title. */\n title: string;\n /** Heading-path array (root → leaf); empty when the doc has no heading. */\n heading_path: string[];\n /** Last-modified time, epoch ms. */\n mtime: number;\n /** Read-side content hash from `Document.hash`. */\n hash: string;\n /** Adapter-provided deep-link URL (`displayUrlFor(doc.id)` for obsidian-fs). */\n display_url: string;\n /** Untyped property bag (YAML frontmatter, typed properties, …). */\n properties: Record<string, unknown>;\n}\n\n/**\n * Attach the denormalized `status` / `superseded_by` extras to a base\n * `CitationPacket` (or any subtype). Reads from the packet's REQUIRED\n * `properties` bag (`Record<string, unknown>`, always populated) — no\n * null guards needed for `properties` itself, only for the inner keys.\n *\n * Generic so callers that pass a `CitationPacket` subtype (e.g. a packet\n * already carrying `relation`) keep their extra fields. Returns a fresh\n * object; does not mutate the input packet.\n *\n * Shared by `assembleDossier` (anchor + linked docs) and `assembleBundle`\n * (anchor) — both denormalize the same two property keys identically.\n */\nexport function withPropertyExtras<T extends CitationPacket>(\n packet: T,\n): T & { status?: string; superseded_by?: string } {\n const out: T & { status?: string; superseded_by?: string } = { ...packet };\n const status = packet.properties.status;\n if (typeof status === \"string\") out.status = status;\n const supersededBy = packet.properties.superseded_by;\n if (typeof supersededBy === \"string\") out.superseded_by = supersededBy;\n return out;\n}\n\n/**\n * Map a `Document` (or its read-side fields) into a `CitationPacket`.\n *\n * Field transcription:\n * - `doc.id` → `packet.doc_id`\n * - `doc.source` → `packet.source_handle` (renamed for the packet surface)\n * - `doc.title` → `packet.title`\n * - `doc.heading_path` (optional) → `packet.heading_path` (defaults to `[]`)\n * - `doc.mtime` → `packet.mtime`\n * - `doc.hash` → `packet.hash` (read-side; not `newHash`)\n * - `displayUrl` → `packet.display_url` (callers compute via `displayUrlFor`)\n * - `doc.properties` → `packet.properties` (shallow-copied)\n *\n * Caller mutations on the returned packet's `heading_path` array or\n * `properties` object cannot leak back into the source `Document` — the\n * array is spread-copied and the property bag is spread-copied at the\n * top level.\n */\nexport function toCitationPacket(\n doc: Pick<Document, \"id\" | \"source\" | \"title\" | \"mtime\" | \"hash\" | \"properties\"> & {\n heading_path?: string[];\n },\n displayUrl: string,\n): CitationPacket {\n return {\n doc_id: doc.id,\n source_handle: doc.source,\n title: doc.title,\n heading_path: doc.heading_path ? [...doc.heading_path] : [],\n mtime: doc.mtime,\n hash: doc.hash,\n display_url: displayUrl,\n properties: { ...doc.properties },\n };\n}\n\n/**\n * Compute a display URL for a `DocId` via the adapter's\n * `formatDisplayUrl` seam (ADR-002 §SourceConnector).\n *\n * This thin wrapper preserves the seam: adapter-specific URL literals\n * live in the source adapter (the single licensed site per the I-5b\n * lint rule). A future Notion / Slack adapter publishes its own\n * deep-link convention; recall does not encode any URL scheme inline.\n *\n * Contract: `formatDisplayUrl` is OPTIONAL on the `SourceConnector`\n * interface (some adapters may not have deep links). When the adapter\n * omits the method or returns `null`, this helper falls back to the\n * DocId string itself so callers always get a non-null `display_url`\n * on the citation packet.\n */\nexport function displayUrlFor(\n docId: DocId,\n source: { formatDisplayUrl?: (id: DocId) => string | null },\n): string {\n return source.formatDisplayUrl?.(docId) ?? docId;\n}\n","/**\n * `expand()` — Phase 4 / 04-03 / GRA-01 typed-edge BFS retrieval.\n *\n * Returns a flat, dedup'd array of citation packets reachable from\n * `seed_doc_ids` within `hops` (1 or 2). Each packet carries an additive\n * `via: { seed_doc_id, hop, edge_type, direction }` provenance trace.\n *\n * Locked contracts (Phase 4 CONTEXT.md):\n * - D-05 Hops hard-capped at 2 (enforced by Zod literal union at the\n * tool boundary; this function trusts the bound).\n * - D-06 `direction` defaults to `\"both\"` (forward+backward).\n * - D-07 Shortest-path dedup via `isShorterPath` comparator.\n * Tie-breakers: lower hop → lower seed_doc_id (lex) → lower\n * edge_type (alpha) → forward over backward.\n * - D-08 `filter_properties` is strict equality on the hydrated\n * packet's `properties` bag. `include_superseded` defaults\n * false; superseded docs are dropped at hydration time via the\n * Phase 2 D-03 forward-only supersede property.\n * - D-09 Module lives in `src/graph/` alongside `graph.ts`.\n *\n * `_memory` opacity rule (ADR-004 §\"memory namespace is sacrosanct\" +\n * Phase 4 RESEARCH.md Pitfall 3):\n * A `_memory/...` doc surfaces in the result set ONLY when an inbound\n * edge in the BFS visited record originates from a non-`_memory`\n * source (a user note that already linked to it). 2-hop traversal MAY\n * NOT surface a `_memory/...` doc via an internal `_memory → _memory`\n * chain that does not pass through a user note first. We track the\n * `inboundSourceNoteId` for each visited node as the BFS expands so\n * the opacity check is O(1) per candidate at hydration time (no\n * second DB pass — T-04-03-01 mitigation).\n *\n * Pitfall 4 (RESEARCH.md lines 536–541): `isShorterPath` is exported as\n * a pure function and unit-tested directly. The comparator pins the\n * tie-breaker order so the `via` field is deterministic across runs\n * (T-04-03-05 mitigation).\n *\n * Unknown seed_doc_ids return as `warnings: [{seed_doc_id, reason:\n * \"unknown_doc\"}]` — soft warning shape, NOT a hard throw (Phase 4\n * CONTEXT §\"Claude's Discretion\" — error semantics on broken seeds).\n *\n * Adapter-seam discipline (Phase 1 Pattern A): zero imports of `fs`,\n * `path.join`, `gray-matter`, or `chokidar`. The hydration path goes\n * through the injected `SourceConnector.readDocument` seam; all SQL\n * reads go through `vault.db.edges` / `vault.db.notes`.\n */\n\nimport { decomposeDocId, formatDocId, parseDocId } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport type { EdgeType } from \"../db/queries/edges.js\";\nimport { type CitationPacket, displayUrlFor, toCitationPacket } from \"../memory/citation-packet.js\";\nimport type { DocId, Document } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\n\n// ─── public types ────────────────────────────────────────────────────────────\n\n/**\n * Direction of edge traversal. Default `\"both\"` per D-06.\n *\n * - `\"forward\"` — traverse outbound edges (seed → target).\n * - `\"backward\"` — traverse inbound edges (source → seed).\n * - `\"both\"` — both directions; results merge with shortest-path\n * dedup applied per D-07.\n */\nexport type ExpandDirection = \"forward\" | \"backward\" | \"both\";\n\n/**\n * Input shape for `expand()`. The Zod schema in `tool-registry.ts`\n * mirrors this verbatim; this interface is the runtime contract.\n */\nexport interface ExpandOptions {\n /** 1+ branded DocIds (URI-style, e.g. `obsidian-fs://vault/path.md`). */\n seed_doc_ids: DocId[];\n /** Hard-capped at 2 per D-05 (Zod literal union enforces this). */\n hops: 1 | 2;\n /** Direction per D-06; default `\"both\"`. */\n direction?: ExpandDirection;\n /** Optional edge-type filter; default = all four types. */\n edge_types?: EdgeType[];\n /**\n * Strict-equality predicate on `Document.properties`. No operators.\n * D-08 mirrors Phase 3 dossier convention.\n */\n filter_properties?: Record<string, unknown>;\n /** Default false per D-08; drops `properties.status === \"superseded\"`. */\n include_superseded?: boolean;\n}\n\n/**\n * Provenance trace attached to each result packet. Records HOW the\n * neighbor was reached: the seed that originated the BFS, the hop\n * count (1 or 2), the edge type, and the direction of traversal.\n *\n * Determinism: the comparator `isShorterPath` pins which trace wins\n * when multiple paths reach the same target (D-07 tie-breakers).\n */\nexport interface ViaTrace {\n seed_doc_id: DocId;\n hop: 1 | 2;\n edge_type: EdgeType;\n direction: \"forward\" | \"backward\";\n}\n\n/**\n * A citation packet (Phase 3 D-05 locked 8-field shape) with the\n * Phase-4-additive `via` field. None of the existing 8 fields are\n * reshaped; `via` is strictly additive (Pattern E).\n */\nexport interface CitationPacketWithVia extends CitationPacket {\n via: ViaTrace;\n}\n\n/**\n * Output shape: deduplicated `documents` (one per unique target doc,\n * with the shortest-path `via`) + soft `warnings` for unknown seeds.\n */\nexport interface ExpansionResult {\n documents: CitationPacketWithVia[];\n warnings: Array<{ seed_doc_id: string; reason: \"unknown_doc\" }>;\n}\n\n/**\n * Injected dependencies for `expand()`. Mirrors the dossier / bundle\n * dep shape so production wiring + unit tests share one contract.\n */\nexport interface ExpandDeps {\n manager: VaultManager;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\n// ─── pure comparator (Pitfall 4 — unit-tested directly) ─────────────────────\n\n/**\n * True iff `a` is a STRICTLY shorter / preferable path than `b`.\n *\n * Tie-breaker order per D-07:\n * 1. lower `hop` wins (shortest path);\n * 2. lower `seed_doc_id` (lexicographic) wins;\n * 3. lower `edge_type` (alphabetical) wins;\n * 4. `\"forward\"` wins over `\"backward\"`.\n *\n * Returns `false` for identical traces — the comparator is strict, not\n * `<=`. The BFS uses this to decide whether to OVERWRITE an existing\n * visited entry when a shorter path is found.\n *\n * Pitfall 4 mitigation: pure function, no side effects, no DB access.\n * Unit-tested directly (RESEARCH.md lines 536–541).\n */\nexport function isShorterPath(a: ViaTrace, b: ViaTrace): boolean {\n // 1) hop\n if (a.hop !== b.hop) return a.hop < b.hop;\n // 2) seed_doc_id (lex)\n if (a.seed_doc_id !== b.seed_doc_id) return a.seed_doc_id < b.seed_doc_id;\n // 3) edge_type (alpha)\n if (a.edge_type !== b.edge_type) return a.edge_type < b.edge_type;\n // 4) direction — forward beats backward\n if (a.direction !== b.direction) return a.direction === \"forward\";\n return false; // identical → not strictly shorter\n}\n\n// ─── internal helpers ───────────────────────────────────────────────────────\n\nconst MEMORY_PREFIX = \"_memory/\";\n\n/**\n * Resolve a seed_doc_id to its underlying note row.\n *\n * Returns `null` if the DocId is malformed, points at an unknown\n * vault, or names a note that is not indexed. The caller surfaces\n * each `null` as a `warnings: [{seed_doc_id, reason: \"unknown_doc\"}]`\n * entry per the soft-error contract.\n */\nfunction resolveSeed(\n deps: ExpandDeps,\n seedDocId: DocId,\n): { vault: Vault; vaultName: string; noteId: number; notePath: string; scheme: string } | null {\n let scheme: string;\n let vaultName: string;\n let resource: string;\n try {\n const docId = parseDocId(seedDocId);\n ({ scheme, authority: vaultName, resource } = decomposeDocId(docId));\n } catch {\n return null;\n }\n let vault: Vault;\n try {\n vault = deps.manager.require(vaultName);\n } catch {\n return null;\n }\n const note = vault.db.notes.getByPath(resource);\n if (!note) return null;\n return { vault, vaultName, noteId: note.id, notePath: resource, scheme };\n}\n\n/** True iff a note path (vault-relative, forward-slash) lives in `_memory/...`. */\nfunction isMemoryPath(notePath: string): boolean {\n return notePath.startsWith(MEMORY_PREFIX);\n}\n\n/** Mutable BFS bookkeeping entry — one per visited noteId. */\ninterface VisitedEntry {\n via: ViaTrace;\n /**\n * The noteId of the SOURCE doc on the edge that produced this\n * candidate's `via` trace. Used at hydration time by the\n * `_memory` opacity check (Pitfall 3): a `_memory` target survives\n * only when its `inboundSourceNoteId` is a non-`_memory` doc. The\n * seeds themselves do not have an inbound edge — they're skipped\n * for the opacity rule because they are EXPLICITLY requested by\n * the caller (a user-driven action; not silent traversal).\n */\n inboundSourceNoteId: number;\n}\n\n// ─── public entry point ─────────────────────────────────────────────────────\n\n/**\n * Bounded typed-edge BFS retrieval. See file header for the full\n * algorithm. Returns deduplicated citation packets with `via`\n * provenance. Empty `seed_doc_ids` returns\n * `{documents: [], warnings: []}`.\n */\nexport async function expand(deps: ExpandDeps, opts: ExpandOptions): Promise<ExpansionResult> {\n const warnings: ExpansionResult[\"warnings\"] = [];\n\n // Empty seeds → trivial empty result. Matches Phase 3 dossier's\n // \"empty result on no-match\" convention.\n if (opts.seed_doc_ids.length === 0) {\n return { documents: [], warnings };\n }\n\n const direction: ExpandDirection = opts.direction ?? \"both\";\n const hops = opts.hops;\n const edgeTypeFilter =\n opts.edge_types && opts.edge_types.length > 0 ? opts.edge_types : undefined;\n\n // Resolve each seed → noteRow. Misses become warnings; we keep\n // processing the rest. Track seed noteIds so the BFS can short-\n // circuit self-loops (test 17/18).\n interface ResolvedSeed {\n seedDocId: DocId;\n vault: Vault;\n vaultName: string;\n noteId: number;\n notePath: string;\n scheme: string;\n }\n const resolved: ResolvedSeed[] = [];\n const seedNoteIds = new Set<number>();\n for (const id of opts.seed_doc_ids) {\n const r = resolveSeed(deps, id);\n if (!r) {\n warnings.push({ seed_doc_id: id, reason: \"unknown_doc\" });\n continue;\n }\n resolved.push({\n seedDocId: id,\n vault: r.vault,\n vaultName: r.vaultName,\n noteId: r.noteId,\n notePath: r.notePath,\n scheme: r.scheme,\n });\n seedNoteIds.add(r.noteId);\n }\n\n // Group resolved seeds by vault — BFS operates per-vault because\n // `vault.db.edges` is scoped to one vault. Cross-vault expand would\n // require the edges to carry vault-namespaced DocIds; v2.0.0 holds\n // each vault's graph independent (matches `list_backlinks` semantics).\n //\n // Within a vault, `visited` is keyed by noteId so the dedup +\n // opacity check both work in O(1) per node. Per-vault `visited`\n // maps live for the BFS and are read by hydration immediately after.\n interface PerVaultState {\n vault: Vault;\n vaultName: string;\n scheme: string;\n visited: Map<number, VisitedEntry>;\n /** Seed noteIds that originated this vault's BFS — used for self-loop skip. */\n seedNoteIdsInVault: Set<number>;\n }\n const byVault = new Map<string, PerVaultState>();\n for (const r of resolved) {\n if (!byVault.has(r.vaultName)) {\n byVault.set(r.vaultName, {\n vault: r.vault,\n vaultName: r.vaultName,\n scheme: r.scheme,\n visited: new Map<number, VisitedEntry>(),\n seedNoteIdsInVault: new Set<number>(),\n });\n }\n byVault.get(r.vaultName)?.seedNoteIdsInVault.add(r.noteId);\n }\n\n // ── BFS per seed ─────────────────────────────────────────────────────────\n //\n // For each resolved seed, run up to two single-direction BFS sweeps\n // (forward + backward when direction === 'both'). Frontier elements\n // carry `{noteId, depth}`. At each depth < hops, query the typed-\n // edge namespace for outbound / inbound rows, apply edge-type\n // filter, skip self-loops + unresolved hyperlinks, then record the\n // candidate in `visited` IF (it's new) OR (the new path is shorter\n // per `isShorterPath`).\n //\n // The seed itself is never added to `visited` — it is the BFS root,\n // not an \"expansion result\". Test 17/18 pin this.\n for (const seed of resolved) {\n const state = byVault.get(seed.vaultName);\n if (!state) continue; // unreachable — we just set it.\n const directionsToWalk: Array<\"forward\" | \"backward\"> =\n direction === \"both\" ? [\"forward\", \"backward\"] : [direction];\n for (const dir of directionsToWalk) {\n let frontier: Array<{ noteId: number; depth: number }> = [{ noteId: seed.noteId, depth: 0 }];\n while (frontier.length > 0) {\n const next: Array<{ noteId: number; depth: number }> = [];\n for (const node of frontier) {\n const newHop: 1 | 2 = (node.depth + 1) as 1 | 2;\n if (newHop > hops) continue; // depth bound\n const rows =\n dir === \"forward\"\n ? seed.vault.db.edges.getForwardLinks(node.noteId, edgeTypeFilter)\n : seed.vault.db.edges.getBacklinks(node.noteId, edgeTypeFilter);\n for (const row of rows) {\n // Resolve the neighbor noteId. For forward edges, the\n // neighbor is `target_doc` (null = unresolved hyperlink —\n // skip; Phase 4 BFS only traverses resolved edges). For\n // backward edges, the neighbor is `source_doc` (always\n // non-null — every edge has a source).\n const targetNoteId =\n dir === \"forward\"\n ? // EdgeForwardLinkRow shape\n (row as { targetNoteId: number | null }).targetNoteId\n : (row as { sourceNoteId: number }).sourceNoteId;\n if (targetNoteId === null) continue; // unresolved hyperlink\n // Self-loop guard (test 17/18): a seed cannot appear in\n // its own results regardless of edge presence.\n if (targetNoteId === seed.noteId) continue;\n // Also skip ANY seed appearing as a 1/2-hop neighbor —\n // seeds are the BFS roots, not results. The plan §<action>\n // describes this as \"seeds are NOT added to visited\".\n // We DO allow OTHER seeds in the result set when expanded\n // from a non-seed source? Spec is ambiguous; the safer\n // reading is: a seed is never a RESULT of expand. Tests\n // 7/8 model the multi-seed case where one seed is reached\n // from another — but those tests assert dedup by hop, not\n // appearance. Re-reading test 8: \"a doc reachable in 1 hop\n // from seed B and 2 hops from seed A appears with via.\n // seed_doc_id === B and via.hop === 1.\" The doc is NOT a\n // seed itself in that test — it's a separate doc. So\n // skipping ALL seeds from the result set matches the\n // expected behavior (and matches recall/dossier semantics\n // where the query input is never echoed back).\n if (state.seedNoteIdsInVault.has(targetNoteId)) continue;\n const candidate: ViaTrace = {\n seed_doc_id: seed.seedDocId,\n hop: newHop,\n edge_type: row.type,\n direction: dir,\n };\n const existing = state.visited.get(targetNoteId);\n if (!existing || isShorterPath(candidate, existing.via)) {\n state.visited.set(targetNoteId, {\n via: candidate,\n inboundSourceNoteId: node.noteId,\n });\n // Only push into next frontier if more hops remain.\n if (newHop < hops) {\n next.push({ noteId: targetNoteId, depth: newHop });\n }\n }\n }\n }\n frontier = next;\n }\n }\n }\n\n // ── Hydration + filters ──────────────────────────────────────────────────\n //\n // For each visited noteId in each vault, load the source `Document`\n // via the injected SourceConnector seam, build the canonical 8-field\n // citation packet, then layer the additive `via` field. Apply the\n // three filters: `_memory` opacity, `include_superseded`, and\n // `filter_properties`.\n //\n // Stale rows (note deleted between BFS and hydration, or read fails)\n // are silently dropped — same defensive posture as dossier + recall.\n const documents: CitationPacketWithVia[] = [];\n for (const [, state] of byVault) {\n // Pre-compute the set of `_memory` noteIds in this vault's visited\n // map so the opacity check is a Set lookup. The check is per\n // candidate (O(1)). Seeds themselves are not in `visited` and so\n // do not participate; only candidates need the rule applied.\n const memoryVisited = new Set<number>();\n for (const [noteId] of state.visited) {\n const row = state.vault.db.notes.getById(noteId);\n if (row && isMemoryPath(row.path)) memoryVisited.add(noteId);\n }\n\n for (const [noteId, entry] of state.visited) {\n const noteRow = state.vault.db.notes.getById(noteId);\n if (!noteRow) continue; // stale BFS row — drop defensively.\n\n // ── _memory opacity rule (ADR-004 + Pitfall 3) ─────────────────\n //\n // A `_memory/...` doc surfaces in the result set ONLY when its\n // inbound BFS edge originates from a non-`_memory` source. The\n // edge's source is captured at frontier expansion as\n // `entry.inboundSourceNoteId` (no second DB query).\n //\n // Concretely:\n // - Candidate is non-`_memory` → always include (subject to\n // other filters).\n // - Candidate is `_memory` AND inbound source is also `_memory`\n // → drop (silent traversal through the memory namespace is\n // forbidden). Cite ADR-004 §\"memory namespace is sacrosanct\"\n // and Pitfall 3.\n // - Candidate is `_memory` AND inbound source is a non-\n // `_memory` user note → include (the user note already\n // references the memory doc; surfacing it does not break\n // opacity).\n //\n // Note: seeds are never `_memory` candidates here — they are\n // BFS roots and are not added to `visited`. If a user explicitly\n // requests a `_memory/...` seed, that's their call (a user-driven\n // action, not silent traversal), and the BFS expands from it\n // normally; but the seed itself is filtered out of results by\n // the seedNoteIdsInVault guard above.\n if (memoryVisited.has(noteId)) {\n const inboundSourceRow = state.vault.db.notes.getById(entry.inboundSourceNoteId);\n const inboundIsMemory = inboundSourceRow != null && isMemoryPath(inboundSourceRow.path);\n if (inboundIsMemory) continue;\n }\n\n // Load the canonical Document via the adapter seam (ADR-002 I-5b).\n const docId = formatDocId(state.scheme, state.vaultName, noteRow.path);\n const source = (() => {\n try {\n return deps.sourceConnectorFor(state.vaultName);\n } catch {\n return null;\n }\n })();\n if (!source) continue;\n let doc: Document;\n try {\n doc = await source.readDocument(docId);\n } catch {\n continue;\n }\n const packet = toCitationPacket(doc, displayUrlFor(docId, source));\n\n // ── include_superseded filter (D-08) ────────────────────────────\n //\n // Default false drops docs whose `properties.status === \"superseded\"`.\n // Forward-only supersede per Phase 2 D-03 means this is a pure\n // property check; no additional graph traversal needed.\n if (!opts.include_superseded && packet.properties.status === \"superseded\") {\n continue;\n }\n\n // ── filter_properties strict equality (D-08) ────────────────────\n //\n // Each key/value pair in `filter_properties` must match the\n // packet's `properties` strictly via `===`. No operators (no\n // $in, no $contains). Mirrors Plan 03 dossier convention.\n if (opts.filter_properties) {\n let match = true;\n for (const [key, want] of Object.entries(opts.filter_properties)) {\n if (packet.properties[key] !== want) {\n match = false;\n break;\n }\n }\n if (!match) continue;\n }\n\n documents.push({ ...packet, via: entry.via });\n }\n }\n\n return { documents, warnings };\n}\n","/**\n * `cluster()` — Phase 4 / 04-05 / GRA-02 Louvain community detection.\n *\n * Runs modularity-maximizing community detection (Blondel et al. 2008)\n * over the typed-edge graph via `graphology` + `graphology-communities-\n * louvain`. Returns one entry per community with deterministic\n * `cluster_id = smallest member DocId` (D-12, D-14).\n *\n * Locked contracts (Phase 4 CONTEXT.md):\n * - D-10 Algorithm: Louvain modularity-maximizing (over Label\n * Propagation / Connected Components).\n * - D-11 Implementation: pure-JS ESM via graphology + graphology-\n * communities-louvain (no native bindings, no LLM).\n * - D-12 Determinism contract — same input produces byte-identical\n * `cluster_id` assignment. Enforced by:\n * 1. Sort node DocIds lexicographically BEFORE insertion.\n * 2. Insert into `new Graph({type:\"undirected\", multi:false})`\n * in sorted order.\n * 3. Pass `seedrandom(\"vault-memory-cluster-v1\")` as\n * Louvain's `rng` option (Pitfall 1).\n * 4. `cluster_id = smallest member DocId per community`.\n * 5. Sort returned clusters by `cluster_id` ascending.\n * - D-13 Hard cap at 5000 nodes; structured error return; `force: true`\n * override.\n * - D-14 Per-cluster output:\n * { cluster_id, size, members: CitationPacket[],\n * summary: { top_types, top_titles, edge_density } }\n * All pure-deterministic — NO LLM enrichment (Phase 5 brief\n * layer owns LLM coupling over cluster output).\n * - D-15a `query` path composes existing primitives:\n * search_hybrid({query, limit: query_top_k ?? 50})\n * → expand({seed_doc_ids: top_k, hops: 1, direction: \"both\"})\n * → cluster the union.\n * `seed_doc_ids` path: cluster exactly that set + its induced\n * 1-hop neighborhood. Both `query` AND `seed_doc_ids` present\n * → return `{ok:false, reason:\"both_seeds_and_query\"}`.\n *\n * `_memory` opacity rule (ADR-004) is INHERITED from `expand()` (Plan\n * 04-03): `cluster()` calls expand() to compute the neighborhood; the\n * opacity filter applies there. This module does NOT re-implement the\n * rule — Test 10 in cluster.test.ts verifies inheritance.\n *\n * Pitfall 1 (RESEARCH.md §\"Louvain non-determinism\"): a second\n * `Math.random()` call site inside the louvain library would defeat\n * the seeded RNG. The determinism snapshot test in cluster.test.ts is\n * the regression gate that would catch any future library drift on\n * this assumption.\n *\n * Adapter-seam discipline: `graphology`, `graphology-communities-\n * louvain`, and `seedrandom` are imported ONLY in this file (per Plan\n * 04-05 Pattern A). No `fs`, `path`, `gray-matter`, or `chokidar`\n * imports. The library imports are pure-JS ESM with zero native\n * bindings, so the adapter-seam invariants are not weakened.\n *\n * References:\n * - Blondel et al. 2008, \"Fast unfolding of communities in large\n * networks\" — original Louvain paper.\n * - graphology / graphology-communities-louvain: Yomguithereal et al.,\n * MIT-licensed, https://graphology.github.io/.\n */\n\nimport Graph from \"graphology\";\nimport louvain from \"graphology-communities-louvain\";\nimport seedrandom from \"seedrandom\";\n\nimport { decomposeDocId, formatDocId, parseDocId } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport type { EdgeType } from \"../db/queries/edges.js\";\nimport { type CitationPacket, displayUrlFor, toCitationPacket } from \"../memory/citation-packet.js\";\nimport type { DocId, Document, SearchHit } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\nimport { expand } from \"./expand.js\";\n\n// ─── public types ────────────────────────────────────────────────────────────\n\n/**\n * Cluster() input — discriminated by which of `query` / `seed_doc_ids`\n * is present. Both-present is a runtime error (D-15a) returned as\n * `{ok:false, reason:\"both_seeds_and_query\"}`; we accept either at the\n * type level and validate at call time so callers can use a single\n * `ClusterOptions` variable.\n */\nexport type ClusterOptions =\n | {\n query: string;\n method: \"edge-community\";\n /**\n * Vault name to scope the `query` search against. CR-02: required\n * on multi-vault setups so the query path is deterministic; on\n * single-vault setups the dispatcher (server.ts) and the runtime\n * cluster() entry below default to the lone configured vault, so\n * single-vault callers can still omit it.\n *\n * Mirrors how `recall` and `search_sections` handle the same\n * constraint at the controller layer.\n */\n vault?: string;\n query_top_k?: number;\n force?: boolean;\n seed_doc_ids?: undefined;\n }\n | {\n seed_doc_ids: DocId[];\n method: \"edge-community\";\n force?: boolean;\n query?: undefined;\n vault?: undefined;\n };\n\n/**\n * Per-cluster output shape (D-14). All fields are pure-deterministic.\n * NO LLM enrichment — that's Phase 5 brief layer's job.\n */\nexport interface Cluster {\n /** Smallest member DocId in this community (lexicographic). */\n cluster_id: DocId;\n /** Member count — `members.length`. */\n size: number;\n /** Hydrated citation packets, one per member. */\n members: CitationPacket[];\n /** Pure-deterministic summary fields. */\n summary: ClusterSummary;\n}\n\nexport interface ClusterSummary {\n /** Top 5 `properties.type` values by count; ties broken alpha. */\n top_types: Array<{ type: string; count: number }>;\n /** Top 3 member titles by intra-cluster degree; ties broken by DocId asc. */\n top_titles: Array<{ title: string; degree: number }>;\n /** Intra-cluster edges ÷ (size choose 2). Zero when `size ≤ 1`. */\n edge_density: number;\n}\n\n/**\n * cluster() return — discriminated union. Hard-cap and mutual-exclusion\n * errors return `{ok:false, ...}`; success returns `{ok:true, clusters,\n * node_count}` with clusters sorted by `cluster_id` ascending.\n */\nexport type ClusterResult =\n | {\n ok: false;\n reason: \"node_count_exceeded\";\n node_count: number;\n threshold: 5000;\n hint: string;\n }\n | { ok: false; reason: \"both_seeds_and_query\"; hint: string }\n | {\n ok: false;\n reason: \"vault_required\";\n hint: string;\n configured_vaults: string[];\n }\n | { ok: true; clusters: Cluster[]; node_count: number };\n\n/**\n * Dependencies injected at call time. Mirrors `ExpandDeps` shape\n * (Plan 04-03) so production wiring and unit tests share one contract.\n *\n * `hybridSearch` is injected as a thin callback rather than imported\n * directly to avoid the `src/search/ → src/graph/cluster.ts →\n * src/search/hybrid.ts` circular dependency. The MCP tool dispatcher in\n * `src/server.ts` binds the real `hybridSearch` at call time.\n */\nexport interface ClusterDeps {\n manager: VaultManager;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n hybridSearch: (vault: Vault, query: string, limit: number) => Promise<SearchHit[]>;\n}\n\n// ─── constants (D-13) ───────────────────────────────────────────────────────\n\n/** Hard-cap on node count (D-13). `force: true` overrides. */\nconst NODE_CAP = 5000;\n/** Louvain seed string. Bump version when changing the determinism contract. */\nconst LOUVAIN_SEED = \"vault-memory-cluster-v1\";\n\n// ─── public entry point ─────────────────────────────────────────────────────\n\n/**\n * Cluster the union of seeds + their 1-hop neighborhood via Louvain\n * community detection. See file header for the full contract.\n */\nexport async function cluster(deps: ClusterDeps, opts: ClusterOptions): Promise<ClusterResult> {\n // ── D-15a mutual exclusion ────────────────────────────────────────────\n if (opts.query !== undefined && opts.seed_doc_ids !== undefined) {\n return {\n ok: false,\n reason: \"both_seeds_and_query\",\n hint: \"Pass exactly one of `query` or `seed_doc_ids`; not both.\",\n };\n }\n if (opts.query === undefined && opts.seed_doc_ids === undefined) {\n return {\n ok: false,\n reason: \"both_seeds_and_query\",\n hint: \"Pass exactly one of `query` or `seed_doc_ids`.\",\n };\n }\n\n // ── Resolve seeds (D-15a) ─────────────────────────────────────────────\n //\n // `query` path: search_hybrid → take top-K doc_ids → use as expand seeds.\n // `seed_doc_ids` path: use provided DocIds directly.\n let seedDocIds: DocId[] = [];\n let vault: Vault | null = null;\n let vaultName: string | null = null;\n let scheme: string | null = null;\n\n if (opts.query !== undefined) {\n // CR-02: resolve the working vault EXPLICITLY. The query path\n // historically scoped to `deps.manager.list()[0]` which silently\n // restricted multi-vault setups to whichever vault sorted first in\n // VaultManager insertion order — non-deterministic across users and\n // silently incomplete. We now require `opts.vault` on multi-vault\n // setups; single-vault setups still accept omission (the lone vault\n // is the only well-defined target).\n //\n // This mirrors how `recall` and `search_sections` enforce the same\n // constraint at the controller layer (server.ts).\n const limit = opts.query_top_k ?? 50;\n const allVaults = deps.manager.list();\n if (allVaults.length === 0) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n let workingVault: Vault | null = null;\n if (opts.vault !== undefined) {\n // Caller specified a vault — resolve via manager.require(), which\n // throws on unknown name. We translate that into a structured\n // {ok:false, reason:\"vault_required\"} (with the caller-supplied\n // name surfaced in `hint`) so the MCP boundary keeps a consistent\n // error envelope; unknown-vault is a caller mistake, not a\n // crash-worthy condition.\n try {\n workingVault = deps.manager.require(opts.vault);\n } catch {\n return {\n ok: false,\n reason: \"vault_required\",\n hint: `Unknown vault: \"${opts.vault}\". Pass one of the configured vault names.`,\n configured_vaults: allVaults.map((v) => v.config.name),\n };\n }\n } else if (allVaults.length === 1) {\n // Single configured vault — omission is fine, scope to that vault.\n workingVault = allVaults[0] ?? null;\n } else {\n // Multi-vault setup without an explicit `vault` filter — reject.\n // This is the CR-02 fix: silent first-vault-wins is replaced with\n // a clear error.\n return {\n ok: false,\n reason: \"vault_required\",\n hint: \"cluster() with `query` requires an explicit `vault:` parameter when multiple vaults are configured.\",\n configured_vaults: allVaults.map((v) => v.config.name),\n };\n }\n if (!workingVault) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n const hits = await deps.hybridSearch(workingVault, opts.query, limit);\n const ids: DocId[] = [];\n for (const h of hits) {\n if (h.doc_id !== undefined) ids.push(h.doc_id);\n }\n seedDocIds = ids;\n } else {\n seedDocIds = (opts.seed_doc_ids ?? []) as DocId[];\n }\n\n // Trivial empty case — no seeds → no clusters.\n if (seedDocIds.length === 0) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n\n // ── 1-hop expansion (D-15a \"induced 1-hop neighborhood\") ──────────────\n //\n // The `_memory` opacity rule is inherited from expand() — we do NOT\n // re-filter here. expand() returns CitationPacketWithVia[] (already\n // filtered for opacity + superseded). For cluster() we only need the\n // doc_ids; we re-hydrate properties/titles separately below to keep\n // the per-cluster member shape consistent for both the seed path AND\n // the query path.\n const expansion = await expand(\n {\n manager: deps.manager,\n sourceConnectorFor: deps.sourceConnectorFor,\n },\n { seed_doc_ids: seedDocIds, hops: 1, direction: \"both\" },\n );\n\n // Union: seeds ∪ 1-hop expansion (deduplicated, sorted).\n const allDocIdsSet = new Set<DocId>();\n for (const s of seedDocIds) allDocIdsSet.add(s);\n for (const d of expansion.documents) allDocIdsSet.add(d.doc_id);\n const sortedDocIds = Array.from(allDocIdsSet).sort() as DocId[];\n\n // Resolve the working vault from the first seed (or the first\n // expansion doc). All DocIds inside a single cluster() invocation are\n // assumed to share a vault (the typed-edge BFS is per-vault per Plan\n // 04-03); cross-vault clustering is out of scope for v2.0.0.\n if (vault === null) {\n const firstId = sortedDocIds[0];\n if (firstId === undefined) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n try {\n const parsed = parseDocId(firstId);\n const dec = decomposeDocId(parsed);\n vault = deps.manager.require(dec.authority);\n vaultName = dec.authority;\n scheme = dec.scheme;\n } catch {\n // Malformed DocId or unknown vault — return empty success rather\n // than crash. The expand() call above would have already returned\n // its own warnings for unknown seeds.\n return { ok: true, clusters: [], node_count: 0 };\n }\n }\n\n // Map DocIds ↔ noteIds for the SQL edge lookup. Skip DocIds that do\n // not resolve to a known note row (defensive — expand() may have\n // returned a doc whose note row was deleted between BFS and our\n // re-resolution).\n const docIdToNoteId = new Map<DocId, number>();\n const noteIdToDocId = new Map<number, DocId>();\n for (const docId of sortedDocIds) {\n try {\n const parsed = parseDocId(docId);\n const dec = decomposeDocId(parsed);\n if (dec.authority !== vaultName) continue; // skip cross-vault\n const note = vault.db.notes.getByPath(dec.resource);\n if (!note) continue;\n docIdToNoteId.set(docId, note.id);\n noteIdToDocId.set(note.id, docId);\n } catch {\n continue;\n }\n }\n\n // The actual node set is the docIds we successfully resolved.\n const resolvedDocIds = Array.from(docIdToNoteId.keys()).sort() as DocId[];\n\n // ── D-13 hard cap ─────────────────────────────────────────────────────\n if (resolvedDocIds.length > NODE_CAP && !opts.force) {\n return {\n ok: false,\n reason: \"node_count_exceeded\",\n node_count: resolvedDocIds.length,\n threshold: NODE_CAP,\n hint: \"pass force:true to compute\",\n };\n }\n\n // Empty / single node — nothing to cluster.\n if (resolvedDocIds.length === 0) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n if (resolvedDocIds.length === 1) {\n const singleId = resolvedDocIds[0];\n if (singleId === undefined) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n // Hydrate the lone member into a single-element cluster.\n const cp = await hydratePacket(deps, scheme!, vaultName!, singleId, vault);\n if (cp === null) {\n return { ok: true, clusters: [], node_count: 0 };\n }\n return {\n ok: true,\n node_count: 1,\n clusters: [\n {\n cluster_id: singleId,\n size: 1,\n members: [cp],\n summary: { top_types: [], top_titles: [], edge_density: 0 },\n },\n ],\n };\n }\n\n // ── Build graphology graph (D-12 step 1–3) ────────────────────────────\n //\n // Sorted DocId insertion is the FIRST determinism gate. The graph is\n // undirected (Louvain operates on undirected graphs) and `multi: false`\n // — parallel edges are collapsed to one. Self-loops are skipped.\n const g = new Graph({ type: \"undirected\", multi: false });\n for (const docId of resolvedDocIds) g.addNode(docId);\n\n const sortedNoteIds = resolvedDocIds.map((d) => docIdToNoteId.get(d)!);\n const edges = vault.db.edges.getAllForNodes(sortedNoteIds);\n for (const e of edges) {\n const srcDocId = noteIdToDocId.get(e.sourceDoc);\n const tgtDocId = noteIdToDocId.get(e.targetDoc);\n if (!srcDocId || !tgtDocId) continue;\n if (srcDocId === tgtDocId) continue; // skip self-loops defensively\n // Normalize endpoint order so (a,b) and (b,a) collapse to one.\n const a = srcDocId < tgtDocId ? srcDocId : tgtDocId;\n const b = srcDocId < tgtDocId ? tgtDocId : srcDocId;\n if (g.hasEdge(a, b)) continue;\n g.addEdge(a, b, { weight: 1 });\n }\n\n // ── Louvain with seeded RNG (D-12 step 4) ─────────────────────────────\n //\n // `randomWalk: true` keeps the algorithm's documented behavior; `rng`\n // overrides the library's internal `Math.random` use. See Pitfall 1.\n const rng = seedrandom(LOUVAIN_SEED);\n const detailed = louvain.detailed(g, {\n rng,\n randomWalk: true,\n });\n\n // `detailed.communities` maps nodeId (DocId) → community index.\n const communities = detailed.communities as Record<string, number>;\n\n // ── Group nodes by community → compute cluster_id + summary ──────────\n const byCommunity = new Map<number, DocId[]>();\n for (const [nodeId, communityIdx] of Object.entries(communities)) {\n const docId = nodeId as DocId;\n const arr = byCommunity.get(communityIdx);\n if (arr === undefined) byCommunity.set(communityIdx, [docId]);\n else arr.push(docId);\n }\n\n const clusters: Cluster[] = [];\n for (const [, memberDocIds] of byCommunity) {\n const sortedMembers = [...memberDocIds].sort() as DocId[];\n const firstMember = sortedMembers[0];\n if (firstMember === undefined) continue;\n const clusterId = firstMember;\n\n // Hydrate each member into a CitationPacket. Drop members that fail\n // to hydrate (note row deleted between BFS and now).\n const members: CitationPacket[] = [];\n for (const docId of sortedMembers) {\n const cp = await hydratePacket(deps, scheme!, vaultName!, docId, vault);\n if (cp !== null) members.push(cp);\n }\n if (members.length === 0) continue;\n\n const summary = computeSummary(members, sortedMembers, g);\n clusters.push({\n cluster_id: clusterId,\n size: members.length,\n members,\n summary,\n });\n }\n\n // ── D-12 step 5: sort clusters by cluster_id ascending ────────────────\n clusters.sort((a, b) => (a.cluster_id < b.cluster_id ? -1 : a.cluster_id > b.cluster_id ? 1 : 0));\n\n return { ok: true, clusters, node_count: resolvedDocIds.length };\n}\n\n// ─── helpers ────────────────────────────────────────────────────────────────\n\n/**\n * Hydrate a single DocId into a CitationPacket via the adapter seam.\n * Returns `null` when the source connector is unavailable or the read\n * fails — callers drop the member silently (same defensive posture as\n * expand()).\n */\nasync function hydratePacket(\n deps: ClusterDeps,\n scheme: string,\n vaultName: string,\n docId: DocId,\n _vault: Vault,\n): Promise<CitationPacket | null> {\n const source = (() => {\n try {\n return deps.sourceConnectorFor(vaultName);\n } catch {\n return null;\n }\n })();\n if (!source) return null;\n const canonicalDocId = formatDocId(scheme, vaultName, decomposeDocId(parseDocId(docId)).resource);\n let doc: Document;\n try {\n doc = await source.readDocument(canonicalDocId);\n } catch {\n return null;\n }\n return toCitationPacket(doc, displayUrlFor(canonicalDocId, source));\n}\n\n/**\n * D-14 summary computation — pure-deterministic, no LLM.\n *\n * - `top_types`: histogram over `members[*].properties.type`, sorted\n * by count desc; ties broken alphabetically; capped at 5.\n * - `top_titles`: per-member intra-cluster degree (count of edges in\n * `g` to OTHER cluster members); sorted by degree desc; ties broken\n * by DocId ascending; capped at 3.\n * - `edge_density`: |intra-cluster edges| / C(size, 2); 0 when\n * `size ≤ 1`.\n */\nfunction computeSummary(\n members: CitationPacket[],\n sortedDocIds: DocId[],\n g: Graph,\n): ClusterSummary {\n const size = members.length;\n\n // top_types histogram.\n const typeCounts = new Map<string, number>();\n for (const m of members) {\n const t = m.properties.type;\n if (typeof t !== \"string\") continue;\n typeCounts.set(t, (typeCounts.get(t) ?? 0) + 1);\n }\n const topTypes = Array.from(typeCounts.entries())\n .sort((a, b) => {\n if (a[1] !== b[1]) return b[1] - a[1]; // count desc\n return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0; // alpha asc\n })\n .slice(0, 5)\n .map(([type, count]) => ({ type, count }));\n\n // Intra-cluster degree per member. A node's degree within the cluster\n // is the count of its graphology neighbors that are ALSO in the\n // sortedDocIds set.\n const memberSet = new Set<string>(sortedDocIds);\n const degreeByDocId = new Map<DocId, number>();\n for (const docId of sortedDocIds) {\n if (!g.hasNode(docId)) {\n degreeByDocId.set(docId, 0);\n continue;\n }\n let d = 0;\n for (const neighbor of g.neighbors(docId)) {\n if (memberSet.has(neighbor)) d += 1;\n }\n degreeByDocId.set(docId, d);\n }\n\n // Member → title + degree. Sort desc by degree, ties by DocId asc.\n const titleEntries = members.map((m) => ({\n doc_id: m.doc_id,\n title: m.title,\n degree: degreeByDocId.get(m.doc_id) ?? 0,\n }));\n titleEntries.sort((a, b) => {\n if (a.degree !== b.degree) return b.degree - a.degree;\n return a.doc_id < b.doc_id ? -1 : a.doc_id > b.doc_id ? 1 : 0;\n });\n const topTitles = titleEntries.slice(0, 3).map(({ title, degree }) => ({ title, degree }));\n\n // edge_density.\n let edgeDensity = 0;\n if (size >= 2) {\n let intraEdgeCount = 0;\n // Count unique edges where both endpoints are in the cluster. We\n // iterate the cluster's nodes and count each (a,b) once by requiring\n // a < b in DocId order.\n for (const docId of sortedDocIds) {\n if (!g.hasNode(docId)) continue;\n for (const neighbor of g.neighbors(docId)) {\n if (!memberSet.has(neighbor)) continue;\n if (docId < neighbor) intraEdgeCount += 1;\n }\n }\n const possible = (size * (size - 1)) / 2;\n edgeDensity = possible > 0 ? intraEdgeCount / possible : 0;\n }\n\n return { top_types: topTypes, top_titles: topTitles, edge_density: edgeDensity };\n}\n","export { listBacklinks, listForwardLinks, findBrokenLinks } from \"./graph.js\";\nexport type { BacklinkResult, ForwardLinkResult, BrokenLinkResult, EdgeType } from \"./graph.js\";\n\n// ── Phase 4 / 04-03 / GRA-01: typed-edge BFS retrieval (`expand`) ──\nexport { expand, isShorterPath } from \"./expand.js\";\nexport type {\n ExpandOptions,\n ExpandDirection,\n ExpandDeps,\n ExpansionResult,\n ViaTrace,\n CitationPacketWithVia,\n} from \"./expand.js\";\n\n// ── Phase 4 / 04-05 / GRA-02: Louvain community detection (`cluster`) ──\nexport { cluster } from \"./cluster.js\";\nexport type {\n Cluster,\n ClusterDeps,\n ClusterOptions,\n ClusterResult,\n ClusterSummary,\n} from \"./cluster.js\";\n","/**\n * Hybrid search via Reciprocal Rank Fusion (RRF).\n *\n * Runs semantic (sqlite-vec L2 over embeddings) and BM25 (FTS5 over chunk text)\n * searches in parallel per vault, then merges their rankings using RRF — a\n * rank-only fusion technique that requires no score normalization between\n * methods.\n *\n * RRF formula (Cormack et al., 2009):\n * rrf_score(item) = Σ_R 1 / (k + rank_R(item))\n * where R ranges over input rankings and rank is 1-based; items missing from\n * a ranking contribute 0 from that ranking.\n *\n * Two-stage fan-out across vaults:\n * - Embed query once per distinct model name (vaults sharing a model share\n * the vector).\n * - Per vault, fire semantic + BM25 in parallel, RRF-merge their chunk-id\n * lists, hydrate hits, then global-sort across vaults and take topK.\n */\n\nimport type { OllamaClient } from \"../ollama/index.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { DocId, SearchHit, SourceHandle } from \"../types.js\";\nimport type { Reranker } from \"../rerank/index.js\";\nimport { formatDocId, parseSourceHandle } from \"../adapters/registry.js\";\nimport {\n expand,\n type CitationPacketWithVia,\n type ExpandDeps,\n type ExpandDirection,\n} from \"../graph/index.js\";\nimport type { EdgeType } from \"../db/queries/edges.js\";\n\nexport interface HybridSearchOptions {\n query: string;\n /** Pre-computed embedding model name. Used for two purposes:\n * 1) look up the active model_id in each vault's DB\n * 2) ensure the query is embedded with the same model the index used */\n embeddingModel: string;\n ollama: OllamaClient;\n vaults: readonly Vault[];\n topK?: number;\n /** RRF constant. Standard: 60. Higher = less emphasis on top ranks. */\n rrfK?: number;\n /** Whether to include the per-method scores in the breakdown. Default true. */\n includeBreakdown?: boolean;\n /**\n * Optional cross-encoder reranker. When provided, hybridSearch fans out\n * `topK × rerankFanOut` candidates from the RRF stage, runs the reranker\n * on those, then resorts by rerank score and returns the new topK.\n *\n * On reranker failure (throw), the un-reranked RRF order is returned.\n */\n reranker?: Reranker;\n /** Candidate pool size as a multiple of topK. Default 5.\n *\n * Sizing rationale: BGE-M3 cosine distances on prose vaults form tight\n * plateaus (all top-N within ~0.02 score). The reranker needs a wide\n * enough pool to include semantically-on-target chunks that the\n * embedding ranks just below the plateau crest. At topK=10, a fanOut\n * of 5 produces a 50-chunk pool — empirically enough to catch chunks\n * the bi-encoder placed in rank 30-50 due to plateau noise.\n *\n * Limitations: a wider pool cannot rescue chunks the bi-encoder ranks\n * beyond the pool. Cross-lingual queries against a model with weak\n * recall on the target language (e.g. BGE-M3 on EN→DE for some terms)\n * can place the relevant chunk past rank 150. The fix there is a model\n * switch, not a larger pool — pool growth costs reranker inference\n * linearly while the marginal recall gain plateaus.\n *\n * Diagnostic: when the highest rerank score across the pool stays\n * below ~0.1, that is a signal that the desired chunk was never in\n * the pool. See `vault-memory-eval-v3-results.md` for the BGE-M3\n * cross-lingual case study. */\n rerankFanOut?: number;\n // ── Phase 3 / 03-05 (D-07, D-08, ASM-07, ASM-08): post-RRF rescore + filter ──\n //\n // All four params are strictly optional with defaults that vanish\n // when unused. The v1-default path (none of these set) is\n // byte-identical to v1 by construction:\n // - recencyWeight=0 + authorityWeight=0 → rescore block short-circuits\n // - includeSuperseded=false + no superseded fixture → SQL filter is a no-op\n //\n /** Additive recency term: `recencyWeight × exp(-age_days / halfLifeDays)`.\n * Default 0 (term contributes nothing — v1 invariance). */\n recencyWeight?: number;\n /** Additive authority term: `authorityWeight × 1` for docs with\n * `frontmatter.authoritative === true`, `× 0` otherwise. Default 0. */\n authorityWeight?: number;\n /** Recency half-life (days). Default 30 (D-07). Exposed so tests can\n * set short half-lives for deterministic age math. */\n halfLifeDays?: number;\n /** When false (default), exclude chunks whose note is `status: superseded`\n * at SQL level via the FTS JOIN + vec0 post-filter (03-05 M4). */\n includeSuperseded?: boolean;\n /** Clock injection seam — defaults to `Date.now`. Mirrors the recall\n * controller's idiom (`src/memory/tools/recall.ts:~205`). */\n clock?: () => number;\n /**\n * Phase 3 / 03-05 (ASM-06): display-URL resolver seam.\n *\n * `hybridSearch` is L0 substrate and is not allowed to mint adapter\n * URL strings (ADR-002 §I-5b — `obsidian://` literals live only in // vault-memory:claude-ok\n * the source adapter or registry). Bootstrap supplies a closure that\n * delegates to the registered `SourceConnector.formatDisplayUrl` for\n * the relevant vault; tests can omit it (no `display_url` populated).\n */\n displayUrlFor?: (vaultName: string, notePath: string) => string;\n // ── Phase 4 / 04-04 / GRA-03 (D-15, D-16): additive auto-expansion ──\n //\n // When `opts.expand` is undefined (the v1/v2 default), this guard\n // short-circuits entirely — zero new DB reads, zero new computation,\n // preserving v1-baseline byte-identical behavior. Expand runs AFTER\n // Phase 3 recency/authority rescore so that expansions attach to the\n // RESCORED top-K (D-16). Expand never participates in score\n // computation; top-K ranking is stable.\n //\n // `expand` and `expandDeps` MUST be supplied together. When only\n // one is set, the guard silently no-ops (defensive: callers wiring\n // this up incrementally see no behavior change until both are\n // provided). The dependency injection mirrors `displayUrlFor` — the\n // graph-traversal seam stays out of hybrid.ts's transitive imports\n // by surfacing it as an optional dep on the call site.\n /** Optional auto-expansion settings (D-15). When set, each hit\n * gains an additive `expansions: CitationPacketWithVia[]` field. */\n expand?: {\n hops: 1 | 2;\n direction?: ExpandDirection;\n edge_types?: EdgeType[];\n };\n /** Injected dependencies required by `expand()` — `manager` and\n * `sourceConnectorFor`. Required whenever `expand` is set; ignored\n * otherwise. */\n expandDeps?: ExpandDeps;\n // ── Alias-aware query expansion (ISSUE-aliases-not-in-fulltext-retrieval) ──\n //\n // A note's frontmatter alias (e.g. `JHE` → \"Jörg Herbers\") lives in\n // `note_aliases`, NOT in `chunks_fts`. So `search_hybrid(\"JHE\")` ranks\n // notes whose BODY contains the token \"JHE\" and never surfaces the\n // person note the alias points to. When the trimmed query EXACTLY\n // matches a known alias in a searched vault, inject/promote that\n // target note to the top of the result list. Surgical by design:\n // only fires on an exact alias match, never touches BM25/semantic\n // scoring, so non-alias queries are byte-identical to before (no FTS\n // re-baseline). Default ON; set false to restore pre-fix behavior.\n aliasExpansion?: boolean;\n}\n\nconst DEFAULT_TOP_K = 10;\nconst DEFAULT_RRF_K = 60;\n/** Minimum non-whitespace chars a chunk must contain to be sent to the\n * reranker. Defends against degenerate near-empty chunks that survived\n * the chunker (e.g. cross-version DBs) — they produce a constant rerank\n * score across the pool and dilute the top-k. */\nconst MIN_RERANK_TRIM_CHARS = 20;\n\n/**\n * Internal: ranked list of opaque item identifiers + the raw scores that\n * produced the ranking. Items must already be in best→worst order.\n */\nexport interface RankedList<T> {\n /** Items in best→worst order (rank 1 = items[0]). */\n items: readonly T[];\n /** Raw score per item, parallel to `items`. Optional — only used for\n * breakdowns; RRF itself ignores it. */\n scores?: ReadonlyMap<T, number>;\n}\n\nexport interface RrfMergeResult<T> {\n item: T;\n rrf: number;\n /** 1-based rank in each input list, or undefined if the item was absent. */\n ranks: (number | undefined)[];\n}\n\n/**\n * Pure RRF merge over N ranked lists. Exported for unit testing.\n *\n * Result is sorted by rrf desc; ties broken by lower minimum rank.\n */\nexport function rrfMerge<T>(\n rankings: ReadonlyArray<RankedList<T>>,\n k: number = DEFAULT_RRF_K,\n): RrfMergeResult<T>[] {\n const scores = new Map<T, { rrf: number; ranks: (number | undefined)[] }>();\n\n rankings.forEach((list, listIdx) => {\n list.items.forEach((item, i) => {\n const rank = i + 1;\n const contribution = 1 / (k + rank);\n const existing = scores.get(item);\n if (existing) {\n existing.rrf += contribution;\n existing.ranks[listIdx] = rank;\n } else {\n const ranks: (number | undefined)[] = new Array(rankings.length).fill(undefined);\n ranks[listIdx] = rank;\n scores.set(item, { rrf: contribution, ranks });\n }\n });\n });\n\n const out: RrfMergeResult<T>[] = [];\n for (const [item, v] of scores) {\n out.push({ item, rrf: v.rrf, ranks: v.ranks });\n }\n out.sort((a, b) => {\n if (b.rrf !== a.rrf) return b.rrf - a.rrf;\n return minDefined(a.ranks) - minDefined(b.ranks);\n });\n return out;\n}\n\nfunction minDefined(xs: (number | undefined)[]): number {\n let m = Number.POSITIVE_INFINITY;\n for (const x of xs) {\n if (x !== undefined && x < m) m = x;\n }\n return m;\n}\n\ninterface PerVaultHit {\n vaultName: string;\n chunkId: number;\n rrf: number;\n semanticScore?: number;\n textScore?: number;\n /** Set when a reranker re-scored this candidate. */\n rerankScore?: number;\n}\n\nexport async function hybridSearch(opts: HybridSearchOptions): Promise<SearchHit[]> {\n const topK = opts.topK ?? DEFAULT_TOP_K;\n const rrfK = opts.rrfK ?? DEFAULT_RRF_K;\n const includeBreakdown = opts.includeBreakdown ?? true;\n const query = opts.query.trim();\n\n if (topK <= 0 || query.length === 0 || opts.vaults.length === 0) {\n return [];\n }\n\n // Per-run query-embedding cache, keyed by model name. Multiple vaults\n // sharing the same embedding model only pay one Ollama round-trip.\n const embedCache = new Map<string, Promise<number[] | null>>();\n const getQueryVector = (model: string): Promise<number[] | null> => {\n const cached = embedCache.get(model);\n if (cached) return cached;\n const p = (async (): Promise<number[] | null> => {\n try {\n const res = await opts.ollama.embed({ model, texts: [query] });\n const v = res.vectors[0];\n return v ?? null;\n } catch {\n return null;\n }\n })();\n embedCache.set(model, p);\n return p;\n };\n\n const rerankFanOut = Math.max(1, opts.rerankFanOut ?? 5);\n // When reranking, we need a wider per-vault pool so the global candidate\n // set is large enough for the cross-encoder to re-order meaningfully.\n const perVaultTopN = opts.reranker ? topK * rerankFanOut : topK;\n\n // 03-05 M4: pass `excludeSuperseded` down to the candidate-list SQL.\n // The flag is read inside `searchOneVault` to pick the JOIN-and-filter\n // FTS statement and to post-filter the vec0 ANN result list via the\n // notes-status partial index. Filter happens at SQL level, not in JS.\n const excludeSuperseded = (opts.includeSuperseded ?? false) === false;\n const perVault = await Promise.all(\n opts.vaults.map((vault) =>\n searchOneVault(\n vault,\n query,\n opts.embeddingModel,\n rrfK,\n perVaultTopN,\n getQueryVector,\n excludeSuperseded,\n ),\n ),\n );\n\n // Global merge: each vault already returned its top-N RRF hits. We\n // re-sort by RRF score across vaults and take the candidate pool.\n const flat: PerVaultHit[] = perVault.flat();\n flat.sort((a, b) => b.rrf - a.rrf);\n\n // ── Phase 3 / 03-05 (D-07, ASM-07, ASM-11): post-RRF additive rescore ──\n //\n // Inserted BEFORE the reranker (the cross-encoder, when active, runs\n // on the rescored pool — rescore shapes the candidate-pool that the\n // reranker sees). When both weights are zero (v1 default), the guard\n // short-circuits entirely and the rescore loop does zero work and\n // zero DB reads — preserving v1 perf exactly.\n //\n // Math (per D-07):\n // final = rrf + recencyWeight × exp(-age_days / halfLifeDays)\n // + authorityWeight × (authoritative ? 1 : 0)\n //\n // Hydration here only fires when rescore weights are non-zero; the\n // `notes.mtime` + `notes.frontmatter` reads are cheap (`getById` is\n // a PK lookup) and only happen for the top-N candidates already in\n // `flat`, never for the full candidate pool. The v1 invariance test\n // (`hybrid.rescore.test.ts`) pins this — same DB-read count as v1.\n const recencyWeight = opts.recencyWeight ?? 0;\n const authorityWeight = opts.authorityWeight ?? 0;\n if (recencyWeight !== 0 || authorityWeight !== 0) {\n const clock = opts.clock ?? Date.now;\n const now = clock();\n const halfLifeMs = (opts.halfLifeDays ?? 30) * 24 * 60 * 60 * 1000;\n const vaultByNameLocal = new Map<string, Vault>();\n for (const v of opts.vaults) vaultByNameLocal.set(v.config.name, v);\n for (const h of flat) {\n const vault = vaultByNameLocal.get(h.vaultName);\n if (!vault) continue;\n const chunk = vault.db.chunks.getById(h.chunkId);\n if (!chunk) continue;\n const note = vault.db.notes.getById(chunk.note_id);\n if (!note) continue;\n const ageMs = Math.max(0, now - note.mtime);\n const recencyTerm = recencyWeight * Math.exp(-ageMs / halfLifeMs);\n let authoritative = false;\n if (authorityWeight !== 0 && note.frontmatter) {\n try {\n const fm = JSON.parse(note.frontmatter) as Record<string, unknown>;\n authoritative = fm[\"authoritative\"] === true;\n } catch {\n // Malformed JSON in notes.frontmatter is treated as\n // non-authoritative — never throw out of the rescore loop.\n authoritative = false;\n }\n }\n const authorityTerm = authorityWeight * (authoritative ? 1.0 : 0);\n h.rrf += recencyTerm + authorityTerm;\n }\n flat.sort((a, b) => b.rrf - a.rrf);\n }\n\n // Optional cross-encoder rerank: re-score the global top-(topK*fanOut)\n // candidates with the reranker, then resort by rerank score. On any\n // failure, fall back silently to the RRF order.\n let winners: PerVaultHit[];\n if (opts.reranker && flat.length > 0) {\n const poolSize = Math.min(flat.length, topK * rerankFanOut);\n const pool = flat.slice(0, poolSize);\n const vaultByNameLocal = new Map<string, Vault>();\n for (const v of opts.vaults) vaultByNameLocal.set(v.config.name, v);\n const texts: string[] = [];\n const indexed: { hit: PerVaultHit; text: string }[] = [];\n for (const h of pool) {\n const vault = vaultByNameLocal.get(h.vaultName);\n if (!vault) continue;\n const chunk = vault.db.chunks.getById(h.chunkId);\n if (!chunk) continue;\n // Skip near-empty chunks: cross-encoder produces a near-constant\n // score for them, which would dilute the pool. They keep their RRF\n // position (still appear in `flat`) but are not re-ranked.\n if (chunk.text.trim().length < MIN_RERANK_TRIM_CHARS) continue;\n indexed.push({ hit: h, text: chunk.text });\n texts.push(chunk.text);\n }\n if (indexed.length === 0) {\n // All pool candidates were filtered as too-short — fall back to RRF\n // order across `flat` rather than calling the reranker on nothing.\n winners = flat.slice(0, topK);\n } else\n try {\n const scores = await opts.reranker.score(query, texts);\n if (scores.length !== indexed.length) {\n throw new Error(`reranker returned ${scores.length} scores for ${indexed.length} chunks`);\n }\n for (let i = 0; i < indexed.length; i++) {\n const entry = indexed[i]!;\n const s = scores[i]!;\n entry.hit.rerankScore = s;\n }\n const reranked = indexed.map((e) => e.hit);\n reranked.sort((a, b) => {\n const ra = a.rerankScore ?? Number.NEGATIVE_INFINITY;\n const rb = b.rerankScore ?? Number.NEGATIVE_INFINITY;\n if (rb !== ra) return rb - ra;\n return b.rrf - a.rrf;\n });\n winners = reranked.slice(0, topK);\n } catch {\n // Reranker failed — fall back to RRF order. Clear any partial\n // rerankScore so the breakdown does not misrepresent the result.\n for (const h of pool) delete h.rerankScore;\n winners = flat.slice(0, topK);\n }\n } else {\n winners = flat.slice(0, topK);\n }\n\n // Hydrate to SearchHit. Look up via the originating vault's DB.\n const vaultByName = new Map<string, Vault>();\n for (const v of opts.vaults) vaultByName.set(v.config.name, v);\n\n const hits: SearchHit[] = [];\n for (const h of winners) {\n const vault = vaultByName.get(h.vaultName);\n if (!vault) continue;\n const chunk = vault.db.chunks.getById(h.chunkId);\n if (!chunk) continue;\n const note = vault.db.notes.getById(chunk.note_id);\n if (!note) continue;\n const hit: SearchHit = {\n vault: vault.config.name,\n notePath: note.path,\n noteTitle: note.title,\n chunkText: chunk.text,\n chunkIdx: chunk.idx,\n headingPath: chunk.heading_path,\n // Surface the rerank score as the primary score when present —\n // it's the final order the caller sees.\n score: h.rerankScore ?? h.rrf,\n };\n if (includeBreakdown) {\n const breakdown: NonNullable<SearchHit[\"scoreBreakdown\"]> = {\n rrf: h.rrf,\n };\n if (h.semanticScore !== undefined) breakdown.semantic = h.semanticScore;\n if (h.textScore !== undefined) breakdown.text = h.textScore;\n if (h.rerankScore !== undefined) breakdown.rerank = h.rerankScore;\n hit.scoreBreakdown = breakdown;\n }\n // ── Phase 3 / 03-05 (ASM-06, D-08): hydrate 9 optional citation fields ──\n //\n // All piggyback on the `note` + `chunk` rows already loaded above —\n // no extra DB read for mtime/hash/status/properties. `heading_path`\n // needs one extra indexed lookup via `SectionsQueries.findContainingChunk`\n // (O(log N) on the `sections_chunk_range` index from migration 010).\n //\n // Per D-08 these fields are additive: v1 callers see a SearchHit\n // whose JSON output is byte-identical to v1 because every new field\n // either populates with a value or is left undefined (and omitted\n // from the JSON serialization).\n let docId: DocId | undefined;\n let sourceHandle: SourceHandle | undefined;\n try {\n docId = formatDocId(\"obsidian-fs\", vault.config.name, note.path);\n sourceHandle = parseSourceHandle(`obsidian-fs://${vault.config.name}`);\n } catch {\n // Malformed vault-name / path → keep doc_id / source_handle\n // undefined rather than failing the whole hit.\n }\n if (docId !== undefined) hit.doc_id = docId;\n if (sourceHandle !== undefined) hit.source_handle = sourceHandle;\n hit.mtime = note.mtime;\n hit.hash = note.hash;\n // Display URL via the injected resolver — keeps the URL minting\n // confined to the source adapter (ADR-002 §I-5b). When the resolver\n // is omitted (test fixtures, smoke tests), display_url stays\n // undefined and is omitted from the JSON response.\n if (opts.displayUrlFor !== undefined) {\n try {\n hit.display_url = opts.displayUrlFor(vault.config.name, note.path);\n } catch {\n // Resolver throws (e.g. unknown vault) → leave display_url\n // unset rather than fail the whole hit.\n }\n }\n // Frontmatter parse — best-effort. Stored as JSON-stringified text\n // by the indexer (src/indexer/indexer.ts:176); malformed JSON\n // produces undefined `properties` rather than throwing.\n let props: Record<string, unknown> | undefined;\n if (note.frontmatter) {\n try {\n props = JSON.parse(note.frontmatter) as Record<string, unknown>;\n } catch {\n props = undefined;\n }\n }\n if (props !== undefined) hit.properties = props;\n // Read denormalized status directly from notes table — single column\n // lookup, no JSON parse. Falls through to props-derived only when the\n // denormalized column is null (legacy / pre-backfill rows).\n const status = vault.db.notes.getStatus(note.id);\n if (typeof status === \"string\") {\n hit.status = status;\n } else if (typeof props?.status === \"string\") {\n hit.status = props.status;\n }\n if (typeof props?.[\"superseded_by\"] === \"string\") {\n hit.superseded_by = props[\"superseded_by\"] as string;\n }\n // Section heading path — promote chunk → enclosing section when one\n // exists. The query is indexed (`sections_chunk_range`) and runs at\n // most once per result hit, so the cost stays bounded by topK.\n const section = vault.db.sections.findContainingChunk(note.id, chunk.id);\n if (section) {\n try {\n hit.heading_path = JSON.parse(section.heading_path) as string[];\n } catch {\n // Malformed JSON heading_path → leave heading_path undefined.\n }\n }\n hits.push(hit);\n }\n\n // Alias-aware query expansion runs BEFORE expand so an injected alias-target\n // hit is part of the seed set and receives `expansions` like any other hit\n // (ISSUE-aliases-not-in-fulltext-retrieval). No-op for non-alias queries.\n injectAliasHits(hits, opts, query, includeBreakdown);\n\n // ── Phase 4 / 04-04 / GRA-03 (D-15, D-16): post-rescore expand attachment ──\n //\n // When `opts.expand` is undefined (the v1/v2 default), this guard\n // short-circuits entirely — zero new DB reads, zero new computation,\n // preserving v1-baseline byte-identical behavior. Expand runs AFTER\n // Phase 3 recency/authority rescore and AFTER hit hydration so that\n // expansions attach to the RESCORED top-K (D-16). Expand never\n // participates in score computation; top-K ranking is stable.\n //\n // Deviation from plan §<action> pseudocode (Rule 3 - Blocking):\n // the plan referenced `expand(vault, {...})` but the actual\n // `expand()` signature is `expand(deps, opts)` where `deps =\n // {manager, sourceConnectorFor}` (locked by Plan 04-03). We use the\n // real signature and inject deps via `opts.expandDeps`. A single\n // expand() call handles ALL hit seeds — `expand()` already groups\n // seeds by vault internally (see `src/graph/expand.ts` `byVault`\n // map), so cross-vault traversal is already prevented at the\n // expand() boundary (T-04-04-02 mitigation: per-vault BFS isolation\n // happens inside expand()).\n if (opts.expand && opts.expandDeps && hits.length > 0) {\n const seedDocIds: DocId[] = [];\n for (const hit of hits) {\n if (hit.doc_id !== undefined) seedDocIds.push(hit.doc_id);\n }\n if (seedDocIds.length > 0) {\n try {\n const expansionInput: Parameters<typeof expand>[1] = {\n seed_doc_ids: seedDocIds,\n hops: opts.expand.hops,\n direction: opts.expand.direction ?? \"both\",\n };\n if (opts.expand.edge_types !== undefined) {\n expansionInput.edge_types = opts.expand.edge_types;\n }\n const result = await expand(opts.expandDeps, expansionInput);\n // Group by `via.seed_doc_id` (D-15). One pass; O(n) where n is\n // the total expansion-doc count.\n const bySeed = new Map<DocId, CitationPacketWithVia[]>();\n for (const doc of result.documents) {\n const seedId = doc.via.seed_doc_id;\n const arr = bySeed.get(seedId);\n if (arr) arr.push(doc);\n else bySeed.set(seedId, [doc]);\n }\n for (const hit of hits) {\n if (hit.doc_id !== undefined) {\n hit.expansions = bySeed.get(hit.doc_id) ?? [];\n }\n }\n } catch {\n // Expand failures are silent. The rest of the hybrid result\n // is intact; only the `expansions` field stays unset. This\n // matches the defensive posture of the reranker fallback\n // (lines 342–347 above).\n }\n }\n }\n\n return hits;\n}\n\n/**\n * Alias-aware query expansion (ISSUE-aliases-not-in-fulltext-retrieval).\n *\n * If the exact query string is a known alias in one of the searched vaults,\n * ensure that alias's target note is in the result set, at the top. A note's\n * frontmatter alias lives in `note_aliases`, NOT in `chunks_fts`, so an\n * exact-alias query (e.g. \"JHE\") otherwise never surfaces the target note.\n *\n * Mutates `hits` in place (unshift/promote). Runs BEFORE the expand block so an\n * injected alias hit participates in expand seeding and gains `expansions` like\n * any organically-retrieved hit (preserves the D-16 with/without-expand\n * invariant). Guard: only fires when aliasExpansion !== false AND the query\n * exactly matches an alias — non-alias queries do zero extra DB work.\n */\nfunction injectAliasHits(\n hits: SearchHit[],\n opts: HybridSearchOptions,\n query: string,\n includeBreakdown: boolean,\n): void {\n if ((opts.aliasExpansion ?? true) !== true) return;\n for (const vault of opts.vaults) {\n let resolved;\n try {\n resolved = vault.db.aliases.resolve(query);\n } catch {\n continue; // alias table missing / malformed → skip this vault\n }\n if (!resolved) continue;\n const note = vault.db.notes.getById(resolved.note_id);\n if (!note) continue;\n // Already surfaced organically? Promote it to the front instead of\n // duplicating, so the alias target is the top hit either way.\n const existingIdx = hits.findIndex(\n (h) => h.vault === vault.config.name && h.notePath === note.path,\n );\n if (existingIdx >= 0) {\n const [existing] = hits.splice(existingIdx, 1);\n if (existing) hits.unshift(existing);\n return;\n }\n // Build a hit from the note's first chunk (person/stub notes may have\n // exactly one). If the note has no chunks, synthesize a minimal hit\n // from the note row so the alias still resolves to something useful.\n const firstChunk = vault.db.chunks.getByNote(note.id)[0];\n const aliasHit: SearchHit = {\n vault: vault.config.name,\n notePath: note.path,\n noteTitle: note.title,\n chunkText: firstChunk?.text ?? note.title,\n chunkIdx: firstChunk?.idx ?? 0,\n headingPath: firstChunk?.heading_path ?? null,\n // Alias matches are exact metadata hits — rank above fuzzy results.\n score: 1,\n };\n if (includeBreakdown) {\n aliasHit.scoreBreakdown = { rrf: 1, alias: resolved.alias };\n }\n try {\n aliasHit.doc_id = formatDocId(\"obsidian-fs\", vault.config.name, note.path);\n aliasHit.source_handle = parseSourceHandle(`obsidian-fs://${vault.config.name}`);\n } catch {\n // keep doc_id/source_handle undefined on malformed name/path\n }\n aliasHit.mtime = note.mtime;\n aliasHit.hash = note.hash;\n if (opts.displayUrlFor !== undefined) {\n try {\n aliasHit.display_url = opts.displayUrlFor(vault.config.name, note.path);\n } catch {\n // leave display_url unset on resolver throw\n }\n }\n if (note.frontmatter) {\n try {\n aliasHit.properties = JSON.parse(note.frontmatter) as Record<string, unknown>;\n } catch {\n // malformed frontmatter → no properties\n }\n }\n const status = vault.db.notes.getStatus(note.id);\n if (typeof status === \"string\") aliasHit.status = status;\n hits.unshift(aliasHit);\n // One exact-alias match is enough; the shortest-path winner already\n // won inside resolve(). Stop after the first vault that resolves it.\n return;\n }\n}\n\n/**\n * Search a single vault. Resolves semantic + BM25 in parallel, RRF-merges,\n * returns the vault's top-N candidates (we keep topK so the global merge\n * has enough to draw from).\n */\nasync function searchOneVault(\n vault: Vault,\n query: string,\n embeddingModelName: string,\n rrfK: number,\n topK: number,\n getQueryVector: (model: string) => Promise<number[] | null>,\n /** 03-05 M4: when true, the FTS path uses the JOIN-and-filter\n * prepared statement against `notes.status`, and the vec0 ANN\n * result list is post-filtered via `getSupersededChunkIds`. When\n * false (the v1 default), both candidate paths are byte-identical\n * to v1. */\n excludeSuperseded = false,\n): Promise<PerVaultHit[]> {\n const fanK = Math.max(topK * 3, topK);\n\n // Resolve the model to use for semantic search.\n //\n // Phase 7c follow-up (v0.7.2): the *active* model in the DB is the source\n // of truth — `switch_active_model` may have promoted a shadow model that\n // doesn't match the config's `default_embedding_model`. The config-named\n // model is only a fallback used when no active model has been registered\n // yet (fresh vault).\n const activeModel = vault.db.models.getActive();\n const queryModelName = activeModel?.name ?? embeddingModelName;\n const canRunSemantic = activeModel !== null;\n\n const semanticPromise: Promise<{\n chunkIds: number[];\n distances: Map<number, number>;\n } | null> = canRunSemantic\n ? (async () => {\n const vec = await getQueryVector(queryModelName);\n if (!vec) return null;\n const hits = vault.db.embeddings.searchSemantic(activeModel.id, vec, fanK);\n const distances = new Map<number, number>();\n const chunkIds: number[] = [];\n for (const h of hits) {\n chunkIds.push(h.chunkId);\n distances.set(h.chunkId, h.distance);\n }\n // 03-05 M4: post-filter vec0 KNN results via the notes_status\n // partial index. vec0 virtual tables don't compose with JOINs\n // the way FTS5 does, so the filter runs as a single follow-up\n // SQL with a parametric IN list. Still SQL-level — zero\n // frontmatter parses. v1 path (excludeSuperseded = false)\n // skips this entirely.\n if (excludeSuperseded && chunkIds.length > 0) {\n const supSet = vault.db.notes.getSupersededChunkIds(chunkIds);\n if (supSet.size > 0) {\n const filtered: number[] = [];\n for (const id of chunkIds) {\n if (!supSet.has(id)) filtered.push(id);\n else distances.delete(id);\n }\n return { chunkIds: filtered, distances };\n }\n }\n return { chunkIds, distances };\n })()\n : Promise.resolve(null);\n\n const bm25Promise: Promise<{\n chunkIds: number[];\n scores: Map<number, number>;\n }> = Promise.resolve().then(() => {\n const hits = vault.db.fts.search(query, fanK, false, excludeSuperseded);\n const scores = new Map<number, number>();\n const chunkIds: number[] = [];\n for (const h of hits) {\n chunkIds.push(h.chunkId);\n scores.set(h.chunkId, h.score);\n }\n return { chunkIds, scores };\n });\n\n const [semantic, bm25] = await Promise.all([semanticPromise, bm25Promise]);\n\n const rankings: RankedList<number>[] = [];\n if (semantic && semantic.chunkIds.length > 0) {\n rankings.push({ items: semantic.chunkIds, scores: semantic.distances });\n }\n if (bm25.chunkIds.length > 0) {\n rankings.push({ items: bm25.chunkIds, scores: bm25.scores });\n }\n\n if (rankings.length === 0) return [];\n\n // Track which list is which for breakdown extraction below.\n const semanticListIdx = semantic && semantic.chunkIds.length > 0 ? 0 : -1;\n const bm25ListIdx = rankings.length === 2 ? 1 : semanticListIdx === -1 ? 0 : -1;\n\n const merged = rrfMerge(rankings, rrfK).slice(0, topK);\n\n return merged.map((m) => {\n const hit: PerVaultHit = {\n vaultName: vault.config.name,\n chunkId: m.item,\n rrf: m.rrf,\n };\n if (semanticListIdx !== -1 && m.ranks[semanticListIdx] !== undefined) {\n const d = semantic!.distances.get(m.item);\n if (d !== undefined) hit.semanticScore = d;\n }\n if (bm25ListIdx !== -1 && m.ranks[bm25ListIdx] !== undefined) {\n const s = bm25.scores.get(m.item);\n if (s !== undefined) hit.textScore = s;\n }\n return hit;\n });\n}\n","/**\n * ContextFit CLI wrapper — the pinned subprocess contract (ADR-008).\n *\n * ContextFit (https://github.com/ContextFit/cf) is a Python, CPU-only,\n * token-native retrieval engine. vault-memory is Node/ESM, so we integrate\n * out-of-process by spawning the `contextfit` binary — no daemon, no shell.\n *\n * This module is the SOLE place that knows ContextFit's CLI flags and\n * `--json` output shape. The `parseQueryOutput` contract is asserted by\n * `cli.contract.test.ts` so an upstream change fails loudly rather than\n * silently mis-parsing.\n *\n * # Adapter-seam carve-out (ADR-002)\n * - `child_process` + raw path handling are ALLOWED inside this directory\n * (same class as `src/contracts/mcp-clients.ts` peer-MCP spawning). The\n * rest of the codebase reaches ContextFit only through `ContextFitBackend`.\n *\n * # CLI contract (contextfit 0.1.0, pinned)\n * contextfit --kb <dir> ingest <source> --rebuild-index-after-ingest\n * contextfit --kb <dir> query \"<text>\" --top-k <n> --method <m> --json\n * contextfit --kb <dir> stats\n */\n\n// cross-spawn (not node:child_process) — its spawn wrapper handles the fd /\n// argument edge cases that make raw `spawn` throw `EBADF` when vault-memory\n// runs as an MCP **stdio server** (the SDK transport holds the parent's\n// stdio fds). This is the same library the MCP SDK itself spawns through.\nimport spawn from \"cross-spawn\";\n\n/** A single retrieved chunk from `contextfit query --json` → `chunks[]`. */\nexport interface ContextFitChunk {\n rank: number;\n chunk_id: number;\n score: number;\n level: number;\n parent_id: number | null;\n token_count: number;\n semantic_id?: number[];\n /** `metadata.source` is the ABSOLUTE filesystem path ContextFit ingested. */\n metadata: { source?: string } & Record<string, unknown>;\n /** Decoded chunk text preview — used as the SearchHit chunkText. */\n preview: string;\n tokens?: number[];\n}\n\n/** Parsed shape of `contextfit query --json` (only the fields we consume). */\nexport interface ContextFitQueryResult {\n query: string;\n method: string;\n retrieved_chunks: number;\n chunks: ContextFitChunk[];\n}\n\nexport type ContextFitMethod = \"exact\" | \"bm25\" | \"sid\" | \"graph\" | \"hierarchy\" | \"hybrid\";\n\nexport interface ContextFitCliConfig {\n /** The `contextfit` executable (bare name on PATH, or absolute path). */\n command: string;\n /** Knowledge-base / index directory passed via `--kb`. Per-vault. */\n kbPath: string;\n /** Tokenizer (default cl100k_base). Passed via `--tokenizer`. */\n tokenizer?: string;\n /** Spawn timeout per call (ms). Default 120_000 for ingest, callers override. */\n timeoutMs?: number;\n}\n\nexport class ContextFitError extends Error {\n override readonly name = \"ContextFitError\";\n constructor(\n message: string,\n readonly code: \"ENOENT\" | \"NONZERO_EXIT\" | \"BAD_JSON\" | \"TIMEOUT\" = \"NONZERO_EXIT\",\n ) {\n super(message);\n }\n}\n\ninterface RunResult {\n stdout: string;\n stderr: string;\n}\n\n/**\n * Spawn `contextfit` with the given args (no shell). Resolves with stdout on\n * exit code 0; rejects with a typed ContextFitError otherwise. `--kb` and\n * `--tokenizer` are global flags and must precede the subcommand.\n */\nfunction runContextFit(\n cfg: ContextFitCliConfig,\n subcommandArgs: string[],\n timeoutMs: number,\n): Promise<RunResult> {\n const globalArgs = [\"--kb\", cfg.kbPath];\n if (cfg.tokenizer) globalArgs.push(\"--tokenizer\", cfg.tokenizer);\n const args = [...globalArgs, ...subcommandArgs];\n\n return new Promise((resolve, reject) => {\n // Pipe all three streams (via cross-spawn) and close stdin — contextfit\n // reads none. cross-spawn avoids the `spawn EBADF` the raw node spawn hits\n // under the MCP stdio server's fd state.\n let child;\n try {\n child = spawn(cfg.command, args, { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n } catch (err) {\n // `spawn` can throw SYNCHRONOUSLY (e.g. EBADF under heavy fd pressure\n // when many vault watchers are live). Surface a typed error; the caller\n // (runContextFitWithRetry) retries transient EBADF.\n const e = err as NodeJS.ErrnoException;\n reject(\n new ContextFitError(\n `contextfit spawn failed: ${e.message}`,\n e.code === \"ENOENT\" ? \"ENOENT\" : \"NONZERO_EXIT\",\n ),\n );\n return;\n }\n child.stdin?.end();\n let stdout = \"\";\n let stderr = \"\";\n let settled = false;\n\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n child.kill(\"SIGKILL\");\n reject(new ContextFitError(`contextfit timed out after ${timeoutMs}ms`, \"TIMEOUT\"));\n }, timeoutMs);\n\n child.stdout?.on(\"data\", (d: Buffer) => {\n stdout += d.toString();\n });\n child.stderr?.on(\"data\", (d: Buffer) => {\n stderr += d.toString();\n });\n child.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (err.code === \"ENOENT\") {\n reject(\n new ContextFitError(\n `contextfit not found (tried '${cfg.command}'). Install it with ` +\n `\\`pipx install contextfit\\` (or pip), or set the command path.`,\n \"ENOENT\",\n ),\n );\n } else {\n reject(new ContextFitError(`contextfit spawn failed: ${err.message}`));\n }\n });\n child.on(\"close\", (codeNum: number | null) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (codeNum === 0) {\n resolve({ stdout, stderr });\n } else {\n reject(\n new ContextFitError(\n `contextfit exited ${codeNum}: ${stderr.trim() || stdout.trim() || \"(no output)\"}`,\n \"NONZERO_EXIT\",\n ),\n );\n }\n });\n });\n}\n\n/**\n * Run `contextfit` with one retry on a transient EBADF. `uv_spawn` can fail\n * with EBADF when the process is under heavy file-descriptor pressure (e.g.\n * many chokidar vault watchers churning the fd table at the moment of spawn);\n * the condition is transient, so a short-delayed retry usually succeeds. Other\n * errors (ENOENT, non-zero exit, bad JSON) are NOT retried.\n */\nasync function runContextFitWithRetry(\n cfg: ContextFitCliConfig,\n subcommandArgs: string[],\n timeoutMs: number,\n): Promise<RunResult> {\n try {\n return await runContextFit(cfg, subcommandArgs, timeoutMs);\n } catch (err) {\n const isEbadf = err instanceof ContextFitError && /EBADF/.test(err.message);\n if (!isEbadf) throw err;\n await new Promise((r) => setTimeout(r, 50));\n return runContextFit(cfg, subcommandArgs, timeoutMs);\n }\n}\n\n/**\n * `contextfit ingest <source>` — (re)build the KB from a directory of files.\n * `--rebuild-index-after-ingest` ensures the BM25/SID indexes are queryable\n * immediately. Returns ContextFit's stdout (human-readable stats) for logging.\n */\nexport async function contextFitIngest(\n cfg: ContextFitCliConfig,\n source: string,\n opts: { chunkSize?: number; overlap?: number } = {},\n): Promise<string> {\n const args = [\"ingest\", source, \"--rebuild-index-after-ingest\"];\n if (opts.chunkSize !== undefined) args.push(\"--chunk-size\", String(opts.chunkSize));\n if (opts.overlap !== undefined) args.push(\"--overlap\", String(opts.overlap));\n const { stdout } = await runContextFitWithRetry(cfg, args, cfg.timeoutMs ?? 600_000);\n return stdout;\n}\n\n/**\n * `contextfit query \"<text>\" --json` — retrieve top-k chunks. Parses the\n * `chunks[]` array out of the JSON envelope. ContextFit prints a non-JSON\n * \"Loading LSH from disk...\" preamble to stdout before the JSON object, so we\n * slice from the first `{` to be robust.\n */\nexport async function contextFitQuery(\n cfg: ContextFitCliConfig,\n query: string,\n opts: { topK?: number; method?: ContextFitMethod } = {},\n): Promise<ContextFitQueryResult> {\n const args = [\"query\", query, \"--json\"];\n if (opts.topK !== undefined) args.push(\"--top-k\", String(opts.topK));\n if (opts.method !== undefined) args.push(\"--method\", opts.method);\n const { stdout } = await runContextFitWithRetry(cfg, args, cfg.timeoutMs ?? 30_000);\n return parseQueryOutput(stdout);\n}\n\n/**\n * Parse the JSON object out of `contextfit query --json` stdout. Tolerates a\n * non-JSON preamble (e.g. \"Loading LSH from disk...\") by slicing from the\n * first `{`. Throws ContextFitError(\"BAD_JSON\") on a malformed/empty result.\n * Exported for the contract test.\n */\nexport function parseQueryOutput(stdout: string): ContextFitQueryResult {\n const start = stdout.indexOf(\"{\");\n if (start === -1) {\n throw new ContextFitError(\n `contextfit query produced no JSON: ${stdout.slice(0, 200)}`,\n \"BAD_JSON\",\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(stdout.slice(start));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new ContextFitError(`contextfit query JSON parse failed: ${msg}`, \"BAD_JSON\");\n }\n const obj = parsed as Partial<ContextFitQueryResult>;\n if (!Array.isArray(obj.chunks)) {\n throw new ContextFitError(\n `contextfit query JSON missing 'chunks' array (got keys: ${Object.keys(obj ?? {}).join(\", \")})`,\n \"BAD_JSON\",\n );\n }\n return {\n query: typeof obj.query === \"string\" ? obj.query : \"\",\n method: typeof obj.method === \"string\" ? obj.method : \"hybrid\",\n retrieved_chunks:\n typeof obj.retrieved_chunks === \"number\" ? obj.retrieved_chunks : obj.chunks.length,\n chunks: obj.chunks as ContextFitChunk[],\n };\n}\n\n/** Probe: is the `contextfit` binary runnable? Returns version string or null. */\nexport async function contextFitProbe(cfg: Pick<ContextFitCliConfig, \"command\">): Promise<boolean> {\n try {\n await new Promise<void>((resolve, reject) => {\n // All-piped (not \"ignore\") to avoid `spawn EBADF` under the MCP stdio\n // server's fd state — same rationale as runContextFit above.\n const child = spawn(cfg.command, [\"--help\"], { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n child.stdin?.end();\n child.on(\"error\", reject);\n child.on(\"close\", (c: number | null) =>\n c === 0 ? resolve() : reject(new Error(`exit ${c}`)),\n );\n });\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Cross-process ingest mutex for the ContextFit KB (Issue #17).\n *\n * A ContextFit ingest is a FULL KB rebuild: it `rm`s the per-vault KB dir and\n * re-runs `contextfit ingest`. Two ingests against the same vault dir race —\n * the loser's temp files get clobbered mid-write and it crashes\n * (`FileNotFoundError: .../chunks/index.json.tmp`), potentially leaving a\n * half-written KB. The four ingest call sites (CLI `index`, serve note-write\n * refresh, serve file-watcher re-ingest, serve startup catch-up) live in\n * different processes and share no in-memory state, so an in-process guard\n * (the watcher's `cfReingestInFlight` boolean) cannot serialize them.\n *\n * This module provides a dedicated per-vault file lock —\n * `~/.vault-memory/locks/<vault>.ingest.lock` — held for the duration of one\n * ingest. It is DELIBERATELY SEPARATE from `src/brief/lock.ts`'s\n * `<vault>.lock`, which the staleness daemon holds for its whole lifetime;\n * reusing that lock would make every ingest on a serve process see it \"held\"\n * and skip forever.\n *\n * Contention policy is SKIP + a persisted dirty flag\n * (`<vault>.ingest.dirty`): a second-comer does not wait — it marks the vault\n * dirty and returns. When the lock holder finishes it checks the flag and does\n * exactly one trailing re-ingest, so the last change is never silently lost\n * even across processes. A dirty flag left behind by a crash is honored on the\n * next ingest or server startup.\n *\n * Lock lifetime is bounded by the ingest itself: ~1–1.5 min typically, and a\n * hard ceiling of the ingest spawn timeout (600 s, after which contextfit is\n * force-killed and the `finally` releases the lock). A crashed holder never\n * strands the lock: it records its PID and the next acquirer steals it when\n * that PID is dead (POSIX `kill(pid, 0)` → ESRCH), mirroring brief/lock.ts.\n */\n\n// vault-memory:claude-ok — process state (~/.vault-memory/locks/), not vault\n// content. Same lockfile carve-out as src/brief/lock.ts (ADR-005).\n\nimport { open, readFile, unlink, mkdir, writeFile, stat } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/** Test-only override for the ~/.vault-memory root. Production omits it. */\nexport interface IngestLockOptions {\n rootOverride?: string;\n}\n\nfunction lockDir(rootOverride?: string): string {\n if (rootOverride !== undefined) return join(rootOverride, \"locks\");\n return join(homedir(), \".vault-memory\", \"locks\");\n}\n\nfunction lockPath(vaultName: string, rootOverride?: string): string {\n return join(lockDir(rootOverride), `${vaultName}.ingest.lock`);\n}\n\nfunction dirtyPath(vaultName: string, rootOverride?: string): string {\n return join(lockDir(rootOverride), `${vaultName}.ingest.dirty`);\n}\n\n/** POSIX `kill(pid, 0)`: true if the pid is alive, false on ESRCH. */\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ESRCH\") return false;\n // EPERM (alive but inaccessible) or anything else → treat as alive so we\n // never steal a lock from a live peer.\n return true;\n }\n}\n\nasync function readOwnerPid(path: string): Promise<number | null> {\n try {\n const buf = await readFile(path, \"utf8\");\n const pid = parseInt(buf.trim(), 10);\n return Number.isFinite(pid) && pid > 0 ? pid : null;\n } catch {\n return null;\n }\n}\n\nexport type IngestLockResult =\n | { acquired: true; path: string }\n | { acquired: false; ownerPid: number; path: string };\n\n/**\n * Try to acquire the ingest lock for a vault. Atomic exclusive create via\n * `open(path, 'wx')`. On EEXIST: steal if the recorded PID is dead or the file\n * is malformed; otherwise return contended. Bounded retries so a racing peer\n * cannot loop us forever.\n */\nexport async function tryAcquireIngestLock(\n vaultName: string,\n options: IngestLockOptions = {},\n): Promise<IngestLockResult> {\n const dir = lockDir(options.rootOverride);\n await mkdir(dir, { recursive: true });\n const path = lockPath(vaultName, options.rootOverride);\n const MAX_ATTEMPTS = 3;\n\n const attempt = async (n: number): Promise<IngestLockResult> => {\n if (n > MAX_ATTEMPTS) return { acquired: false, ownerPid: -1, path };\n try {\n const handle = await open(path, \"wx\");\n try {\n await handle.writeFile(`${process.pid}\\n`);\n } finally {\n await handle.close();\n }\n return { acquired: true, path };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n const ownerPid = await readOwnerPid(path);\n if (ownerPid === null || !isProcessAlive(ownerPid)) {\n // Stale (dead owner) or malformed: unlink and retry.\n await unlink(path).catch(() => undefined);\n return attempt(n + 1);\n }\n return { acquired: false, ownerPid, path };\n }\n };\n\n return attempt(1);\n}\n\n/** Release the ingest lock. Safe to call even if we don't hold it. */\nexport async function releaseIngestLock(\n vaultName: string,\n options: IngestLockOptions = {},\n): Promise<void> {\n await unlink(lockPath(vaultName, options.rootOverride)).catch(() => undefined);\n}\n\n/** Mark a vault as needing a (re-)ingest — set by a skipped second-comer. */\nexport async function markIngestDirty(\n vaultName: string,\n options: IngestLockOptions = {},\n): Promise<void> {\n const dir = lockDir(options.rootOverride);\n await mkdir(dir, { recursive: true });\n await writeFile(dirtyPath(vaultName, options.rootOverride), `${process.pid}\\n`).catch(\n () => undefined,\n );\n}\n\n/** True if a dirty flag is present for the vault. */\nexport async function isIngestDirty(\n vaultName: string,\n options: IngestLockOptions = {},\n): Promise<boolean> {\n try {\n await stat(dirtyPath(vaultName, options.rootOverride));\n return true;\n } catch {\n return false;\n }\n}\n\n/** Clear the dirty flag — called by the lock holder before it ingests. */\nexport async function clearIngestDirty(\n vaultName: string,\n options: IngestLockOptions = {},\n): Promise<void> {\n await unlink(dirtyPath(vaultName, options.rootOverride)).catch(() => undefined);\n}\n","/**\n * ContextFitBackend — the CPU-only, token-native retrieval engine (ADR-008).\n *\n * A second retrieval engine selectable per vault via `backend = \"contextfit\"`.\n * Unlike the default Ollama+sqlite-vec path it needs NO embedding model and NO\n * GPU: ContextFit (a Python CLI) ingests the vault's markdown into a per-vault\n * knowledge-base directory and answers queries over it (BM25 + Semantic-IDs).\n *\n * Process model: out-of-process via the `contextfit` CLI (see `./cli.ts`). No\n * daemon — cold-start per call is fine at ContextFit's ~10 ms query latency.\n *\n * This adapter is engine-specific glue; it normalizes ContextFit results into\n * the canonical `SearchHit` so all downstream assembly/citation code stays\n * engine-agnostic.\n */\n\nimport { homedir } from \"node:os\";\nimport { rm } from \"node:fs/promises\";\nimport { join, relative, isAbsolute } from \"node:path\";\nimport type { VaultConfig, SearchHit } from \"../../../types.js\";\nimport {\n contextFitIngest,\n contextFitQuery,\n contextFitProbe,\n type ContextFitCliConfig,\n type ContextFitChunk,\n} from \"./cli.js\";\nimport {\n tryAcquireIngestLock,\n releaseIngestLock,\n markIngestDirty,\n isIngestDirty,\n clearIngestDirty,\n} from \"./ingest-lock.js\";\n\nconst DEFAULT_COMMAND = \"contextfit\";\n\n/** Per-vault ContextFit KB directory: ~/.vault-memory/contextfit/<name>/. */\nexport function contextFitKbDir(vaultName: string): string {\n return join(homedir(), \".vault-memory\", \"contextfit\", vaultName);\n}\n\n/** Build the CLI config for a vault from its VaultConfig. */\nexport function cliConfigForVault(vault: VaultConfig): ContextFitCliConfig {\n const cfg: ContextFitCliConfig = {\n command: vault.contextfit?.command ?? DEFAULT_COMMAND,\n kbPath: contextFitKbDir(vault.name),\n };\n if (vault.contextfit?.tokenizer) cfg.tokenizer = vault.contextfit.tokenizer;\n return cfg;\n}\n\nexport interface ContextFitIndexResult {\n /**\n * \"skipped\" (Issue #17): another ingest for this vault was already in flight,\n * so this call marked the vault dirty and returned WITHOUT ingesting. The\n * in-flight holder does a trailing re-ingest, so the change is not lost.\n * Callers treat \"skipped\" as success (no error), not failure.\n */\n status: \"completed\" | \"failed\" | \"skipped\";\n /** Human-readable stats line from ContextFit's ingest output. */\n stats: string;\n durationMs: number;\n error?: string;\n}\n\n/**\n * Index a vault with ContextFit: spawn `contextfit ingest <vaultPath>`. Full\n * (re)build — ContextFit owns its own incremental logic; we always pass the\n * vault root and `--rebuild-index-after-ingest` so the KB is immediately\n * queryable. Throws ContextFitError on spawn/exec failure (caller logs).\n */\nexport async function indexVaultWithContextFit(\n vault: VaultConfig,\n opts: {\n onProgress?: (msg: string) => void;\n /** Test-only: `~/.vault-memory` root override for the ingest lock. */\n lockRootOverride?: string;\n /**\n * Test-only dependency injection. Production omits these and the real\n * probe/ingest (which spawn the `contextfit` CLI) are used. Tests pass\n * fakes to exercise the lock/dirty/trailing-pass orchestration without the\n * binary.\n */\n _deps?: {\n probe?: (cfg: ContextFitCliConfig) => Promise<boolean>;\n ingest?: (cfg: ContextFitCliConfig, source: string) => Promise<string>;\n clearKb?: (kbPath: string) => Promise<void>;\n };\n } = {},\n): Promise<ContextFitIndexResult> {\n const log = opts.onProgress ?? (() => {});\n const cfg = cliConfigForVault(vault);\n const start = Date.now();\n const lockOpts =\n opts.lockRootOverride !== undefined ? { rootOverride: opts.lockRootOverride } : {};\n const probe =\n opts._deps?.probe ?? ((c: ContextFitCliConfig) => contextFitProbe({ command: c.command }));\n const ingest = opts._deps?.ingest ?? contextFitIngest;\n const clearKb = opts._deps?.clearKb ?? ((p: string) => rm(p, { recursive: true, force: true }));\n\n log(`ContextFit: ingesting ${vault.path} → ${cfg.kbPath}`);\n const available = await probe(cfg);\n if (!available) {\n return {\n status: \"failed\",\n stats: \"\",\n durationMs: Date.now() - start,\n error:\n `ContextFit CLI not runnable (tried '${cfg.command}'). Install with ` +\n `\\`pipx install contextfit\\` or set [[vaults]].contextfit.command.`,\n };\n }\n\n // Issue #17: serialize ingests cross-process. Second-comer marks the vault\n // dirty and skips (no wait, no wasted double-rebuild); the in-flight holder\n // does a trailing re-ingest so the latest change is captured.\n const lock = await tryAcquireIngestLock(vault.name, lockOpts);\n if (!lock.acquired) {\n await markIngestDirty(vault.name, lockOpts);\n log(`ContextFit: re-ingest already in progress (pid ${lock.ownerPid}); flagged for retry`);\n return { status: \"skipped\", stats: \"\", durationMs: Date.now() - start };\n }\n\n try {\n // Loop so a change that lands DURING our ingest triggers exactly one more\n // pass. Bounded to avoid an unbounded churn loop under constant writes; the\n // watcher's debounce already coalesces bursts, so 1 trailing pass suffices\n // in practice and MAX_PASSES is a safety backstop.\n const MAX_PASSES = 8;\n let stats = \"\";\n let passes = 0;\n do {\n // Clear the flag BEFORE ingesting: any write that arrives after this\n // point re-sets it and earns another pass; writes before it are already\n // captured by the rebuild we are about to do.\n await clearIngestDirty(vault.name, lockOpts);\n // ContextFit refuses to ingest into an existing KB (it finds the manifest\n // and exits non-zero, demanding --resume or a clean dir). Our index\n // semantics are always a FULL rebuild, so clear the KB dir first — this\n // makes re-index / live-reindex / write-refresh / catchup idempotent.\n await clearKb(cfg.kbPath);\n stats = await ingest(cfg, vault.path);\n passes += 1;\n } while (passes < MAX_PASSES && (await isIngestDirty(vault.name, lockOpts)));\n log(stats.trim().split(\"\\n\").slice(-3).join(\" · \"));\n return { status: \"completed\", stats, durationMs: Date.now() - start };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { status: \"failed\", stats: \"\", durationMs: Date.now() - start, error: message };\n } finally {\n await releaseIngestLock(vault.name, lockOpts);\n }\n}\n\n/**\n * Map a ContextFit `metadata.source` (absolute path ContextFit ingested) back\n * to a vault-relative POSIX path matching the `notes.path` convention. Returns\n * null when the source isn't under the vault root (defensive — skip the hit).\n */\nexport function sourceToNotePath(source: string | undefined, vaultPath: string): string | null {\n if (!source) return null;\n const rel = isAbsolute(source) ? relative(vaultPath, source) : source;\n if (rel.startsWith(\"..\")) return null; // outside the vault root\n return rel.split(/[\\\\/]/).join(\"/\");\n}\n\n/** Map one ContextFit chunk → SearchHit. Returns null for un-addressable hits. */\nfunction chunkToHit(chunk: ContextFitChunk, vault: VaultConfig): SearchHit | null {\n const notePath = sourceToNotePath(chunk.metadata?.source, vault.path);\n if (notePath === null) return null;\n // Derive a display title from the path basename (ContextFit doesn't return\n // a note title); downstream callers that need the real title re-read the note.\n const base = notePath.split(\"/\").pop() ?? notePath;\n const noteTitle = base.replace(/\\.md$/i, \"\");\n const hit: SearchHit = {\n vault: vault.name,\n notePath,\n noteTitle,\n chunkText: chunk.preview ?? \"\",\n chunkIdx: chunk.chunk_id,\n headingPath: null,\n score: chunk.score,\n scoreBreakdown: { contextfit: chunk.score },\n };\n return hit;\n}\n\n/**\n * Search a ContextFit-backed vault. Spawns `contextfit query`, maps the\n * returned chunks to SearchHit[] (vault-relative paths). Engine-agnostic\n * output — the caller treats these identically to Ollama-path hits.\n */\nexport async function searchVaultWithContextFit(\n vault: VaultConfig,\n query: string,\n opts: { topK?: number } = {},\n): Promise<SearchHit[]> {\n const cfg = cliConfigForVault(vault);\n const method = vault.contextfit?.method ?? \"hybrid\";\n const result = await contextFitQuery(cfg, query, {\n topK: opts.topK ?? 10,\n method,\n });\n const hits: SearchHit[] = [];\n for (const chunk of result.chunks) {\n const hit = chunkToHit(chunk, vault);\n if (hit) hits.push(hit);\n }\n return hits;\n}\n","/**\n * searchVaults — engine-dispatching search front-end (ADR-008).\n *\n * vault-memory supports two retrieval engines selectable per vault:\n * - \"ollama\" (default): Ollama embeddings + sqlite-vec + FTS5 hybrid\n * (`hybridSearch`).\n * - \"contextfit\": CPU-only token-native engine via its CLI\n * (`searchVaultWithContextFit`).\n *\n * Callers used to invoke `hybridSearch` directly. `searchVaults` is a\n * drop-in wrapper with the same options that partitions the requested vaults\n * by their configured `backend`, runs each group through the right engine,\n * and merges the results into one `SearchHit[]` sorted by score (descending),\n * truncated to `topK`. Engine-mixing is fine because every engine returns the\n * canonical `SearchHit`; scores are per-engine and only used for intra-result\n * ordering, never cross-engine semantics.\n *\n * When every vault is \"ollama\" (the common case), this delegates straight to\n * `hybridSearch` with zero behavior change.\n */\n\nimport type { SearchHit } from \"../types.js\";\nimport { hybridSearch, type HybridSearchOptions } from \"./hybrid.js\";\n\nfunction isContextFit(vault: HybridSearchOptions[\"vaults\"][number]): boolean {\n return vault.config.backend === \"contextfit\";\n}\n\nexport async function searchVaults(opts: HybridSearchOptions): Promise<SearchHit[]> {\n const topK = opts.topK ?? 10;\n const cfVaults = opts.vaults.filter(isContextFit);\n const ollamaVaults = opts.vaults.filter((v) => !isContextFit(v));\n\n // Fast path: no ContextFit vaults → behave exactly like hybridSearch.\n if (cfVaults.length === 0) {\n return hybridSearch(opts);\n }\n\n const { searchVaultWithContextFit } = await import(\"../adapters/retrieval/contextfit/index.js\");\n\n // Run ContextFit vaults (each via its CLI) and the Ollama group concurrently.\n // A failing ContextFit vault (CLI missing, bad KB) must not take down the\n // whole search — log to stderr and yield no hits for that vault.\n const cfPromise = Promise.all(\n cfVaults.map((v) =>\n searchVaultWithContextFit(v.config, opts.query, { topK }).catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[search:${v.config.name}] ContextFit query failed: ${msg}`);\n return [] as SearchHit[];\n }),\n ),\n );\n const ollamaPromise =\n ollamaVaults.length > 0\n ? hybridSearch({ ...opts, vaults: ollamaVaults })\n : Promise.resolve([] as SearchHit[]);\n\n const [cfResultsNested, ollamaResults] = await Promise.all([cfPromise, ollamaPromise]);\n const cfResults = cfResultsNested.flat();\n\n // Merge + sort by score desc, then truncate to topK. Scores are per-engine;\n // this ordering is best-effort across a heterogeneous result set (rare —\n // most setups are single-engine). Within a single engine the order is exact.\n const merged = [...ollamaResults, ...cfResults];\n merged.sort((a, b) => b.score - a.score);\n return merged.slice(0, topK);\n}\n","/**\n * Minimal glob-pattern matcher for vault-relative paths.\n *\n * Supports the Obsidian/gitignore-style subset we need:\n * - `*` matches zero or more chars except `/`\n * - `**` matches zero or more chars including `/`\n * - `?` matches exactly one char except `/`\n * - Other characters match literally (regex-special chars are escaped)\n *\n * No brace expansion, no character classes, no negation. If we ever need\n * those we'll add picomatch — but every additional dependency in this\n * package costs us npm-install pain (better-sqlite3 already gave us\n * trouble), so we keep it tiny.\n */\n\n/** Convert a glob into an anchored regex source. Cached per pattern. */\nconst cache = new Map<string, RegExp>();\n\nfunction compile(pattern: string): RegExp {\n const cached = cache.get(pattern);\n if (cached) return cached;\n\n let re = \"\";\n for (let i = 0; i < pattern.length; i++) {\n const ch = pattern[i]!;\n if (ch === \"*\") {\n if (pattern[i + 1] === \"*\") {\n re += \".*\";\n i++;\n } else {\n re += \"[^/]*\";\n }\n } else if (ch === \"?\") {\n re += \"[^/]\";\n } else if (/[.+^${}()|[\\]\\\\]/.test(ch)) {\n re += \"\\\\\" + ch;\n } else {\n re += ch;\n }\n }\n const compiled = new RegExp(`^${re}$`);\n cache.set(pattern, compiled);\n return compiled;\n}\n\n/**\n * True iff `path` matches any of the given glob patterns. Empty pattern\n * list returns false (no exclusion).\n */\nexport function matchesAnyGlob(path: string, patterns: readonly string[]): boolean {\n for (const p of patterns) {\n if (compile(p).test(path)) return true;\n }\n return false;\n}\n","export { hybridSearch, rrfMerge } from \"./hybrid.js\";\nexport type { HybridSearchOptions, RankedList, RrfMergeResult } from \"./hybrid.js\";\n// ADR-008: engine-dispatching search front-end. Drop-in for hybridSearch;\n// routes contextfit-backed vaults to the CPU-only engine, ollama vaults to\n// the embeddings+sqlite-vec hybrid, and merges.\nexport { searchVaults } from \"./dispatch.js\";\nexport { matchesAnyGlob } from \"./glob.js\";\n","/**\n * Cross-encoder reranker (Phase 7d, optional).\n *\n * `Reranker.score(query, chunks)` returns a relevance score per chunk —\n * higher = more relevant. Scores are NOT necessarily normalized between\n * runs; only their relative order matters within a single call.\n *\n * # Strategy\n *\n * Ollama hosts cross-encoder rerankers like `bge-reranker-v2-m3` (BAAI,\n * MIT-licensed, multilingual), but the server only exposes the embedding\n * layer — not the classification head that produces the actual relevance\n * logit. The community workaround\n * (https://github.com/overcuriousity/ollama-utils/tree/main/plugins/reranking-endpoint)\n * is:\n *\n * 1. Feed the model `\"Query: {q}\\n\\nDocument: {d}\\n\\nRelevance:\"` as\n * a single text input via /api/embed.\n * 2. Compute the L2 norm of the returned embedding vector.\n * 3. For bge-reranker models, *lower magnitude = more relevant*, so we\n * negate the magnitude to produce a \"higher = better\" score.\n *\n * This is a proxy, not the true classification logit, but it correlates\n * well enough in practice to be useful as a rerank signal on top of\n * hybrid retrieval. When/if Ollama exposes the classification head, or\n * when we ship an ONNX runtime, the `OllamaReranker` class can be\n * swapped out behind the same interface without API churn.\n *\n * # Failure semantics\n *\n * Reranking is strictly best-effort: any error from Ollama (network,\n * model not loaded, parse failure) causes `score()` to throw, and\n * callers MUST treat the failure as \"no rerank available\" and fall back\n * to the upstream ranking. See `hybridSearch` for the integration.\n */\n\nimport type { OllamaClient } from \"../ollama/index.js\";\n\nexport interface Reranker {\n /**\n * Score each chunk against the query. Returns one score per chunk,\n * in the same order as the input. Higher = more relevant.\n *\n * Throws on transport / parse failure. Callers should catch and fall\n * back to the un-reranked order.\n */\n score(query: string, chunks: readonly string[]): Promise<number[]>;\n}\n\nexport interface OllamaRerankerOptions {\n ollama: OllamaClient;\n model: string;\n}\n\n/**\n * Reranker backed by Ollama's /api/embed endpoint.\n *\n * Expects a cross-encoder model like `qllama/bge-reranker-v2-m3`. See\n * file header for the magnitude-as-proxy caveat.\n */\nexport class OllamaReranker implements Reranker {\n private readonly ollama: OllamaClient;\n private readonly model: string;\n\n constructor(opts: OllamaRerankerOptions) {\n this.ollama = opts.ollama;\n this.model = opts.model;\n }\n\n async score(query: string, chunks: readonly string[]): Promise<number[]> {\n if (chunks.length === 0) return [];\n const inputs = chunks.map((c) => formatPair(query, c));\n const res = await this.ollama.embed({ model: this.model, texts: inputs });\n if (res.vectors.length !== chunks.length) {\n throw new Error(`Reranker: expected ${chunks.length} vectors, got ${res.vectors.length}`);\n }\n // For bge-reranker: lower L2 magnitude ⇒ more relevant. Negate so\n // \"higher score = more relevant\" matches the Reranker contract.\n return res.vectors.map((v) => -l2Norm(v));\n }\n}\n\n/**\n * Format a query/document pair as a single string. Matches the prompt\n * shape used by overcuriousity/ollama-utils — keep stable so scores are\n * comparable across runs.\n */\nexport function formatPair(query: string, doc: string): string {\n return `Query: ${query}\\n\\nDocument: ${doc}\\n\\nRelevance:`;\n}\n\nfunction l2Norm(v: readonly number[]): number {\n let sum = 0;\n for (const x of v) sum += x * x;\n return Math.sqrt(sum);\n}\n","/**\n * ONNX-runtime cross-encoder reranker (Phase 8).\n *\n * Replaces the L2-norm proxy from Phase 7d (`OllamaReranker`) with a real\n * cross-encoder forward pass over BAAI/bge-reranker-v2-m3 (ONNX-quantized).\n *\n * # Model files\n *\n * Expects two files in `modelDir`:\n * - `model_quantized.onnx` (≈570 MB, INT8)\n * - `tokenizer.json` (≈17 MB)\n *\n * Both are downloaded by `scripts/download-reranker.sh` (or the\n * `vault-memory download-reranker` CLI subcommand) from\n * https://huggingface.co/onnx-community/bge-reranker-v2-m3-ONNX.\n *\n * # Output semantics\n *\n * The model outputs a single logit per (query, document) pair. We apply\n * sigmoid to map to [0, 1]; higher = more relevant — matching the\n * `Reranker` contract directly (no negation hack).\n *\n * # Lazy loading\n *\n * `onnxruntime-node` and `@huggingface/tokenizers` are imported lazily on\n * the first `score()` call so users who never enable `rerank:true` don't\n * pay the load cost (and so test runs without the model files pass).\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Reranker } from \"./reranker.js\";\n\nexport interface OnnxRerankerOptions {\n /** Directory containing `model_quantized.onnx` + `tokenizer.json`. */\n modelDir: string;\n /** Max sequence length per (query, doc) pair. Default 512. */\n maxLength?: number;\n}\n\ninterface LoadedSession {\n // Kept as `unknown` to avoid pulling the type at module load.\n session: any;\n tokenizer: any;\n ort: any;\n}\n\nexport class OnnxReranker implements Reranker {\n private readonly modelDir: string;\n private readonly maxLength: number;\n private loaded: LoadedSession | null = null;\n private loading: Promise<LoadedSession> | null = null;\n\n constructor(opts: OnnxRerankerOptions) {\n this.modelDir = opts.modelDir;\n this.maxLength = opts.maxLength ?? 512;\n }\n\n /**\n * Score each chunk against the query. Returns sigmoid(logit) per pair.\n * Throws if the model files are missing (with a copy-pasteable curl\n * command in the error message).\n */\n async score(query: string, chunks: readonly string[]): Promise<number[]> {\n if (chunks.length === 0) return [];\n const { session, tokenizer, ort } = await this.load();\n\n // Tokenize each (query, chunk) pair separately so we can build the\n // batch with per-row truncation to maxLength, then pad to the longest\n // row in the batch (saves work over padding everything to 512).\n const encoded = chunks.map((chunk) => {\n const enc = tokenizer.encode(query, { text_pair: chunk });\n let ids: number[] = enc.ids;\n let mask: number[] = enc.attention_mask;\n if (ids.length > this.maxLength) {\n ids = ids.slice(0, this.maxLength);\n mask = mask.slice(0, this.maxLength);\n }\n return { ids, mask };\n });\n\n const seqLen = Math.max(...encoded.map((e) => e.ids.length));\n const batch = encoded.length;\n const inputIds = new BigInt64Array(batch * seqLen);\n const attentionMask = new BigInt64Array(batch * seqLen);\n for (let i = 0; i < batch; i++) {\n const row = encoded[i]!;\n for (let j = 0; j < row.ids.length; j++) {\n inputIds[i * seqLen + j] = BigInt(row.ids[j]!);\n attentionMask[i * seqLen + j] = BigInt(row.mask[j]!);\n }\n // Remaining positions stay 0n (pad token id 0 for XLM-R / bge-m3).\n }\n\n const feeds: Record<string, any> = {\n input_ids: new ort.Tensor(\"int64\", inputIds, [batch, seqLen]),\n attention_mask: new ort.Tensor(\"int64\", attentionMask, [batch, seqLen]),\n };\n const out = await session.run(feeds);\n // The model exports its output as `logits`. Fall back to first key\n // for robustness against minor export variants.\n const logitsTensor = out.logits ?? out[Object.keys(out)[0] as keyof typeof out];\n const data = logitsTensor.data as Float32Array;\n // logits shape: [batch, 1] — one score per pair. Sigmoid → [0, 1].\n const scores: number[] = new Array(batch);\n for (let i = 0; i < batch; i++) {\n scores[i] = sigmoid(data[i]!);\n }\n return scores;\n }\n\n private async load(): Promise<LoadedSession> {\n if (this.loaded) return this.loaded;\n if (this.loading) return this.loading;\n this.loading = (async () => {\n const modelPath = join(this.modelDir, \"model_quantized.onnx\");\n const tokenizerPath = join(this.modelDir, \"tokenizer.json\");\n if (!existsSync(modelPath)) {\n throw new Error(\n `OnnxReranker: model file not found at ${modelPath}. ` +\n `Run: curl -L https://huggingface.co/onnx-community/bge-reranker-v2-m3-ONNX/resolve/main/onnx/model_quantized.onnx -o ${modelPath}`,\n );\n }\n if (!existsSync(tokenizerPath)) {\n throw new Error(\n `OnnxReranker: tokenizer file not found at ${tokenizerPath}. ` +\n `Run: curl -L https://huggingface.co/onnx-community/bge-reranker-v2-m3-ONNX/resolve/main/tokenizer.json -o ${tokenizerPath}`,\n );\n }\n const [ort, tokMod, tokJson] = await Promise.all([\n import(\"onnxruntime-node\"),\n import(\"@huggingface/tokenizers\"),\n readFile(tokenizerPath, \"utf-8\"),\n ]);\n // @huggingface/tokenizers expects two args: the tokenizer.json object\n // *and* a separate config object with special-token strings (bos/eos/\n // pad/unk). HF distributions ship that as tokenizer_config.json, but\n // for bge-reranker-v2-m3 only tokenizer.json is published. We derive\n // the config from added_tokens — known stable: XLM-RoBERTa schema\n // (<s>=0, <pad>=1, </s>=2, <unk>=3).\n const tokenizerJson = JSON.parse(tokJson);\n const config = deriveTokenizerConfig(tokenizerJson);\n const tokenizer = new (tokMod as any).Tokenizer(tokenizerJson, config);\n const session = await (ort as any).InferenceSession.create(modelPath);\n const loaded: LoadedSession = { session, tokenizer, ort };\n this.loaded = loaded;\n return loaded;\n })();\n return this.loading;\n }\n}\n\nfunction sigmoid(x: number): number {\n return 1 / (1 + Math.exp(-x));\n}\n\n/**\n * Derive the tokenizer config (special-token strings) from added_tokens.\n * @huggingface/tokenizers needs this as a second constructor arg; HF\n * usually ships it as a separate tokenizer_config.json, but bge-reranker-\n * v2-m3 only publishes tokenizer.json — so we reconstruct from added_tokens.\n *\n * Falls back to XLM-RoBERTa defaults (the reranker's base architecture).\n */\nfunction deriveTokenizerConfig(tokenizerJson: any): Record<string, string> {\n const added: Array<{ id: number; content: string; special?: boolean }> =\n tokenizerJson.added_tokens ?? [];\n const byContent = new Map(added.map((t) => [t.content, t]));\n const pick = (...candidates: string[]): string => {\n for (const c of candidates) if (byContent.has(c)) return c;\n return candidates[0]!;\n };\n return {\n bos_token: pick(\"<s>\"),\n eos_token: pick(\"</s>\"),\n pad_token: pick(\"<pad>\"),\n unk_token: pick(\"<unk>\"),\n };\n}\n","export { OllamaReranker, formatPair } from \"./reranker.js\";\nexport type { Reranker, OllamaRerankerOptions } from \"./reranker.js\";\nexport { OnnxReranker } from \"./onnx-reranker.js\";\nexport type { OnnxRerankerOptions } from \"./onnx-reranker.js\";\n","/**\n * MCP response helpers — `ok` / `errorResponse` / `errorResponseJson`.\n *\n * Extracted verbatim from `src/server.ts` (the bootstrap god-file). These\n * shape the `{ content: [{ type: \"text\", text }] }` / `isError` envelopes\n * the MCP SDK expects. Zero closure dependencies, zero runtime imports.\n *\n * # Adapter-seam discipline\n *\n * Pure helpers. No node:path / node:fs / chokidar / gray-matter imports.\n */\n\nexport function ok(data: object): { content: Array<{ type: \"text\"; text: string }> } {\n return {\n content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }],\n };\n}\n\nexport function errorResponse(message: string): {\n isError: true;\n content: Array<{ type: \"text\"; text: string }>;\n} {\n return {\n isError: true,\n content: [{ type: \"text\", text: message }],\n };\n}\n\n/**\n * Structured `isError: true` response — the JSON payload is stringified\n * into the single `text` content block. Used by Phase 3 assembly tools\n * for the `{error: \"doc_not_found\", doc_id}` contract (plan 03-02).\n * Distinct from `errorResponse` (free-text) so callers can pattern-match\n * `JSON.parse(content[0].text).error === \"doc_not_found\"`.\n */\nexport function errorResponseJson(payload: object): {\n isError: true;\n content: Array<{ type: \"text\"; text: string }>;\n} {\n return {\n isError: true,\n content: [{ type: \"text\", text: JSON.stringify(payload) }],\n };\n}\n","/**\n * Pure utility helpers extracted from `src/server.ts` (the bootstrap\n * god-file). None of these close over `serve()` state — they take their\n * inputs as explicit parameters.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. The string\n * `.split(\"/\")` operations in `decodeNoteId` / `defaultBasename` /\n * `normalizeFolderHint` are plain string manipulation, NOT `node:path`.\n * Display-URL routing delegates to the adapter registry seam\n * (`parseSourceHandle` / `formatDocId` / `SourceConnector.formatDisplayUrl`).\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\nimport type { VaultManager } from \"../vault/index.js\";\nimport type { AdapterRegistry } from \"../adapters/registry.js\";\nimport { formatDocId, parseSourceHandle } from \"../adapters/registry.js\";\n\nexport function countWords(content: string): number {\n if (content.length === 0) return 0;\n return content.split(/\\s+/).filter((s) => s.length > 0).length;\n}\n\n/**\n * Resolve which vaults a search should hit.\n *\n * Scope resolution (priority highest first):\n * 1. Explicit `vaultFilter` from the request → exactly those vaults.\n * 2. `activeVault` from VAULT_MEMORY_ACTIVE_VAULT env var → just that one.\n * 3. Neither set → all configured vaults (legacy behaviour).\n *\n * Indexing-status filter:\n * - Vaults whose audit log shows an unfinished index run are excluded\n * ONLY when the caller didn't ask for them explicitly. Idea: implicit\n * cross-vault search shouldn't surface chunks whose embeddings aren't\n * ready yet. Explicit single-vault requests pass through unchanged\n * (caller takes responsibility, gets a `note` field in the response).\n *\n * Returns the resolved targets plus the names of any skipped vaults, so the\n * caller can include a transparency note in the response.\n */\nexport function resolveVaultTargets(\n manager: VaultManager,\n vaultFilter: string[] | undefined,\n activeVault: string | undefined,\n): { targets: ReturnType<VaultManager[\"list\"]>; skipped: string[] } {\n // Explicit request → honour even if mid-index (caller's choice).\n if (vaultFilter) {\n return { targets: vaultFilter.map((n) => manager.require(n)), skipped: [] };\n }\n const candidates = activeVault ? [manager.require(activeVault)] : manager.list();\n const targets: typeof candidates = [];\n const skipped: string[] = [];\n for (const v of candidates) {\n if (v.db.audit.isIndexing()) {\n skipped.push(v.config.name);\n } else {\n targets.push(v);\n }\n }\n return { targets, skipped };\n}\n\nexport function encodeNoteId(vault: string, path: string): string {\n return `${vault}:${path}`;\n}\n\nexport function decodeNoteId(id: string): { vault: string; path: string } {\n const idx = id.indexOf(\":\");\n if (idx <= 0 || idx === id.length - 1) {\n throw new Error(`Invalid id: ${id}. Expected format <vault>:<vault-relative-path>.`);\n }\n return { vault: id.slice(0, idx), path: id.slice(idx + 1) };\n}\n\n/**\n * D-01 (plan 01-04 task 06): the v1 `obsidianUrl(vault, path)` helper was\n * deleted. Display URLs now flow through `SourceConnector.formatDisplayUrl`\n * — the obsidian-fs source mints the same deep-link URL string byte-for-byte\n * (the Obsidian `open` URL scheme; verified same `encodeURIComponent`-per-\n * segment encoding scheme; documented in\n * `.planning/phases/01-…/01-04-SUMMARY.md` §\"URL encoding parity\"). Future\n * adapters (notion-api etc.) can publish their own display URLs without\n * changing core code.\n *\n * The internal helper `displayUrl(registry, vault, path)` below is the\n * routing shim; it resolves the source and delegates.\n */\nexport function displayUrl(registry: AdapterRegistry, vaultName: string, notePath: string): string {\n const source = registry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`));\n const docId = formatDocId(\"obsidian-fs\", vaultName, notePath);\n // `formatDisplayUrl` is optional on the SourceConnector interface; for\n // future adapters that don't expose one, fall back to the raw doc_uri.\n return source.formatDisplayUrl?.(docId) ?? `obsidian-fs://${vaultName}/${notePath}`;\n}\n\nexport function truncateSnippet(text: string, max: number): string {\n const collapsed = text.replace(/\\s+/g, \" \").trim();\n if (collapsed.length <= max) return collapsed;\n return collapsed.slice(0, max - 1).trimEnd() + \"…\";\n}\n\n/**\n * Aggregate the top-N tags across all notes in a vault.\n *\n * Tags can live in two places in our schema: a top-level `tags` array in\n * frontmatter (Obsidian convention) or inline `#tag` hashtags in the body.\n * For v0.9.0 we read the frontmatter form only — it is what the user\n * curates explicitly and what other tools (Datacore queries, dataview)\n * already aggregate. Inline hashtags would need a separate pass through\n * note bodies and are deferred until users ask for it.\n *\n * Implementation uses SQLite's json_each over the stored frontmatter blob.\n * `frontmatter` is TEXT containing a JSON object; we look up the `tags` key\n * and iterate. Notes without frontmatter or without a tags array are\n * silently skipped.\n */\nexport function aggregateTopTags(\n db: BetterSqlite3.Database,\n limit: number,\n): Array<{ tag: string; count: number }> {\n // Real vaults accumulate frontmatter drift: `tags` may be an array,\n // a single string, a nested object, or missing entirely. SQLite's\n // json_each() throws on non-array/object inputs and aborts the whole\n // query — so we pre-filter to rows where `tags` is actually an array.\n // The CROSS JOIN with the JSON table then only sees well-formed inputs.\n const rows = db\n .prepare<[number], { tag: string; count: number }>(\n `\n SELECT je.value AS tag, COUNT(*) AS count\n FROM notes\n JOIN json_each(json_extract(notes.frontmatter, '$.tags')) AS je\n WHERE notes.frontmatter IS NOT NULL\n AND json_type(notes.frontmatter, '$.tags') = 'array'\n AND typeof(je.value) = 'text'\n GROUP BY je.value\n ORDER BY count DESC, tag ASC\n LIMIT ?\n `,\n )\n .all(limit);\n return rows;\n}\n\n/**\n * Aggregate the top-N most common frontmatter keys across all notes.\n * Surfaces the user's schema conventions to an agent on first connect.\n */\nexport function aggregateTopFrontmatterKeys(\n db: BetterSqlite3.Database,\n limit: number,\n): Array<{ key: string; count: number }> {\n // Same filter rationale as aggregateTopTags: a single note with a\n // non-object frontmatter blob (rare, but happens after manual edits)\n // would abort the whole aggregate.\n const rows = db\n .prepare<[number], { key: string; count: number }>(\n `\n SELECT je.key AS key, COUNT(*) AS count\n FROM notes\n JOIN json_each(notes.frontmatter) AS je\n WHERE notes.frontmatter IS NOT NULL\n AND json_type(notes.frontmatter) = 'object'\n GROUP BY je.key\n ORDER BY count DESC, key ASC\n LIMIT ?\n `,\n )\n .all(limit);\n return rows;\n}\n\nexport function safeParseFrontmatter(s: string): Record<string, unknown> | null {\n try {\n const parsed = JSON.parse(s) as unknown;\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function defaultBasename(path: string): string {\n const base = path.split(\"/\").pop() ?? path;\n return base.replace(/\\.md$/i, \"\");\n}\n\nexport function normalizeFolderHint(hint: string | undefined): string {\n if (!hint) return \"\";\n let h = hint.trim();\n // Strip leading slash; ensure trailing slash if non-empty.\n if (h.startsWith(\"/\")) h = h.slice(1);\n if (h.length > 0 && !h.endsWith(\"/\")) h = `${h}/`;\n return h;\n}\n","/**\n * Frontmatter query — minimal DSL against the JSON-stored frontmatter column.\n *\n * Uses SQLite's JSON1 extension (built into modern SQLite, no extra load needed).\n *\n * Predicate shapes:\n * { field: scalar } → field equals scalar\n * { field: { $in: [a, b, ...] } } → field is one of\n * { field: { $exists: true } } → field is present (not null/missing)\n * { field: { $exists: false } } → field absent or null\n * { field: { $contains: scalar } } → for arrays: array contains scalar\n *\n * Multiple top-level keys are AND-combined.\n *\n * Field path uses dot-notation: \"class\" or \"tags\" or \"links.0\".\n * Internally we map to JSON1 `json_extract(frontmatter, '$.path')`.\n */\n\nimport type { Vault } from \"../vault/index.js\";\nimport type { NoteRow } from \"../types.js\";\n\ntype Scalar = string | number | boolean | null;\n\nexport type Predicate = Scalar | { $in: Scalar[] } | { $exists: boolean } | { $contains: Scalar };\n\nexport interface QueryFrontmatterInput {\n where: Record<string, Predicate>;\n limit?: number;\n}\n\ninterface CompiledClause {\n sql: string;\n params: unknown[];\n}\n\nconst MAX_FIELD_DEPTH = 5;\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction buildJsonPath(field: string): string {\n // Reject anything that smells like SQL injection. We only allow\n // [A-Za-z0-9_.] plus simple array indexes.\n if (!/^[A-Za-z_][A-Za-z0-9_.]*$/.test(field)) {\n throw new Error(\n `Invalid frontmatter field: \"${field}\". Use dot.notation with alphanumeric segments.`,\n );\n }\n const parts = field.split(\".\");\n if (parts.length > MAX_FIELD_DEPTH) {\n throw new Error(`Field depth exceeds maximum (${MAX_FIELD_DEPTH}): ${field}`);\n }\n return \"$.\" + parts.map((p) => (/^\\d+$/.test(p) ? `[${p}]` : p)).join(\".\");\n}\n\nfunction compileClause(field: string, predicate: Predicate): CompiledClause {\n const jsonPath = buildJsonPath(field);\n const extract = `json_extract(frontmatter, '${jsonPath}')`;\n\n // Scalar equality\n if (predicate === null || typeof predicate !== \"object\") {\n if (predicate === null) {\n return { sql: `${extract} IS NULL`, params: [] };\n }\n return { sql: `${extract} = ?`, params: [predicate] };\n }\n\n if (isPlainObject(predicate)) {\n if (\"$in\" in predicate) {\n const values = predicate.$in;\n if (!Array.isArray(values) || values.length === 0) {\n // empty $in → never matches\n return { sql: \"0\", params: [] };\n }\n const placeholders = values.map(() => \"?\").join(\", \");\n return { sql: `${extract} IN (${placeholders})`, params: [...values] };\n }\n if (\"$exists\" in predicate) {\n return {\n sql: predicate.$exists ? `${extract} IS NOT NULL` : `${extract} IS NULL`,\n params: [],\n };\n }\n if (\"$contains\" in predicate) {\n // Array contains. Use json_each to scan.\n // Note: this requires the field to actually be a JSON array; if not\n // it just yields no rows.\n return {\n sql: `EXISTS (SELECT 1 FROM json_each(frontmatter, '${jsonPath}') WHERE value = ?)`,\n params: [predicate.$contains],\n };\n }\n }\n\n throw new Error(`Unsupported predicate for field \"${field}\": ${JSON.stringify(predicate)}`);\n}\n\nexport function queryFrontmatter(vault: Vault, input: QueryFrontmatterInput): NoteRow[] {\n const clauses: CompiledClause[] = [];\n for (const [field, predicate] of Object.entries(input.where)) {\n clauses.push(compileClause(field, predicate));\n }\n\n if (clauses.length === 0) {\n // No filters → return everything (capped). Caller probably wants `listAll`.\n return vault.db.notes.listAll(input.limit ?? 100);\n }\n\n const where = clauses.map((c) => `(${c.sql})`).join(\" AND \");\n const params = clauses.flatMap((c) => c.params);\n const limit = Math.min(Math.max(1, input.limit ?? 100), 1000);\n\n const stmt = vault.db.handle.prepare<unknown[], NoteRow>(\n `SELECT * FROM notes WHERE frontmatter IS NOT NULL AND ${where} ORDER BY mtime DESC LIMIT ${limit}`,\n );\n\n return stmt.all(...params);\n}\n","import { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\n\nexport interface ScanOptions {\n excludeGlobs?: string[];\n}\n\nconst DEFAULT_EXCLUDES = [\".obsidian/**\", \".trash/**\", \"node_modules/**\"];\n\n/**\n * Recursively walk `rootPath` and return absolute paths of all `.md` files.\n * Symlinks are NOT followed (loop-safe).\n *\n * Excludes are matched against the *relative* posix path of each file/dir.\n * A directory is pruned if its relative path matches any exclude glob.\n */\nexport async function scanVault(rootPath: string, options?: ScanOptions): Promise<string[]> {\n const root = path.resolve(rootPath);\n const excludes = options?.excludeGlobs ?? DEFAULT_EXCLUDES;\n const matchers = excludes.map(compileGlob);\n\n const results: string[] = [];\n await walk(root, root, matchers, results);\n results.sort();\n return results;\n}\n\n/**\n * Phase 6 / Plan 06-04 — Enumerate task-contract YAML files directly under\n * `_contracts/` (non-recursive; CONTRACT_PATH_REGEX = `^_contracts/[^/]+\\.yaml$`).\n *\n * Kept separate from `scanVault` because the indexer assumes `scanVault`\n * yields only `.md` files; broadening that would cascade through the\n * markdown parser. Contract YAML enumeration is a separate seam exposed\n * to `ObsidianFsSource.listDocuments` so the contract loader's boot scan\n * sees real YAML on disk.\n *\n * Returns absolute paths sorted lexicographically.\n */\nexport async function scanContractFiles(rootPath: string): Promise<string[]> {\n const root = path.resolve(rootPath);\n const contractsDir = path.join(root, \"_contracts\");\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(contractsDir, { withFileTypes: true });\n } catch {\n return [];\n }\n const results: string[] = [];\n for (const entry of entries) {\n // Pitfall F3 — non-recursive; `_contracts/memory/*.yaml` belongs to\n // the Phase 2 MemoryContract loader, not the task-contract loader.\n if (!entry.isFile()) continue;\n if (!entry.name.toLowerCase().endsWith(\".yaml\")) continue;\n results.push(path.join(contractsDir, entry.name));\n }\n results.sort();\n return results;\n}\n\nasync function walk(root: string, dir: string, matchers: RegExp[], out: string[]): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const abs = path.join(dir, entry.name);\n const rel = toPosix(path.relative(root, abs));\n if (rel.length === 0) continue;\n if (isExcluded(rel, matchers)) continue;\n\n if (entry.isSymbolicLink()) {\n // Skip symlinks entirely to avoid loops.\n continue;\n }\n if (entry.isDirectory()) {\n await walk(root, abs, matchers, out);\n } else if (entry.isFile() && abs.toLowerCase().endsWith(\".md\")) {\n out.push(abs);\n }\n }\n}\n\nfunction isExcluded(relPath: string, matchers: RegExp[]): boolean {\n for (const re of matchers) {\n if (re.test(relPath)) return true;\n }\n return false;\n}\n\nfunction toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n\n/**\n * Compile a minimal glob to a RegExp.\n * Supports:\n * - `*` → any chars except `/`\n * - `**` → any chars including `/`\n * - `?` → single char except `/`\n * - everything else literal\n *\n * The pattern matches the whole relative path. To also match descendants of\n * a matched directory (Obsidian convention), if the pattern ends with `/**`,\n * we also match the bare directory prefix.\n */\nexport function compileGlob(glob: string): RegExp {\n // Match descendants too when pattern ends with `/**`.\n const trimmed = glob.replace(/^\\.\\//, \"\");\n const altDir = trimmed.endsWith(\"/**\") ? trimmed.slice(0, -3) : null;\n\n const toRe = (g: string): string => {\n let re = \"\";\n for (let i = 0; i < g.length; i++) {\n const c = g[i];\n if (c === undefined) continue;\n if (c === \"*\") {\n if (g[i + 1] === \"*\") {\n re += \".*\";\n i++;\n } else {\n re += \"[^/]*\";\n }\n } else if (c === \"?\") {\n re += \"[^/]\";\n } else if (/[.+^${}()|[\\]\\\\]/.test(c)) {\n re += \"\\\\\" + c;\n } else {\n re += c;\n }\n }\n return re;\n };\n\n const parts = [toRe(trimmed)];\n if (altDir !== null) parts.push(toRe(altDir));\n return new RegExp(\"^(?:\" + parts.join(\"|\") + \")$\");\n}\n","import type { ParsedWikilink } from \"../../../types.js\";\n\n/**\n * Wikilink extraction.\n *\n * Recognised forms:\n * [[Target]]\n * [[Target|Alias]]\n * [[Target#Anchor]]\n * [[Target#Anchor|Alias]]\n * [[Folder/Sub/Target]]\n * [[Target.md]]\n *\n * Embeds (`![[...]]`) and block-references (`[[Target^block-id]]`) are not\n * specially handled — embeds are skipped (the leading `!` prevents the regex\n * match below since we anchor on a non-`!` preceding char), and block-refs\n * are parsed as a normal link whose target ends up containing the `^`-suffix\n * inside `rawTarget`. This is intentional: keep parsing robust, defer\n * semantics to a later layer.\n *\n * Code-block handling:\n * - Triple-backtick fenced blocks: contents are MASKED (replaced with\n * spaces, newlines preserved) so wikilinks inside them are ignored\n * but line numbers for following content remain correct.\n * - Inline code (single backticks) is NOT masked. We consider this\n * acceptable for now — wikilinks inside inline code are rare and the\n * downstream cost of a false positive is low.\n */\n\nconst WIKILINK_RE = /(^|[^!])\\[\\[([^\\[\\]\\n]+?)\\]\\]/g;\n\n/**\n * Regex variant without the `!`-prefix guard. Frontmatter values are scalars\n * (or arrays of scalars) — there's no embed-syntax to disambiguate against,\n * and the surrounding YAML quoting strips any leading char. So we want a\n * pure `[[...]]` matcher here.\n */\nconst FRONTMATTER_WIKILINK_RE = /\\[\\[([^\\[\\]\\n]+?)\\]\\]/g;\n\nexport function extractWikilinks(content: string): ParsedWikilink[] {\n const masked = maskFencedCodeBlocks(content);\n const results: ParsedWikilink[] = [];\n\n // Precompute newline offsets for fast line lookup.\n const lineStarts: number[] = [0];\n for (let i = 0; i < masked.length; i++) {\n if (masked[i] === \"\\n\") lineStarts.push(i + 1);\n }\n\n WIKILINK_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = WIKILINK_RE.exec(masked)) !== null) {\n const prefix = match[1] ?? \"\";\n const inner = match[2];\n if (inner === undefined) continue;\n // Position of the inner target in the masked string:\n const innerStart = match.index + prefix.length + 2; // skip prefix + \"[[\"\n\n const parsed = parseInner(inner);\n if (parsed === null) continue;\n\n const line = lineOf(lineStarts, innerStart);\n results.push({ ...parsed, line });\n }\n\n return results;\n}\n\ninterface InnerParsed {\n rawTarget: string;\n normalizedTarget: string;\n anchor: string | null;\n alias: string | null;\n}\n\nfunction parseInner(inner: string): InnerParsed | null {\n // Split alias first (everything after the first `|`).\n let target = inner;\n let alias: string | null = null;\n const pipeIdx = inner.indexOf(\"|\");\n if (pipeIdx >= 0) {\n target = inner.slice(0, pipeIdx);\n alias = inner.slice(pipeIdx + 1).trim();\n if (alias.length === 0) alias = null;\n }\n\n // Split anchor (first `#` in target).\n let rawTarget = target;\n let anchor: string | null = null;\n const hashIdx = target.indexOf(\"#\");\n if (hashIdx >= 0) {\n rawTarget = target.slice(0, hashIdx);\n anchor = target.slice(hashIdx + 1).trim();\n if (anchor.length === 0) anchor = null;\n }\n\n rawTarget = rawTarget.trim();\n if (rawTarget.length === 0) return null;\n\n const normalizedTarget = normalizeTarget(rawTarget);\n\n return { rawTarget, normalizedTarget, anchor, alias };\n}\n\nfunction normalizeTarget(raw: string): string {\n // Strip trailing .md (case-insensitive), normalize backslashes to forward.\n let t = raw.replace(/\\\\/g, \"/\");\n t = t.replace(/\\.md$/i, \"\");\n return t;\n}\n\nfunction lineOf(lineStarts: number[], offset: number): number {\n // Binary search the largest lineStart <= offset.\n let lo = 0;\n let hi = lineStarts.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n const v = lineStarts[mid];\n if (v !== undefined && v <= offset) lo = mid;\n else hi = mid - 1;\n }\n return lo + 1; // 1-based\n}\n\n/**\n * Replace contents inside triple-backtick fences with spaces, preserving\n * newlines and overall length. Handles fences like ```lang ... ```.\n */\nfunction maskFencedCodeBlocks(content: string): string {\n const chars = content.split(\"\");\n const fenceRe = /^([ \\t]*)(`{3,}|~{3,})([^\\n]*)$/gm;\n // We'll do a stateful scan line by line for correctness.\n const lines = content.split(\"\\n\");\n let inFence = false;\n let fenceMarker = \"\";\n let absOffset = 0;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n const trimmed = line.trimStart();\n if (!inFence) {\n const m = /^(`{3,}|~{3,})/.exec(trimmed);\n if (m !== null && m[1] !== undefined) {\n inFence = true;\n fenceMarker = m[1][0] ?? \"`\";\n // Do NOT mask the fence line itself — only contents inside.\n }\n } else {\n const m = /^(`{3,}|~{3,})\\s*$/.exec(trimmed);\n if (m !== null && m[1] !== undefined && m[1][0] === fenceMarker) {\n inFence = false;\n } else {\n // Mask this content line: replace every char with space.\n for (let j = 0; j < line.length; j++) {\n chars[absOffset + j] = \" \";\n }\n }\n }\n absOffset += line.length + 1; // +1 for the \"\\n\"\n }\n // suppress unused fenceRe (kept for clarity)\n void fenceRe;\n return chars.join(\"\");\n}\n\n/**\n * Extract wikilinks from a parsed YAML frontmatter object.\n *\n * Walks the frontmatter recursively and collects every `[[Target]]`,\n * `[[Target|Alias]]`, `[[Target#Anchor]]` occurrence found in any string\n * value at any depth. Supports the common Obsidian vault patterns:\n *\n * organisation: \"[[Holger Hoos]]\"\n * members: [\"[[Jörg Herbers]]\", \"[[Oliver Wrede]]\"]\n * affiliated_with:\n * - \"[[INFORM GmbH]]\"\n * - \"[[RWTH Aachen]]\"\n * Teilnehmer: \"[[OWR]], [[JHE]]\"\n *\n * Edge cases handled:\n * - Unquoted YAML wikilinks (`Klient: [[LAG]]`) parse as nested arrays of\n * strings via YAML's flow-sequence syntax — gray-matter delivers\n * `[[\"LAG\"]]`. We treat string-array elements as plain wikilink targets\n * (no anchor/alias parsing — those forms require the bracket syntax to\n * survive YAML, which only happens inside quotes).\n * - Skip the `aliases:` / `alias:` keys entirely — those are alias names,\n * not links to other notes. Body wikilinks may reference an alias as\n * target, but the alias entry itself is not a link.\n *\n * All emitted wikilinks carry `line: 0` to mark \"from frontmatter\" — the\n * frontmatter offset isn't reachable from gray-matter without re-parsing,\n * and consumers (graph queries, broken-link detection) only need source/\n * target/anchor/alias; line numbers are advisory.\n */\nexport function extractFrontmatterWikilinks(\n frontmatter: Record<string, unknown> | null,\n): ParsedWikilink[] {\n if (!frontmatter) return [];\n const results: ParsedWikilink[] = [];\n for (const [key, value] of Object.entries(frontmatter)) {\n if (key === \"aliases\" || key === \"alias\") continue;\n collectFromValue(value, results);\n }\n return results;\n}\n\nfunction collectFromValue(value: unknown, out: ParsedWikilink[]): void {\n if (typeof value === \"string\") {\n collectFromString(value, out);\n return;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n collectFromValue(item, out);\n }\n return;\n }\n if (value !== null && typeof value === \"object\") {\n for (const v of Object.values(value as Record<string, unknown>)) {\n collectFromValue(v, out);\n }\n }\n // numbers, booleans, null → no wikilink can be hiding here.\n}\n\nfunction collectFromString(s: string, out: ParsedWikilink[]): void {\n FRONTMATTER_WIKILINK_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = FRONTMATTER_WIKILINK_RE.exec(s)) !== null) {\n const inner = match[1];\n if (inner === undefined) continue;\n const parsed = parseInner(inner);\n if (parsed === null) continue;\n out.push({ ...parsed, line: 0 });\n }\n}\n","/**\n * Datacore / Dataview fenced-block handling for indexing (ADR-033).\n *\n * Obsidian notes embed dynamic views as fenced code blocks:\n * ```datacorejsx … ``` (JavaScript/JSX — Datacore)\n * ```datacore … ```\n * ```dataview … ``` (DQL — Dataview)\n * ```dataviewjs … ```\n *\n * These render a DIFFERENT structure (tables/lists) at view time INSIDE\n * Obsidian; the rendered output is never persisted to disk. A headless indexer\n * therefore only sees the block SOURCE — JavaScript / a query DSL — which is\n * noise for retrieval (see ADR-033 §Context).\n *\n * This module is the headless baseline of ADR-033: replace each dynamic-view\n * fence's BODY with a short neutral placeholder so the query source doesn't\n * pollute the index, while leaving surrounding prose + headings intact. The\n * Obsidian plugin (ADR-033 phase 3) later OVERRIDES this with the actually\n * rendered content when Datacore is active.\n *\n * Pure string transform — no fs / Obsidian / network. The transform is applied\n * to the INDEXED projection of a note's body only; the raw body (used for the\n * change-detection hash and for wikilink extraction) is untouched.\n */\n\n/** Fence languages whose body is dynamic-view source, not prose. */\nconst DYNAMIC_VIEW_LANGS = new Set([\n \"datacore\",\n \"datacorejsx\",\n \"datacorejs\",\n \"dataview\",\n \"dataviewjs\",\n]);\n\n/** Placeholder substituted for a stripped dynamic-view block body. */\nexport const DATACORE_PLACEHOLDER = \"[Datacore view]\";\n\nconst FENCE_OPEN_RE = /^(\\s*)(`{3,}|~{3,})\\s*([A-Za-z0-9_-]*)\\s*$/;\n\n/**\n * Replace the body of every Datacore/Dataview fenced block with a neutral\n * placeholder line, preserving everything else byte-for-byte. The fence\n * delimiters are dropped along with the body — the placeholder stands in for\n * the whole block so chunking/sectioning see a short, meaningful token instead\n * of code.\n *\n * Matching rules (CommonMark-ish, sufficient for Obsidian):\n * - An opening fence is ``` or ~~~ (3+) followed by an info string; the block\n * closes on the first line that is a fence of the SAME marker char and at\n * least the same length, with no info string.\n * - Only blocks whose info string (lowercased) is a known dynamic-view lang are\n * replaced. All other code blocks pass through unchanged.\n * - An unterminated dynamic-view fence (no closing fence to EOF) is replaced\n * through end-of-input — defensive against malformed notes.\n *\n * Returns `{ content, replaced }` where `replaced` is the number of blocks\n * substituted (0 ⇒ the input is returned unchanged, so callers can cheaply\n * detect \"no dynamic views\").\n */\nexport function stripDynamicViewBlocks(body: string): { content: string; replaced: number } {\n if (!body.includes(\"```\") && !body.includes(\"~~~\")) {\n return { content: body, replaced: 0 };\n }\n const lines = body.split(\"\\n\");\n const out: string[] = [];\n let replaced = 0;\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i]!;\n const open = FENCE_OPEN_RE.exec(line);\n if (open) {\n const indent = open[1] ?? \"\";\n const marker = open[2] ?? \"\";\n const lang = (open[3] ?? \"\").toLowerCase();\n const markerChar = marker[0]!;\n const isDynamic = DYNAMIC_VIEW_LANGS.has(lang);\n\n // Find the closing fence (same char, length >= opening, empty info).\n let j = i + 1;\n let closed = false;\n while (j < lines.length) {\n const close = FENCE_OPEN_RE.exec(lines[j]!);\n if (\n close &&\n (close[2] ?? \"\")[0] === markerChar &&\n (close[2] ?? \"\").length >= marker.length &&\n (close[3] ?? \"\") === \"\"\n ) {\n closed = true;\n break;\n }\n j++;\n }\n\n if (isDynamic) {\n // Replace the whole block (open..close) with one placeholder line,\n // preserving the opening indent so it reads naturally in context.\n out.push(`${indent}${DATACORE_PLACEHOLDER}`);\n replaced++;\n i = closed ? j + 1 : lines.length; // skip block (or to EOF if unterminated)\n } else {\n // Non-dynamic code block: emit verbatim, including delimiters.\n out.push(line);\n if (closed) {\n for (let k = i + 1; k <= j; k++) out.push(lines[k]!);\n i = j + 1;\n } else {\n for (let k = i + 1; k < lines.length; k++) out.push(lines[k]!);\n i = lines.length;\n }\n }\n } else {\n out.push(line);\n i++;\n }\n }\n\n if (replaced === 0) return { content: body, replaced: 0 };\n return { content: out.join(\"\\n\"), replaced };\n}\n","import { createHash } from \"node:crypto\";\n\n/** SHA-256 hex digest of input string (utf-8). */\nexport function sha256(input: string): string {\n return createHash(\"sha256\").update(input, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Canonical JSON serialization with stable, alphabetically-sorted object keys.\n *\n * Why: JavaScript preserves object-property insertion order, so\n * `JSON.stringify({a:1,b:2})` and `JSON.stringify({b:2,a:1})` produce different\n * strings even though the objects are semantically identical. When this output\n * is fed into the note `hash`, the same note re-parsed with frontmatter keys in\n * a different order would yield a different hash — causing spurious optimistic-\n * concurrency conflicts in `write_note` / `update_frontmatter`.\n *\n * Rules:\n * - Object keys are sorted lexicographically.\n * - Arrays preserve their insertion order (order is semantically meaningful).\n * - Primitives (string/number/boolean) use standard JSON.stringify.\n * - `null` and `undefined` serialize to \"null\".\n * - Recursion through nested objects and arrays.\n *\n * Migration note: existing rows in the SQLite index were hashed with the\n * non-canonical `JSON.stringify`. We intentionally do NOT migrate them — the\n * next time each note is re-indexed (any mtime change or a full re-scan),\n * its hash is recomputed canonically and self-heals.\n */\nexport function canonicalJsonStringify(value: unknown): string {\n if (value === null || value === undefined) return \"null\";\n if (Array.isArray(value)) {\n return \"[\" + value.map((v) => canonicalJsonStringify(v)).join(\",\") + \"]\";\n }\n if (typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n const parts = keys.map((k) => JSON.stringify(k) + \":\" + canonicalJsonStringify(obj[k]));\n return \"{\" + parts.join(\",\") + \"}\";\n }\n // Primitives (string, number, boolean). NaN/Infinity → \"null\" via JSON.stringify.\n const s = JSON.stringify(value);\n return s === undefined ? \"null\" : s;\n}\n\n/**\n * Canonical content-hash for a note: sha256(content + canonicalJson(frontmatter ?? {})).\n *\n * All call sites (reader/parser, write, frontmatter/update) MUST go\n * through this function to guarantee identical hashes across the codebase.\n */\nexport function computeNoteHash(\n content: string,\n frontmatter: Record<string, unknown> | null | undefined,\n): string {\n return sha256(content + canonicalJsonStringify(frontmatter ?? {}));\n}\n\n/**\n * Body-only hash: sha256(content) — independent of frontmatter.\n *\n * Used by the indexer to short-circuit chunk + embed work when the body\n * is unchanged but frontmatter differs. See migration 006 for rationale.\n *\n * Note: this is intentionally NOT a substring of computeNoteHash's input —\n * we want it to remain stable when frontmatter changes, which the\n * combined hash explicitly does not.\n */\nexport function computeBodyHash(content: string): string {\n return sha256(content);\n}\n","import { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport matter from \"gray-matter\";\nimport type { ParsedNote } from \"../../../types.js\";\nimport { extractWikilinks, extractFrontmatterWikilinks } from \"./wikilinks.js\";\nimport { stripDynamicViewBlocks } from \"../../../reader/datacore.js\";\nimport { computeNoteHash, computeBodyHash } from \"./hash.js\";\n\n/**\n * Parse a single markdown file into a ParsedNote.\n *\n * `relativePath` is always posix (forward slashes), relative to `vaultRoot`.\n */\nexport async function parseNote(absolutePath: string, vaultRoot: string): Promise<ParsedNote> {\n const raw = await fs.readFile(absolutePath, \"utf-8\");\n const stat = await fs.stat(absolutePath);\n\n const parsed = matter(raw);\n const content = parsed.content;\n const fmData = parsed.data as Record<string, unknown> | undefined;\n const frontmatter: Record<string, unknown> | null =\n fmData !== undefined && Object.keys(fmData).length > 0 ? fmData : null;\n\n const title = extractTitle(content) ?? path.basename(absolutePath, \".md\");\n const hash = computeNoteHash(content, frontmatter);\n const bodyHash = computeBodyHash(content);\n const mtime = Math.floor(stat.mtimeMs);\n // Body wikilinks first (richer data: line, alias), then frontmatter.\n // We deduplicate the frontmatter additions against the body set on the\n // (normalizedTarget, anchor) key — otherwise a member-list in frontmatter\n // that's also referenced in body would produce a \"phantom\" extra backlink.\n //\n // (Note: SQLite's UNIQUE(source, target, anchor) constraint does NOT dedup\n // here, because NULL anchors are never equal to each other under SQL\n // semantics. App-level dedup is the only reliable path.)\n //\n // Within body and within frontmatter we keep duplicates: body duplicates\n // are pre-existing behaviour (multiple mentions of the same target across\n // different lines were always inserted as separate rows), and frontmatter\n // duplicates are vanishingly rare in practice. Limiting the dedup scope\n // keeps this change minimal and behaviour-preserving for body wikilinks.\n const bodyLinks = extractWikilinks(content);\n const frontmatterLinks = extractFrontmatterWikilinks(frontmatter);\n const wikilinks =\n frontmatterLinks.length === 0\n ? bodyLinks\n : mergeFrontmatterIntoBody(bodyLinks, frontmatterLinks);\n const wordCount = countWords(content);\n const relativePath = toPosix(path.relative(path.resolve(vaultRoot), path.resolve(absolutePath)));\n\n // ADR-033: project a clean body for indexing — Datacore/Dataview dynamic-view\n // fence bodies become a neutral placeholder so query source doesn't pollute\n // the index. `content` (hash + wikilinks) is untouched. No-op when the note\n // has no dynamic-view blocks (returns `content` unchanged).\n const indexedContent = stripDynamicViewBlocks(content).content;\n\n return {\n relativePath,\n content,\n indexedContent,\n frontmatter,\n title,\n hash,\n bodyHash,\n mtime,\n wikilinks,\n wordCount,\n };\n}\n\n/**\n * Combine body and frontmatter wikilinks, dropping any frontmatter entry whose\n * `(normalizedTarget, anchor)` already appears in the body set. Both inputs\n * are preserved otherwise in their original order.\n */\nfunction mergeFrontmatterIntoBody(\n body: ReturnType<typeof extractWikilinks>,\n fm: ReturnType<typeof extractFrontmatterWikilinks>,\n): ReturnType<typeof extractWikilinks> {\n const seen = new Set<string>();\n for (const w of body) {\n seen.add(`${w.normalizedTarget}\u0000${w.anchor ?? \"\"}`);\n }\n const result = body.slice();\n for (const w of fm) {\n const key = `${w.normalizedTarget}\u0000${w.anchor ?? \"\"}`;\n if (seen.has(key)) continue;\n seen.add(key);\n result.push(w);\n }\n return result;\n}\n\n/** Find the first H1 (`# Title`) at the start of a line. */\nfunction extractTitle(content: string): string | null {\n const lines = content.split(\"\\n\");\n for (const line of lines) {\n const m = /^#\\s+(.+?)\\s*$/.exec(line);\n if (m !== null && m[1] !== undefined) return m[1].trim();\n // Stop scanning into the body too far — but Obsidian title H1 can be\n // anywhere near the top. We keep scanning the whole content; cheap.\n }\n return null;\n}\n\nfunction countWords(content: string): number {\n if (content.length === 0) return 0;\n return content.split(/\\s+/).filter((s) => s.length > 0).length;\n}\n\nfunction toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n","/**\n * ObsidianFsSource — the v2 SourceConnector implementation for\n * filesystem-backed Obsidian vaults.\n *\n * Wraps the relocated scanner / parser / hash / wikilinks modules behind\n * the ADR-002 §SourceConnector contract. This file is the SOLE entry\n * point through which Layer-0 retrieval obtains content for an\n * obsidian-fs vault; the registry hands callers an `ObsidianFsSource`\n * keyed by the `obsidian-fs://<vault-name>` handle.\n *\n * # Invariant carve-outs (ADR-002)\n *\n * - I-2 (raw `node:fs` / `node:path`): ALLOWED inside this directory.\n * `readDocument`, `hash`, and `exists` use `fs.readFile` / `fs.stat`\n * directly; `formatDisplayUrl` uses `path.basename` style helpers.\n * - I-3 (raw file-path manipulation): ALLOWED — the adapter owns the\n * conversion between DocId and absolute filesystem path.\n * - I-4 (YAML-frontmatter parsing via `gray-matter`): ALLOWED — the\n * relocated `./parser.ts` already imports it, and that import is now\n * confined to this directory (modulo the existing write-side leaks in\n * `src/write/write.ts` and `src/frontmatter/update.ts`, which plan\n * 01-04 absorbs into the delivery adapter).\n *\n * # Capabilities (Invariant I-7 — honest publication)\n *\n * bodyShape: \"flat-text\" — single-paragraph fallback for v1 compat\n * properties: \"untyped\" — YAML frontmatter is untyped per ADR-003\n * linkTypes: [\"wikilink\"] — sole edge type emitted\n * identityStable: false — paths rename; DocIds are not durable\n * permissions: false — fs ACLs not modeled\n * contentHashStable: true — sha256(content + canonicalJson(fm))\n * refHashKind: \"content\" — DocumentRef.hash === Document.hash\n * watch: \"push\" — chokidar lands in plan 01-05\n *\n * # Phase-3 follow-ups\n *\n * - `blocks` is a single-paragraph stub; richer block decomposition is\n * Phase 3 work (ADR-003 BlockNode union).\n * - The v1 hash semantics (`computeNoteHash(body, frontmatter)`) are\n * preserved for Phase 1 backwards-compat. ADR-003 H-1..H-6 may revise\n * the canonical hash later.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Document, DocId, SourceHandle, VaultConfig, WikilinkRef } from \"../../../types.js\";\nimport type { DocumentRef, ListOptions, SourceCapabilities, SourceConnector } from \"../types.js\";\nimport { formatDocId, parseSourceHandle } from \"../../registry.js\";\nimport { scanVault, scanContractFiles } from \"./scanner.js\";\nimport { parseNote } from \"./parser.js\";\nimport { computeBodyHash } from \"./hash.js\";\nimport { errorMessage } from \"../../../errors/format.js\";\n\n/**\n * Phase 6 / Plan 06-04 — task-contract YAML path matcher. Mirrors\n * `CONTRACT_PATH_REGEX` in `src/contracts/types.ts` (Pitfall F3\n * non-recursion). YAML files under `_contracts/` are enumerated +\n * read through the SourceConnector seam so the contract loader's\n * boot scan + ChangeFeed paths see real on-disk YAML.\n */\nconst CONTRACT_PATH_RE = /^_contracts\\/[^/]+\\.yaml$/;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ObsidianFsSource\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst SCHEME = \"obsidian-fs\";\n\nexport class ObsidianFsSource implements SourceConnector {\n readonly handle: SourceHandle;\n\n readonly capabilities: SourceCapabilities = {\n bodyShape: \"flat-text\",\n properties: \"untyped\",\n linkTypes: [\"wikilink\"] as const,\n identityStable: false,\n permissions: false,\n contentHashStable: true,\n refHashKind: \"content\",\n watch: \"push\",\n };\n\n constructor(private readonly vault: VaultConfig) {\n this.handle = parseSourceHandle(`${SCHEME}://${vault.name}`);\n }\n\n // ── enumeration ────────────────────────────────────────────────────────────\n\n async *listDocuments(opts?: ListOptions): AsyncIterable<DocumentRef> {\n const excludeOverlay = opts?.excludeGlobs;\n const mdFiles = await scanVault(this.vault.path, {\n ...(excludeOverlay ? { excludeGlobs: excludeOverlay } : {}),\n });\n // Phase 6 / Plan 06-04 — yield task-contract YAML files alongside .md\n // notes. The contract loader's boot scan + ChangeFeed paths filter by\n // CONTRACT_PATH_REGEX inside `src/contracts/loader.ts`, so unrelated\n // consumers (indexer, watcher, search) that filter on `.md` extension\n // are unaffected. The indexer uses scanVault() directly (not this\n // method) so its .md-only contract is preserved.\n const yamlFiles = await scanContractFiles(this.vault.path);\n const files = mdFiles.concat(yamlFiles);\n files.sort();\n const since = opts?.since;\n const limit = opts?.limit;\n let yielded = 0;\n for (const abs of files) {\n if (limit !== undefined && yielded >= limit) break;\n const rel = this.toPosix(path.relative(path.resolve(this.vault.path), abs));\n const stat = await fs.stat(abs);\n const mtime = Math.floor(stat.mtimeMs);\n if (since !== undefined && mtime < since) continue;\n // Cheap content hash for the ref — matches refHashKind: \"content\"\n const body = await fs.readFile(abs, \"utf-8\");\n const hash = computeBodyHash(body);\n // A pathological filename (e.g. an embedded newline from a botched\n // Obsidian title) makes pathToDocId → formatDocId throw. Skipping the\n // one bad file keeps a single malformed note from aborting the whole\n // listDocuments() iteration — which previously took down bootScan /\n // the contract registry for the entire vault.\n let id: DocId;\n try {\n id = this.pathToDocId(rel);\n } catch (err) {\n console.error(\n `[obsidian-fs:${this.vault.name}] skipping un-addressable file ` +\n `${JSON.stringify(rel)}: ${errorMessage(err)}`,\n );\n continue;\n }\n yield { id, mtime, hash };\n yielded++;\n }\n }\n\n // ── single-doc reads ───────────────────────────────────────────────────────\n\n async readDocument(id: DocId): Promise<Document> {\n const rel = this.docIdToPath(id);\n const abs = this.absPath(rel);\n\n // Phase 6 / Plan 06-04 — task-contract YAML branch. parseNote()\n // assumes markdown + frontmatter; it would mis-parse a YAML\n // contract file. Return the raw text as a single paragraph block\n // with no properties; the contract loader (`src/contracts/loader.ts`)\n // is the only consumer and parses the body via `yaml@2.9`.\n if (CONTRACT_PATH_RE.test(rel)) {\n const body = await fs.readFile(abs, \"utf-8\");\n const stat = await fs.stat(abs);\n const hash = computeBodyHash(body);\n return {\n id,\n source: this.handle,\n title: rel,\n blocks: [{ kind: \"paragraph\", text: body }],\n properties: {},\n links: [],\n mtime: Math.floor(stat.mtimeMs),\n hash,\n display_url: this.formatDisplayUrl(id),\n };\n }\n\n const parsed = await parseNote(abs, this.vault.path);\n\n // D-05: surface wikilinks as Document.properties.wikilinks: WikilinkRef[]\n const wikilinks: WikilinkRef[] = parsed.wikilinks.map((w) => {\n const ref: WikilinkRef = { target: w.normalizedTarget };\n if (w.alias !== null) ref.alias = w.alias;\n if (w.anchor !== null) ref.section = w.anchor;\n return ref;\n });\n\n const properties: Record<string, unknown> = {\n ...(parsed.frontmatter ?? {}),\n wikilinks,\n };\n\n return {\n id,\n source: this.handle,\n title: parsed.title,\n blocks: [{ kind: \"paragraph\", text: parsed.content }],\n properties,\n links: [],\n mtime: parsed.mtime,\n hash: parsed.hash,\n display_url: this.formatDisplayUrl(id),\n };\n }\n\n async hash(id: DocId): Promise<string> {\n const rel = this.docIdToPath(id);\n const abs = this.absPath(rel);\n const body = await fs.readFile(abs, \"utf-8\");\n return computeBodyHash(body);\n }\n\n async exists(id: DocId): Promise<boolean> {\n try {\n const rel = this.docIdToPath(id);\n const abs = this.absPath(rel);\n await fs.stat(abs);\n return true;\n } catch {\n return false;\n }\n }\n\n // ── display ────────────────────────────────────────────────────────────────\n\n formatDisplayUrl(id: DocId): string {\n const rel = this.docIdToPath(id);\n const vault = encodeURIComponent(this.vault.name);\n const file = encodeURIComponent(rel);\n return `obsidian://open?vault=${vault}&file=${file}`;\n }\n\n // ── helpers ────────────────────────────────────────────────────────────────\n\n /**\n * Parse the URI authority + resource off a DocId. Asserts the authority\n * matches `this.vault.name` — prevents one vault's adapter from reading\n * another vault's file via a forged DocId (T-01-03-02 in the plan's\n * threat model).\n */\n private docIdToPath(id: DocId): string {\n const prefix = `${SCHEME}://`;\n if (!id.startsWith(prefix)) {\n throw new Error(`DocId scheme mismatch: expected \"${SCHEME}://…\", got ${JSON.stringify(id)}`);\n }\n const rest = id.slice(prefix.length);\n const slash = rest.indexOf(\"/\");\n if (slash < 0) {\n throw new Error(`Invalid DocId shape: missing resource path in ${JSON.stringify(id)}`);\n }\n const authority = rest.slice(0, slash);\n const resource = rest.slice(slash + 1);\n if (authority !== this.vault.name) {\n throw new Error(\n `DocId vault mismatch: id authority \"${authority}\" does not match ` +\n `this adapter's configured vault \"${this.vault.name}\"`,\n );\n }\n if (resource.length === 0) {\n throw new Error(`Invalid DocId: empty resource path in ${JSON.stringify(id)}`);\n }\n return resource;\n }\n\n private pathToDocId(rel: string): DocId {\n const posix = this.toPosix(rel);\n return formatDocId(SCHEME, this.vault.name, posix);\n }\n\n private absPath(rel: string): string {\n return path.resolve(this.vault.path, rel);\n }\n\n private toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n }\n}\n","/**\n * Token-count approximation for chunk sizing.\n *\n * This is intentionally NOT a real BPE tokenizer. We use a simple length/4\n * heuristic that is \"good enough\" to keep chunks in a ~400-token band, which\n * is all the chunker actually needs.\n *\n * Why this is fine:\n * - Chunk sizing is a band, not an exact budget. Embedding models tell us at\n * call time if a chunk was too long, and we re-chunk then.\n * - A real tokenizer (tiktoken, transformers.js) would couple us to a model\n * family. We embed via Ollama with model-agnostic input, so any model-\n * specific count would be wrong for some models anyway.\n *\n * If we ever need accuracy, swap this for a real tokenizer here — the rest of\n * the chunker only depends on `countTokens`.\n */\nexport function countTokens(text: string): number {\n if (text.length === 0) return 0;\n return Math.ceil(text.length / 4);\n}\n","/**\n * Heading-aware Markdown chunker.\n *\n * Strategy (in order of preference for splits):\n * 1. Heading boundaries (level 1–3) — preferred.\n * 2. Paragraph boundaries (blank lines) — used when a heading section is\n * itself too long.\n * 3. Sentence boundaries (`.!?` followed by whitespace + uppercase) —\n * naive, no abbreviation handling in MVP.\n * 4. Hard cut at `maxTokens * 4` characters — last resort.\n *\n * Overlap is applied as a character window taken from the tail of the previous\n * chunk; if a sentence boundary is found within the overlap window, we start\n * at that boundary for cleaner reads.\n */\n\nimport type { Chunk, ChunkOptions } from \"../types.js\";\nimport { countTokens } from \"./tokens.js\";\nimport { extractHeadings, headingPathAtOffset } from \"./headings.js\";\nimport type { HeadingRef } from \"./headings.js\";\n\nconst DEFAULT_MAX_TOKENS = 400;\nconst DEFAULT_OVERLAP_TOKENS = 50;\n/**\n * Minimum non-whitespace characters required for a chunk to be kept.\n * Notes that begin with a blank line before the first heading produce a\n * leading whitespace-only span; those would otherwise become a chunk_idx=0\n * \"\\n\" chunk and pollute search top-k because their embedding is close to\n * the embedding of every other near-empty text (cosine ≈ 1.0). Trimming\n * here is the source of truth — search/rerank don't need a follow-up filter.\n */\nconst MIN_CHUNK_TRIM_CHARS = 3;\n\ninterface Span {\n start: number;\n end: number; // exclusive\n}\n\nexport function chunkNote(content: string, options?: ChunkOptions): Chunk[] {\n if (content.length === 0) return [];\n\n const maxTokens = options?.maxTokens ?? DEFAULT_MAX_TOKENS;\n const overlapTokens = options?.overlapTokens ?? DEFAULT_OVERLAP_TOKENS;\n const maxChars = maxTokens * 4;\n const overlapChars = overlapTokens * 4;\n\n const headings = extractHeadings(content);\n\n // Fast path: whole note fits.\n if (countTokens(content) <= maxTokens) {\n if (content.trim().length < MIN_CHUNK_TRIM_CHARS) return [];\n return [\n {\n idx: 0,\n text: content,\n headingPath: headingPathAtOffset(headings, 0),\n startOffset: 0,\n endOffset: content.length,\n tokenCount: countTokens(content),\n },\n ];\n }\n\n // 1. Build initial spans by splitting at level 1–3 headings.\n const headingSpans = splitAtHeadings(content, headings, maxChars);\n\n // 2. For each span still too large, recursively split: paragraphs → sentences → hard cut.\n const finalSpans: Span[] = [];\n for (const span of headingSpans) {\n if (span.end - span.start <= maxChars) {\n finalSpans.push(span);\n } else {\n finalSpans.push(...splitParagraphs(content, span, maxChars));\n }\n }\n\n // 3. Apply overlap and build Chunk objects.\n // headingPath is computed at the *primary* span start (pre-overlap) so the\n // heading describes the chunk's own content, not borrowed overlap text.\n const chunks: Chunk[] = [];\n for (let i = 0; i < finalSpans.length; i++) {\n const span = finalSpans[i];\n if (!span) continue;\n const primaryStart = span.start;\n let start = span.start;\n const end = span.end;\n\n if (i > 0 && overlapChars > 0) {\n const overlapStart = Math.max(0, start - overlapChars);\n // Try to align to a sentence boundary inside the overlap window.\n const window = content.slice(overlapStart, start);\n const sentenceIdx = findLastSentenceBoundary(window);\n start = sentenceIdx >= 0 ? overlapStart + sentenceIdx : overlapStart;\n }\n\n const text = content.slice(start, end);\n // Drop whitespace-only and tiny chunks: they produce near-identical\n // embeddings and pollute search top-k (see MIN_CHUNK_TRIM_CHARS doc).\n if (text.trim().length < MIN_CHUNK_TRIM_CHARS) continue;\n\n chunks.push({\n idx: chunks.length,\n text,\n headingPath: headingPathAtOffset(headings, primaryStart),\n startOffset: start,\n endOffset: end,\n tokenCount: countTokens(text),\n });\n }\n\n return chunks;\n}\n\n/**\n * Split content into spans bounded by level 1–3 ATX headings.\n * Each span starts at a heading (or the document start) and runs until just\n * before the next eligible heading.\n *\n * Headings deeper than level 3 do not break sections (they live inside).\n */\nfunction splitAtHeadings(content: string, headings: HeadingRef[], _maxChars: number): Span[] {\n const boundaries: number[] = [0];\n for (const h of headings) {\n if (h.level <= 3 && h.startOffset > 0) {\n boundaries.push(h.startOffset);\n }\n }\n boundaries.push(content.length);\n\n // Deduplicate / sort defensively.\n const uniq = [...new Set(boundaries)].sort((a, b) => a - b);\n\n const spans: Span[] = [];\n for (let i = 0; i < uniq.length - 1; i++) {\n const start = uniq[i];\n const end = uniq[i + 1];\n if (start === undefined || end === undefined) continue;\n if (end > start) spans.push({ start, end });\n }\n return spans;\n}\n\n/**\n * Split a single span by paragraph boundaries (blank lines / `\\n\\n+`), packing\n * paragraphs greedily up to `maxChars`. Falls back to sentence-splitting for\n * any paragraph that is itself too long.\n */\nfunction splitParagraphs(content: string, span: Span, maxChars: number): Span[] {\n const text = content.slice(span.start, span.end);\n const paragraphs: Span[] = [];\n const re = /\\n{2,}/g;\n let cursor = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n const paraEnd = m.index;\n if (paraEnd > cursor) {\n paragraphs.push({ start: span.start + cursor, end: span.start + paraEnd });\n }\n cursor = m.index + m[0].length;\n }\n if (cursor < text.length) {\n paragraphs.push({ start: span.start + cursor, end: span.end });\n }\n if (paragraphs.length === 0) {\n paragraphs.push({ start: span.start, end: span.end });\n }\n\n const out: Span[] = [];\n let current: Span | null = null;\n\n const flush = () => {\n if (!current) return;\n if (current.end - current.start <= maxChars) {\n out.push(current);\n } else {\n out.push(...splitSentences(content, current, maxChars));\n }\n current = null;\n };\n\n for (const p of paragraphs) {\n if (!current) {\n current = { start: p.start, end: p.end };\n continue;\n }\n if (p.end - current.start <= maxChars) {\n current = { start: current.start, end: p.end };\n } else {\n flush();\n current = { start: p.start, end: p.end };\n }\n }\n flush();\n\n return out;\n}\n\n/**\n * Sentence-aware split. Detects `.!?` followed by whitespace + an uppercase\n * letter as a sentence boundary. No abbreviation handling in MVP.\n *\n * Packs sentences greedily up to `maxChars`. Falls back to hard cut for any\n * sentence that is itself too long.\n */\nfunction splitSentences(content: string, span: Span, maxChars: number): Span[] {\n const text = content.slice(span.start, span.end);\n const boundaries: number[] = [];\n const re = /[.!?]\\s+(?=[A-ZÄÖÜ])/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n boundaries.push(m.index + m[0].length);\n }\n\n const sentences: Span[] = [];\n let cursor = 0;\n for (const b of boundaries) {\n if (b > cursor) {\n sentences.push({ start: span.start + cursor, end: span.start + b });\n cursor = b;\n }\n }\n if (cursor < text.length) {\n sentences.push({ start: span.start + cursor, end: span.end });\n }\n if (sentences.length === 0) {\n sentences.push({ start: span.start, end: span.end });\n }\n\n const out: Span[] = [];\n let current: Span | null = null;\n\n const flush = () => {\n if (!current) return;\n if (current.end - current.start <= maxChars) {\n out.push(current);\n } else {\n out.push(...hardCut(current, maxChars));\n }\n current = null;\n };\n\n for (const s of sentences) {\n if (!current) {\n current = { start: s.start, end: s.end };\n continue;\n }\n if (s.end - current.start <= maxChars) {\n current = { start: current.start, end: s.end };\n } else {\n flush();\n current = { start: s.start, end: s.end };\n }\n }\n flush();\n\n return out;\n}\n\n/**\n * Last-resort: cut into fixed-size character windows.\n */\nfunction hardCut(span: Span, maxChars: number): Span[] {\n const out: Span[] = [];\n for (let s = span.start; s < span.end; s += maxChars) {\n out.push({ start: s, end: Math.min(span.end, s + maxChars) });\n }\n return out;\n}\n\n/**\n * Find the offset within `window` just after the last sentence boundary,\n * or -1 if none found.\n */\nfunction findLastSentenceBoundary(window: string): number {\n const re = /[.!?]\\s+(?=[A-ZÄÖÜ])/g;\n let last = -1;\n let m: RegExpExecArray | null;\n while ((m = re.exec(window)) !== null) {\n last = m.index + m[0].length;\n }\n return last;\n}\n","/**\n * Chunker module — heading-aware Markdown chunking for embedding.\n *\n * Public surface:\n * - `chunkNote(content, options?)` — split a note body into Chunk[]\n * - `countTokens(text)` — approximate token counter (length/4 heuristic)\n * - `extractHeadings(content)` — ATX heading extraction (ignores code fences)\n */\n\nexport { chunkNote } from \"./chunker.js\";\nexport { countTokens } from \"./tokens.js\";\nexport { extractHeadings, headingPathAtOffset } from \"./headings.js\";\nexport type { HeadingRef } from \"./headings.js\";\n","/**\n * WikilinkResolver — per-index-run resolver with prepared-statement reuse\n * and a target-path → noteId cache.\n *\n * Why this exists:\n * resolveWikilinkTarget() is called once per wikilink during indexVault.\n * Each call did up to three SQL operations and prepared the filename-match\n * statement on the fly. On large vaults (5k notes / 20k links) that's\n * measurable. This class:\n * - prepares the filename-match statement once,\n * - memoises results by normalised target path inside a single run.\n *\n * Cache scope:\n * One instance per indexVault run. Notes inserted during the run can\n * change resolution results (transient broken links), which is why the\n * second pass uses a fresh resolver — see indexer.ts. Do NOT reuse an\n * instance across runs.\n *\n * Key choice:\n * Obsidian's heuristic in this codebase ignores the source note's folder,\n * so the cache key is just the normalised target path. If same-folder\n * priority is added later, switch to `${sourcePath}::${targetPath}`.\n */\n\nimport type BetterSqlite3 from \"better-sqlite3\";\nimport type { Vault } from \"../vault/index.js\";\n\nexport interface ResolveHit {\n id: number;\n path: string;\n}\n\nexport class WikilinkResolver {\n private readonly vault: Vault;\n private readonly filenameStmt: BetterSqlite3.Statement<\n [string, string],\n { id: number; path: string }\n >;\n private readonly cache = new Map<string, ResolveHit | null>();\n\n constructor(vault: Vault) {\n this.vault = vault;\n this.filenameStmt = vault.db.handle.prepare(\n `SELECT id, path FROM notes\n WHERE path = ?\n OR path LIKE ?\n ORDER BY length(path) ASC\n LIMIT 1`,\n );\n }\n\n /**\n * Resolve a wikilink target the way Obsidian does, in priority order:\n * 1) exact relative path match (with or without .md)\n * 2) filename-only match anywhere in the vault — shortest path wins\n * 3) alias match — looks up note_aliases (case-insensitive)\n *\n * Returns null if no candidate exists.\n */\n resolve(normalizedTarget: string): ResolveHit | null {\n const cached = this.cache.get(normalizedTarget);\n if (cached !== undefined) return cached;\n\n const hit = this.resolveUncached(normalizedTarget);\n this.cache.set(normalizedTarget, hit);\n return hit;\n }\n\n private resolveUncached(normalizedTarget: string): ResolveHit | null {\n // 1. Exact relative path (with .md, then without)\n const exact =\n this.vault.db.notes.getByPath(`${normalizedTarget}.md`) ??\n this.vault.db.notes.getByPath(normalizedTarget);\n if (exact) return { id: exact.id, path: exact.path };\n\n // 2 + 3 only apply to slash-less targets (filename-only references).\n if (!normalizedTarget.includes(\"/\")) {\n const filename = `${normalizedTarget}.md`;\n const suffix = `%/${filename}`;\n const hit = this.filenameStmt.get(filename, suffix);\n if (hit) return hit;\n\n const aliasHit = this.vault.db.aliases.resolve(normalizedTarget);\n if (aliasHit) {\n return { id: aliasHit.note_id, path: aliasHit.path };\n }\n }\n\n return null;\n }\n\n /** Test/diagnostics: cache size after a run. */\n get cacheSize(): number {\n return this.cache.size;\n }\n}\n","/**\n * Edge extractors — produce typed `EdgeInput[]` rows for the `edges`\n * table from a single `ParsedNote`.\n *\n * Phase 4 / 04-02 / GRA-04. Implements the contracts:\n * - D-02 — `extractAllEdges` unified entry: wikilink + mention +\n * frontmatter-ref + hyperlink in one parse pass.\n * - D-03 — mention: casefold + min-length 4 + word-boundary, scanned\n * only on paragraph blocks (headings + fenced code + inline\n * code + bracketed wikilink spans are pre-masked away).\n * Candidate set built once per indexer run from `note_aliases`.\n * - Pitfall 6 — frontmatter-ref two-rule heuristic:\n * (a) ANY property whose value is `[[...]]` syntax →\n * resolve via `WikilinkResolver`; `rel` = property name.\n * (b) Allowlisted property names (closed set of 8) whose\n * value is a bare string → resolve against\n * `note_aliases` only.\n *\n * Source-neutral by construction: zero imports of `fs`, `path`,\n * `chokidar`, or `gray-matter`. CI `scripts/lint-adapters.sh` verifies\n * this on every push (rule I-2). All inputs flow through the\n * already-parsed `ParsedNote` shape produced by the obsidian-fs\n * adapter (Phase 1 seam).\n *\n * RESEARCH.md §\"Code Examples\" lines 580–656 spell out the algorithms;\n * `<interfaces>` in `04-02-edge-extractors-PLAN.md` pins the exact\n * function signatures + the `FRONTMATTER_REF_ALLOWLIST` constant.\n *\n * Idempotency: re-extracting the same note yields the same `EdgeInput[]`\n * (order-stable; mention candidates sorted by `alias_norm` ASC inside\n * `db.aliases.listAll()`). The DB layer's `UNIQUE INDEX` on\n * `(source_doc, target_doc, type, anchor)` + `INSERT OR IGNORE` makes\n * the write side idempotent independently — see `src/db/queries/edges.ts`.\n */\n\nimport type { ParsedNote } from \"../types.js\";\nimport type { EdgeInput } from \"../db/queries/edges.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { WikilinkResolver } from \"./resolver.js\";\n\n// ───────────────────────────────────────────────────────────────────────────\n// constants (D-03 + Pitfall 6)\n// ───────────────────────────────────────────────────────────────────────────\n\n/**\n * Minimum casefolded alias length eligible for mention extraction.\n *\n * D-03 fixes this at 4 to block pronoun / acronym noise (\"the\", \"API\",\n * \"you\"). RESEARCH §Pitfall 2 + A1 carry the empirical reasoning; if\n * false positives exceed 3/note on the Atlas fixture, the plan\n * §verification step raises it to 5.\n */\nexport const MIN_MENTION_LEN = 4 as const;\n\n/**\n * Closed allowlist of frontmatter property names whose bare-string\n * values are resolved against `note_aliases` (Pitfall 6 rule (b)).\n *\n * Sealed at the **type level** via `ReadonlySet<string>` — the TS\n * compiler rejects `.add()` at any call site without an explicit\n * cast. Runtime sealing via `Object.freeze` is intentionally avoided:\n * it is a no-op on the internal slot Set uses for its entries, so it\n * gives a false sense of immutability. The closed-set property is a\n * *compile-time* invariant; widening this set requires an ADR plus a\n * matching update to the threat-model mitigations T-04-02-01 +\n * T-04-02-02 (over-activation / private-term over-matching).\n */\nexport const FRONTMATTER_REF_ALLOWLIST: ReadonlySet<string> = new Set<string>([\n \"assignee\",\n \"owner\",\n \"project\",\n \"related\",\n \"parent\",\n \"child\",\n \"attendees\",\n \"superseded_by\",\n]);\n\n// ───────────────────────────────────────────────────────────────────────────\n// entry point — D-02\n// ───────────────────────────────────────────────────────────────────────────\n\n/**\n * Run all four extractors on a single parsed note. No cross-type\n * dedup — the UNIQUE index on `edges` handles row-level idempotency\n * (Pattern C from PATTERNS.md).\n *\n * Order:\n * 1. wikilink (delegates to `extractWikilinkEdges` — same shape as\n * the legacy `insertWikilinks` helper produces, just reshaped\n * to `EdgeInput`)\n * 2. mention\n * 3. frontmatter-ref\n * 4. hyperlink\n *\n * Stable order matters for snapshot tests downstream (Plan 04-04\n * cluster output) and for the per-note edge dump used in\n * `<verification>` empirical validation.\n */\nexport function extractAllEdges(\n vault: Vault,\n parsed: ParsedNote,\n resolver: WikilinkResolver,\n): EdgeInput[] {\n return [\n ...extractWikilinkEdges(parsed, resolver),\n ...extractMentionEdges(parsed, vault),\n ...extractFrontmatterRefEdges(parsed, vault, resolver),\n ...extractHyperlinkEdges(parsed),\n ];\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// wikilink extractor\n// ───────────────────────────────────────────────────────────────────────────\n\n/**\n * Reshape `parsed.wikilinks` (produced by the parser's\n * `extractWikilinks` + `extractFrontmatterWikilinks`) into typed\n * `EdgeInput` rows. Resolution uses the long-lived `WikilinkResolver`\n * to amortize prepared-statement cost across a full indexer run.\n *\n * Per D-01, the legacy `wikilinks` table also receives these rows\n * via the existing `insertWikilinks` helper in `single.ts` /\n * `indexer.ts`. This function only adds the `edges` side; the\n * indexer write path stays a dual-write until v3 retires `wikilinks`.\n */\nexport function extractWikilinkEdges(parsed: ParsedNote, resolver: WikilinkResolver): EdgeInput[] {\n const out: EdgeInput[] = [];\n for (const wl of parsed.wikilinks) {\n const hit = resolver.resolve(wl.normalizedTarget);\n out.push({\n targetNoteId: hit?.id ?? null,\n targetPath: wl.normalizedTarget,\n type: \"wikilink\",\n rel: null,\n anchor: wl.anchor,\n lineNumber: wl.line,\n linkText: wl.alias,\n });\n }\n return out;\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// mention extractor — D-03\n// ───────────────────────────────────────────────────────────────────────────\n\ninterface MentionCandidate {\n noteId: number;\n path: string;\n}\n\n/**\n * Per-note mention extraction.\n *\n * Algorithm (RESEARCH lines 581–611):\n * 1. Build candidate set from `note_aliases` — casefold each alias\n * and skip if length < MIN_MENTION_LEN. (T-04-02-04 mitigation:\n * `db.aliases.listAll()` returns rows sorted by `alias_norm`\n * ASC for deterministic regex alternation.)\n * 2. Mask the note body to keep only \"paragraph\" scope:\n * - strip fenced code blocks (replace contents with spaces,\n * preserving newlines so line numbers stay aligned),\n * - strip ATX heading lines,\n * - strip inline backtick code spans,\n * - strip wikilink `[[...]]` spans (those become wikilink\n * edges; the bare text after the span on the same line\n * can still match — see Test 3).\n * 3. Run `\\b(alt1|alt2|...)\\b` (casefold + Unicode-aware\n * word-boundary via lookbehind/lookahead on \\w) over the\n * masked body; for each hit, push an EdgeInput.\n * 4. Dedup by `${targetNoteId}:${lineNumber}` per RESEARCH line 609.\n */\nexport function extractMentionEdges(parsed: ParsedNote, vault: Vault): EdgeInput[] {\n const candidates = buildMentionCandidateSet(vault);\n if (candidates.size === 0) return [];\n\n const masked = maskForMentionScope(parsed.content);\n\n // Precompute line starts for O(log n) line lookup per match.\n const lineStarts = computeLineStarts(masked);\n\n // Build a single regex from the candidate set. Sorted descending by\n // length so longer aliases win greedy alternation (prevents \"alice\"\n // from masking \"alice-chen\" when both are registered).\n const alts = [...candidates.keys()]\n .sort((a, b) => b.length - a.length || a.localeCompare(b))\n .map(escapeRegex);\n // Word-boundary via character-class lookbehind/lookahead so it\n // works for aliases containing `-` and `_` (which \\w does match).\n // We use `(?<![\\w-])` + `(?![\\w-])` — the alias side keeps\n // hyphens intact (\"alice-chen\" as a whole token) while still\n // rejecting \"inspire\" matching \"spire\".\n const re = new RegExp(`(?<![\\\\w-])(?:${alts.join(\"|\")})(?![\\\\w-])`, \"gi\");\n\n const seen = new Set<string>();\n const out: EdgeInput[] = [];\n let match: RegExpExecArray | null;\n while ((match = re.exec(masked)) !== null) {\n const lower = match[0].toLowerCase();\n const cand = candidates.get(lower);\n if (!cand) continue;\n const line = lineOf(lineStarts, match.index);\n const key = `${cand.noteId}:${line}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push({\n targetNoteId: cand.noteId,\n targetPath: cand.path,\n type: \"mention\",\n rel: null,\n anchor: null,\n lineNumber: line,\n linkText: null,\n });\n }\n return out;\n}\n\nfunction buildMentionCandidateSet(vault: Vault): Map<string, MentionCandidate> {\n const out = new Map<string, MentionCandidate>();\n for (const row of vault.db.aliases.listAll()) {\n const norm = row.alias_norm;\n if (norm.length < MIN_MENTION_LEN) continue;\n // First-seen-wins so the deterministic `alias_norm ASC` order\n // from listAll() decides ties.\n if (!out.has(norm)) {\n out.set(norm, { noteId: row.note_id, path: row.path });\n }\n }\n return out;\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// frontmatter-ref extractor — Pitfall 6\n// ───────────────────────────────────────────────────────────────────────────\n\nconst WIKILINK_SHAPED = /^\\s*\\[\\[([^\\]]+)\\]\\]\\s*$/;\n\n/**\n * Recursive frontmatter walker — emits one edge per matched value.\n *\n * Rule (a): wikilink-shaped property value at ANY depth → resolver\n * lookup. `rel` carries the TOP-LEVEL property name (not the dotted\n * path — RESEARCH +interfaces both treat `attendees: [\"[[X]]\"]` as\n * `rel='attendees'` for every array element; this matches the plan's\n * Test 9 expectation and aligns with how Plan 04-03's `expand()`\n * filters by `rel`).\n *\n * Rule (b): top-level property name in the closed 8-key allowlist\n * with a bare-string value → resolve against `note_aliases` only.\n * Sub-arrays of bare strings on allowlisted keys are also resolved\n * (e.g. `attendees: [\"alice-chen\", \"bob-martinez\"]` — each element\n * goes through the alias resolver).\n *\n * Rule (a) takes precedence over (b) for a given value: a value\n * that's `[[...]]` shaped never falls through to alias-only\n * resolution.\n */\nexport function extractFrontmatterRefEdges(\n parsed: ParsedNote,\n vault: Vault,\n resolver: WikilinkResolver,\n): EdgeInput[] {\n const fm = parsed.frontmatter;\n if (!fm) return [];\n\n const out: EdgeInput[] = [];\n\n for (const [key, value] of Object.entries(fm)) {\n if (key === \"aliases\" || key === \"alias\") continue;\n collectFrontmatterRefsForKey(key, value, vault, resolver, out);\n }\n return out;\n}\n\nfunction collectFrontmatterRefsForKey(\n key: string,\n value: unknown,\n vault: Vault,\n resolver: WikilinkResolver,\n out: EdgeInput[],\n): void {\n // Array → recurse per element with same `key`.\n if (Array.isArray(value)) {\n for (const item of value) {\n collectFrontmatterRefsForKey(key, item, vault, resolver, out);\n }\n return;\n }\n // Plain string — try rule (a) first, then rule (b) if allowlisted.\n if (typeof value === \"string\") {\n // Rule (a) — wikilink syntax. Fires for ANY key.\n const wl = WIKILINK_SHAPED.exec(value);\n if (wl !== null) {\n const inner = wl[1];\n if (inner !== undefined) {\n // Strip alias / anchor parts mirroring the body wikilink parser.\n const normalized = normalizeWikilinkInner(inner);\n if (normalized.length > 0) {\n const hit = resolver.resolve(normalized);\n if (hit) {\n out.push({\n targetNoteId: hit.id,\n targetPath: normalized,\n type: \"frontmatter-ref\",\n rel: key,\n anchor: null,\n lineNumber: null,\n linkText: null,\n });\n }\n }\n }\n return;\n }\n // Rule (b) — closed allowlist; alias-only resolution.\n if (FRONTMATTER_REF_ALLOWLIST.has(key)) {\n const aliasHit = vault.db.aliases.resolve(value);\n if (aliasHit) {\n out.push({\n targetNoteId: aliasHit.note_id,\n targetPath: aliasHit.path,\n type: \"frontmatter-ref\",\n rel: key,\n anchor: null,\n lineNumber: null,\n linkText: null,\n });\n }\n }\n return;\n }\n // Nested object — recurse, but carry the TOP-LEVEL key forward\n // (consistent with the array case + the plan's Test 9 expectation).\n if (value !== null && typeof value === \"object\") {\n for (const v of Object.values(value as Record<string, unknown>)) {\n collectFrontmatterRefsForKey(key, v, vault, resolver, out);\n }\n }\n}\n\nfunction normalizeWikilinkInner(inner: string): string {\n // Strip `|alias` and `#anchor` suffixes; trim; drop trailing `.md`.\n let s = inner;\n const pipe = s.indexOf(\"|\");\n if (pipe >= 0) s = s.slice(0, pipe);\n const hash = s.indexOf(\"#\");\n if (hash >= 0) s = s.slice(0, hash);\n s = s.trim().replace(/\\\\/g, \"/\").replace(/\\.md$/i, \"\");\n return s;\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// hyperlink extractor\n// ───────────────────────────────────────────────────────────────────────────\n\nconst MD_LINK_RE = /(!?)\\[(?:[^\\]]*?)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g;\nconst BARE_URL_RE = /(?<![\\(\\[a-zA-Z0-9])https?:\\/\\/[^\\s)\\]]+/g;\n\n/**\n * Paragraph-scope hyperlink extraction.\n *\n * Captures:\n * - `[text](https?://...)` — markdown link form\n * - `![alt](https?://...)` — image with http(s) target only\n * - bare `https?://...` URLs in prose\n *\n * Skips:\n * - relative `[text](path)` / `![alt](path)` — those are future\n * `embed` edges (Phase 4 v3 scope).\n * - URLs inside fenced code blocks — masked out before matching,\n * same treatment as mention scope.\n *\n * One edge per unique URL per line — dedup happens on `(targetPath,\n * lineNumber)` because the same URL may legitimately appear on\n * different lines and we want to preserve the line provenance.\n * Per-line collapse mirrors the mention dedup rule (RESEARCH 609).\n */\nexport function extractHyperlinkEdges(parsed: ParsedNote): EdgeInput[] {\n const masked = maskForMentionScope(parsed.content);\n const lineStarts = computeLineStarts(masked);\n\n const seen = new Set<string>();\n const out: EdgeInput[] = [];\n\n MD_LINK_RE.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = MD_LINK_RE.exec(masked)) !== null) {\n const url = m[2];\n if (url === undefined) continue;\n const line = lineOf(lineStarts, m.index);\n const cleaned = stripTrailingPunctuation(url);\n pushHyperlinkEdge(out, seen, cleaned, line);\n }\n\n BARE_URL_RE.lastIndex = 0;\n while ((m = BARE_URL_RE.exec(masked)) !== null) {\n const raw = m[0];\n const line = lineOf(lineStarts, m.index);\n const cleaned = stripTrailingPunctuation(raw);\n pushHyperlinkEdge(out, seen, cleaned, line);\n }\n\n return out;\n}\n\nfunction pushHyperlinkEdge(out: EdgeInput[], seen: Set<string>, url: string, line: number): void {\n const key = `${url}:${line}`;\n if (seen.has(key)) return;\n seen.add(key);\n out.push({\n targetNoteId: null,\n targetPath: url,\n type: \"hyperlink\",\n rel: null,\n anchor: null,\n lineNumber: line,\n linkText: null,\n });\n}\n\nfunction stripTrailingPunctuation(url: string): string {\n // Trim common terminator punctuation that authors append immediately\n // after a URL (\"...see https://example.com.\"). Keeps trailing slashes\n // and intentional fragments / queries intact.\n return url.replace(/[.,;:!?]+$/, \"\");\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// shared masking — keep mention + hyperlink scope to \"paragraph-like\"\n// regions: no headings, no fenced code, no inline code, no [[wikilink]]\n// spans. Newlines and offsets are preserved so line lookups stay valid.\n// ───────────────────────────────────────────────────────────────────────────\n\nfunction maskForMentionScope(content: string): string {\n const lines = content.split(\"\\n\");\n const out: string[] = [];\n\n let inFence = false;\n let fenceMarker = \"\";\n for (const line of lines) {\n const trimmed = line.trimStart();\n if (!inFence) {\n const fenceOpen = /^(`{3,}|~{3,})/.exec(trimmed);\n if (fenceOpen !== null && fenceOpen[1] !== undefined) {\n inFence = true;\n fenceMarker = fenceOpen[1][0] ?? \"`\";\n out.push(blankLine(line));\n continue;\n }\n } else {\n const fenceClose = /^(`{3,}|~{3,})\\s*$/.exec(trimmed);\n if (fenceClose !== null && fenceClose[1] !== undefined && fenceClose[1][0] === fenceMarker) {\n inFence = false;\n out.push(blankLine(line));\n continue;\n }\n out.push(blankLine(line));\n continue;\n }\n // ATX heading lines: mask entirely. (Setext headings are rare in\n // Obsidian vaults and v1 wikilink extraction did not special-case\n // them either — leaving them in scope is consistent.)\n if (/^\\s{0,3}#{1,6}\\s/.test(line)) {\n out.push(blankLine(line));\n continue;\n }\n // Mask inline code spans + bracketed wikilinks within the line.\n let lineOut = line;\n lineOut = maskRanges(lineOut, /`[^`\\n]*`/g);\n lineOut = maskRanges(lineOut, /\\[\\[[^\\[\\]\\n]+\\]\\]/g);\n out.push(lineOut);\n }\n\n return out.join(\"\\n\");\n}\n\nfunction blankLine(line: string): string {\n // Preserve length so byte offsets / line numbers are stable.\n return \" \".repeat(line.length);\n}\n\nfunction maskRanges(line: string, re: RegExp): string {\n let result = \"\";\n let last = 0;\n re.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(line)) !== null) {\n result += line.slice(last, m.index);\n result += \" \".repeat(m[0].length);\n last = m.index + m[0].length;\n }\n result += line.slice(last);\n return result;\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// line-lookup helpers (mirrors the obsidian-fs parser idiom)\n// ───────────────────────────────────────────────────────────────────────────\n\nfunction computeLineStarts(content: string): number[] {\n const starts: number[] = [0];\n for (let i = 0; i < content.length; i++) {\n if (content[i] === \"\\n\") starts.push(i + 1);\n }\n return starts;\n}\n\nfunction lineOf(lineStarts: number[], offset: number): number {\n // Largest lineStart <= offset, 1-based.\n let lo = 0;\n let hi = lineStarts.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n const v = lineStarts[mid];\n if (v !== undefined && v <= offset) lo = mid;\n else hi = mid - 1;\n }\n return lo + 1;\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[\\\\^$.*+?()[\\]{}|]/g, \"\\\\$&\");\n}\n","/**\n * Phase 3 — `src/sections/` barrel.\n *\n * Re-exports the section-identity surface that the indexer, the\n * assembly layer (`src/assembly/` — landing in Phase 3 slices\n * 03-02..03-04), and downstream consumers depend on.\n *\n * Adapter-seam discipline (per 03-CONTEXT.md, enforced by\n * `scripts/lint-adapters.sh`): nothing under `src/sections/` imports\n * `fs`, `gray-matter`, `chokidar`, `path.join`, or `path.resolve`.\n */\n\nexport { computeAnchor, blockToPlainText } from \"./anchor.js\";\nexport { extractSections, markdownToSectionBlocks } from \"./extract.js\";\nexport { backfillSectionsFromChunks } from \"./backfill.js\";\nexport type { SectionInfo, SectionRow, InsertSectionRow } from \"../types.js\";\n","/**\n * Index Builder — orchestrates Reader → Chunker → Ollama → DB.\n *\n * Two modes:\n * - full: wipe chunks/embeddings/wikilinks, re-index everything\n * - incremental: only re-index notes whose hash changed (default)\n *\n * Returns run statistics.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { scanVault } from \"../adapters/source/obsidian-fs/scanner.js\";\nimport { parseNote } from \"../adapters/source/obsidian-fs/parser.js\";\nimport { chunkNote } from \"../chunker/index.js\";\nimport { computeChunkIdFragment } from \"../chunker/chunk-id.js\";\nimport { OllamaClient } from \"../ollama/index.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type {\n ChunkRow,\n InsertSectionRow,\n ParsedNote,\n ParsedWikilink,\n SectionInfo,\n} from \"../types.js\";\nimport { WikilinkResolver } from \"./resolver.js\";\nimport { extractAllEdges } from \"./extract-edges.js\";\nimport { extractSections, markdownToSectionBlocks } from \"../sections/index.js\";\nimport { extractHeadings } from \"../chunker/headings.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\nexport interface IndexerOptions {\n mode?: \"full\" | \"incremental\";\n embeddingModel: string;\n /** Phase 7c: optional secondary (shadow) embedding model. When set, every\n * chunk is embedded with BOTH the primary and the secondary model in\n * parallel. Stored in separate `embeddings_<dim>` tables so search and\n * the active model are unaffected. Used by `/setup-memory-system` and\n * the watcher to keep a shadow index live for a future model switch. */\n secondaryEmbeddingModel?: string;\n /** Ollama client. Required when `embeddings !== \"none\"`. ContextFit-backed\n * vaults pass `embeddings: \"none\"` and may omit this (no Ollama needed). */\n ollama?: OllamaClient;\n /** ADR-008: embedding strategy.\n * - \"ollama\" (default): build the full SQLite layer AND embed chunks into\n * sqlite-vec (the classic path).\n * - \"none\": build the full SQLite content layer (notes, chunks, sections,\n * wikilinks, edges, audit) but SKIP embedding + model registration. Used\n * by ContextFit vaults, whose search runs through the ContextFit engine —\n * the SQLite layer still powers graph/sections/frontmatter/stats tools. */\n embeddings?: \"ollama\" | \"none\";\n /** Called periodically with progress info. */\n onProgress?: (msg: string) => void;\n}\n\nexport interface IndexRunResult {\n runId: string;\n status: \"completed\" | \"failed\";\n notesIndexed: number;\n notesUpdated: number;\n notesDeleted: number;\n notesSkipped: number;\n chunksCreated: number;\n durationMs: number;\n error?: string;\n}\n\nexport async function indexVault(vault: Vault, options: IndexerOptions): Promise<IndexRunResult> {\n const startedAt = Date.now();\n const runId = randomUUID();\n const mode = options.mode ?? \"incremental\";\n const log = options.onProgress ?? (() => {});\n // ADR-008: \"none\" builds the SQLite content layer but skips embedding +\n // model registration (ContextFit vaults). \"ollama\" is the classic path.\n const embedMode = options.embeddings ?? \"ollama\";\n const ollama = options.ollama;\n\n // 1. Resolve / upsert model in DB — ONLY for the Ollama embedding path.\n // ContextFit vaults register no model and need no Ollama at all.\n let dim = 0;\n let modelRow: { id: number } | null = null;\n let secondaryModelRow: { id: number; dim: number } | null = null;\n\n if (embedMode === \"ollama\") {\n if (!ollama) {\n throw new Error(\"indexVault: embeddings='ollama' requires an OllamaClient (options.ollama).\");\n }\n log(`Probing Ollama model: ${options.embeddingModel}`);\n const health = await ollama.healthCheck();\n if (!health.ok) {\n throw new Error(`Ollama unreachable: ${health.error ?? \"unknown error\"}`);\n }\n const modelExists = await ollama.modelExists(options.embeddingModel);\n if (!modelExists) {\n throw new Error(\n `Embedding model \"${options.embeddingModel}\" not found in Ollama. ` +\n `Available: ${health.models?.join(\", \") ?? \"(none)\"}. ` +\n `Run: ollama pull ${options.embeddingModel}`,\n );\n }\n\n // Probe dim with a 1-text embed (cheap)\n const probe = await ollama.embed({\n model: options.embeddingModel,\n texts: [\"probe\"],\n });\n dim = probe.dim;\n modelRow = vault.db.models.upsert({\n name: options.embeddingModel,\n provider: \"ollama\",\n dim,\n });\n\n // Phase 7c: secondary (shadow) model registration. We probe + upsert with\n // active=false so the primary stays active. Probing also fails fast if the\n // model isn't pulled — better than discovering that mid-run on note 5000.\n if (options.secondaryEmbeddingModel) {\n const secName = options.secondaryEmbeddingModel;\n log(`Probing secondary (shadow) model: ${secName}`);\n const secExists = await ollama.modelExists(secName);\n if (!secExists) {\n throw new Error(\n `Secondary embedding model \"${secName}\" not found in Ollama. ` +\n `Run: ollama pull ${secName}`,\n );\n }\n const secProbe = await ollama.embed({\n model: secName,\n texts: [\"probe\"],\n });\n const row = vault.db.models.upsert({\n name: secName,\n provider: \"ollama\",\n dim: secProbe.dim,\n active: false,\n });\n secondaryModelRow = { id: row.id, dim: row.dim };\n }\n }\n\n vault.db.audit.startRun({\n runId,\n vaultName: vault.config.name,\n modelId: modelRow?.id ?? null,\n trigger: mode === \"full\" ? \"manual-full\" : \"manual-incremental\",\n });\n\n let notesIndexed = 0;\n let notesUpdated = 0;\n let notesDeleted = 0;\n let notesSkipped = 0;\n let chunksCreated = 0;\n\n // Per-run resolver: prepared statements reused, results memoised.\n // First pass uses this. Second pass (after all notes are inserted) uses\n // a fresh instance so newly-visible notes aren't masked by stale \"null\"\n // cache entries from the first pass.\n const firstPassResolver = new WikilinkResolver(vault);\n\n try {\n // 2. Full mode: clear derived layer\n if (mode === \"full\") {\n log(\"Full mode: clearing existing chunks and embeddings\");\n // Cascade via FK: deleting notes wipes chunks/embeddings/wikilinks.\n // But we want to keep notes (and re-upsert) — so we clear chunks only.\n vault.db.transaction(() => {\n const allNotes = vault.db.notes.listAll();\n for (const n of allNotes) {\n // Sections FIRST — sections reference chunks via\n // chunk_id_first/last with no ON DELETE cascade, so deleting\n // chunks while sections still point at them trips a FOREIGN KEY\n // constraint. Mirrors the per-note re-index path below and\n // single.ts step 7. (Issue #16: this full-wipe loop omitted the\n // sections delete, so `index --full` failed on any vault with a\n // populated sections table.)\n vault.db.sections.deleteByNote(n.id);\n vault.db.chunks.deleteByNote(n.id);\n vault.db.wikilinks.deleteByNote(n.id);\n // Phase 4 / 04-01 (D-01): dual-write mirror.\n vault.db.edges.deleteByNote(n.id);\n }\n });\n }\n\n // 3. Scan vault\n log(`Scanning ${vault.config.path}`);\n const files = await scanVault(vault.config.path, {\n excludeGlobs: vault.config.exclude_globs,\n });\n log(`Found ${files.length} markdown files`);\n\n // 4. Parse + decide per-note\n const parsedNotes: Array<{ parsed: ParsedNote; noteId: number; needsReindex: boolean }> = [];\n\n for (const file of files) {\n let parsed: ParsedNote;\n try {\n parsed = await parseNote(file, vault.config.path);\n } catch (err) {\n // Robustheit gegen invalides Frontmatter / kaputte Notes:\n // skip + log statt Vault-Abort. User-Notes sind nicht unser Vertrag.\n notesSkipped++;\n const msg = err instanceof Error ? err.message.split(\"\\n\")[0] : String(err);\n const rel = file.startsWith(vault.config.path)\n ? file.slice(vault.config.path.length + 1)\n : file;\n log(` skipped (parse error): ${rel} — ${msg}`);\n continue;\n }\n // Issue #14 / P1: read the PRE-upsert state so we can make a correct\n // re-index decision. `upsertByPath` mutates notes.hash/content in place,\n // so we must capture the previous hashes BEFORE it runs — otherwise a\n // changed body keeps stale chunks/embeddings/sections/edges (the bug).\n // Mirrors the 3-way decision in `src/indexer/single.ts`:\n // - hash unchanged → no re-embed (metadata maintenance only)\n // - body_hash unchanged → frontmatter-only edit: keep chunks\n // - body changed / NULL body_hash → full re-embed\n const previous = vault.db.notes.getByPath(parsed.relativePath);\n const hashUnchanged = previous != null && previous.hash === parsed.hash;\n const bodyUnchanged =\n previous != null && previous.body_hash != null && previous.body_hash === parsed.bodyHash;\n\n const upsert = vault.db.notes.upsertByPath({\n path: parsed.relativePath,\n content: parsed.content,\n frontmatter: parsed.frontmatter ? JSON.stringify(parsed.frontmatter) : null,\n title: parsed.title,\n hash: parsed.hash,\n bodyHash: parsed.bodyHash,\n mtime: parsed.mtime,\n wordCount: parsed.wordCount,\n });\n\n // Phase 3 / 03-01 (M4): maintain the denormalized notes.status\n // column in sync with the frontmatter on every write. The\n // migration-time backfill populates this for existing notes;\n // this call keeps it correct for new writes and re-indexes.\n // Done unconditionally (every run, not just on reindex) so an\n // alias-only frontmatter edit that flips status also propagates.\n vault.db.notes.setStatus(upsert.id, extractStatus(parsed.frontmatter));\n\n // Persist aliases from frontmatter. We do this every run (not just on\n // reindex) so alias-only frontmatter edits propagate even when the body\n // is unchanged. The set is idempotent: setForNote does delete+insert.\n vault.db.aliases.setForNote(upsert.id, extractAliases(parsed.frontmatter));\n\n // A note with zero chunks still needs (re-)indexing even if its hash\n // matched — e.g. a previous run inserted the row but crashed before\n // chunking, or a legacy row predates the chunk layer.\n const chunkCount = vault.db.chunks.getByNote(upsert.id).length;\n\n // Full re-embed when: full mode, brand-new note, no chunks yet, OR the\n // body actually changed (hash differs AND body_hash differs / is NULL).\n const bodyChanged = !hashUnchanged && !bodyUnchanged;\n const needsReindex = mode === \"full\" || upsert.isNew || chunkCount === 0 || bodyChanged;\n\n // Frontmatter-only edit (hash changed, body identical): the note row +\n // status + aliases are already updated above. We must ALSO refresh\n // wikilinks + typed edges (frontmatter can hold wikilink-shaped refs\n // like `owner: \"[[X]]\"`), but we KEEP chunks/embeddings/sections —\n // no Ollama roundtrip. Mirrors single.ts step 4b.\n const frontmatterOnly = !upsert.isNew && !needsReindex && !hashUnchanged;\n\n if (upsert.isNew) notesIndexed++;\n else if (needsReindex || frontmatterOnly) notesUpdated++;\n\n if (needsReindex) {\n parsedNotes.push({ parsed, noteId: upsert.id, needsReindex: true });\n } else if (frontmatterOnly) {\n vault.db.wikilinks.deleteByNote(upsert.id);\n vault.db.edges.deleteByNote(upsert.id);\n insertWikilinks(vault, upsert.id, parsed.wikilinks, firstPassResolver);\n writeAllEdges(vault, upsert.id, parsed, firstPassResolver);\n }\n }\n\n log(`${parsedNotes.length} notes need (re-)indexing`);\n\n // 5. Chunk + embed + persist\n for (const { parsed, noteId } of parsedNotes) {\n // Clear derived layer for this note. Sections FIRST — sections\n // reference chunks via chunk_id_first/last with no ON DELETE cascade,\n // so deleting chunks while sections still point at them trips a\n // FOREIGN KEY constraint. Ordering matches single.ts step 7. (Before\n // Issue #14's P1 fix this path only ran for brand-new notes, which\n // have no sections yet — so the wrong order never surfaced. It does\n // now that changed notes are correctly re-indexed.)\n vault.db.sections.deleteByNote(noteId);\n vault.db.chunks.deleteByNote(noteId);\n vault.db.wikilinks.deleteByNote(noteId);\n // Phase 4 / 04-01 (D-01): dual-write mirror.\n vault.db.edges.deleteByNote(noteId);\n\n const chunks = chunkNote(parsed.indexedContent);\n\n if (chunks.length === 0) {\n // empty note — record wikilinks anyway, but no chunks/embeddings\n insertWikilinks(vault, noteId, parsed.wikilinks, firstPassResolver);\n // Phase 4 / 04-02 / GRA-04 / D-02: also emit typed edges for\n // frontmatter-ref / hyperlink / mention. A note with only\n // frontmatter (no body) can still contribute owner / attendees\n // edges to the graph.\n writeAllEdges(vault, noteId, parsed, firstPassResolver);\n continue;\n }\n\n // Insert chunks first to get IDs.\n // Phase 5 / D-05: compute chunk_id_fragment via the canonical\n // helper (`src/chunker/chunk-id.ts`) at every insert path.\n // Scattered createHash calls are an anti-pattern (RESEARCH §Pitfall 14).\n const chunkInputs = chunks.map((c) => ({\n idx: c.idx,\n text: c.text,\n headingPath: c.headingPath,\n startOffset: c.startOffset,\n endOffset: c.endOffset,\n tokenCount: c.tokenCount,\n chunkIdFragment: computeChunkIdFragment(c.text),\n }));\n const chunkIds = vault.db.chunks.insertBatch(noteId, chunkInputs);\n\n // Phase 3 / 03-01: extract + persist sections. Runs AFTER chunks\n // are inserted so chunk IDs exist (sections.chunk_id_first/last\n // reference chunks.id). The chunk-to-section binning uses the\n // chunker's start_offset to find each chunk's owning heading\n // region. Sections of a heading with no body content get\n // chunk_id_first = chunk_id_last = NULL.\n // Defensive: section building must never abort the whole vault index\n // because of one pathological note. The duplicate-anchor crash is\n // handled at the insert layer (insertOneResolving); this catch covers\n // any other unexpected failure — log and continue with the rest of the\n // vault (see ISSUE-indexer-duplicate-anchor.md \"Notes for the agent\").\n try {\n buildSectionsForNote(vault, noteId, parsed.indexedContent, chunkIds);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `[indexer:${vault.config.name}] section build failed for ${parsed.relativePath}: ${message} — skipping sections for this note`,\n );\n }\n\n // Embed — ONLY in the Ollama path. ContextFit vaults (embedMode \"none\")\n // skip this entirely: chunks + sections + links + edges are persisted\n // above; search runs through the ContextFit engine, not sqlite-vec.\n if (embedMode === \"ollama\") {\n const embedResult = await ollama!.embed({\n model: options.embeddingModel,\n texts: chunks.map((c) => c.text),\n });\n if (embedResult.dim !== dim) {\n throw new Error(`Embedding dimension mismatch: expected ${dim}, got ${embedResult.dim}`);\n }\n\n const embeddingInputs = chunkIds.map((chunkId, i) => ({\n chunkId,\n modelId: modelRow!.id,\n vector: embedResult.vectors[i]!,\n }));\n vault.db.embeddings.insertBatch(embeddingInputs);\n\n // Phase 7c: shadow-index pass. Embed each chunk a second time with\n // the secondary model and persist into its dim-specific table.\n // Independent failure surface: if secondary embed throws, the primary\n // index for this run still completes — the secondary will be retried\n // on the next index run (idempotent: LEFT JOIN in start_shadow_index).\n if (secondaryModelRow) {\n const secEmbed = await ollama!.embed({\n model: options.secondaryEmbeddingModel!,\n texts: chunks.map((c) => c.text),\n });\n if (secEmbed.dim !== secondaryModelRow.dim) {\n throw new Error(\n `Secondary embedding dimension mismatch: expected ` +\n `${secondaryModelRow.dim}, got ${secEmbed.dim}`,\n );\n }\n vault.db.embeddings.insertBatch(\n chunkIds.map((chunkId, i) => ({\n chunkId,\n modelId: secondaryModelRow!.id,\n vector: secEmbed.vectors[i]!,\n })),\n );\n }\n }\n\n // Wikilinks (v1 invariant write path — D-01)\n insertWikilinks(vault, noteId, parsed.wikilinks, firstPassResolver);\n // Phase 4 / 04-02 / GRA-04 / D-02: typed-edge unified write.\n writeAllEdges(vault, noteId, parsed, firstPassResolver);\n\n chunksCreated += chunks.length;\n }\n\n // 6. Detect deleted notes (in DB but not on disk)\n const knownPaths = new Set(files.map((f) => relativize(f, vault.config.path)));\n const dbNotes = vault.db.notes.listAll();\n for (const n of dbNotes) {\n if (!knownPaths.has(n.path)) {\n vault.db.notes.deleteByPath(n.path);\n notesDeleted++;\n }\n }\n\n // 7. Second-pass wikilink resolution.\n //\n // The first pass resolved wikilinks while notes were being inserted in\n // arbitrary order — so any link to a note that hadn't been inserted yet,\n // or any link via an alias whose owner hadn't been processed yet, was\n // marked unresolved. Now that the full notes + aliases tables exist, we\n // re-resolve broken links once. This converts \"transient broken\" links\n // (resolution-order artifact) into proper edges without re-parsing files.\n log(\"Resolving deferred wikilinks (second pass)\");\n const broken = vault.db.wikilinks.resolveBrokenLinks();\n let resolved = 0;\n const updateStmt = vault.db.handle.prepare(\n `UPDATE wikilinks SET target_note = ?\n WHERE source_note = ? AND target_path = ? AND target_note IS NULL`,\n );\n // Fresh resolver for the second pass — the notes table is now complete,\n // so first-pass \"null\" cache entries would mask newly-resolvable links.\n const secondPassResolver = new WikilinkResolver(vault);\n for (const link of broken) {\n const hit = secondPassResolver.resolve(link.targetPath);\n if (hit) {\n updateStmt.run(hit.id, link.sourceNoteId, link.targetPath);\n resolved++;\n }\n }\n if (resolved > 0) log(`Second pass resolved ${resolved} wikilinks`);\n\n vault.db.audit.finishRun(runId, {\n notesIndexed,\n chunksCreated,\n notesUpdated,\n notesDeleted,\n });\n\n if (notesSkipped > 0) {\n log(`${notesSkipped} note(s) skipped due to parse errors`);\n }\n\n return {\n runId,\n status: \"completed\",\n notesIndexed,\n notesUpdated,\n notesDeleted,\n notesSkipped,\n chunksCreated,\n durationMs: Date.now() - startedAt,\n };\n } catch (err) {\n const message = errorMessage(err);\n vault.db.audit.finishRun(runId, {\n notesIndexed,\n chunksCreated,\n notesUpdated,\n notesDeleted,\n error: message,\n });\n return {\n runId,\n status: \"failed\",\n notesIndexed,\n notesUpdated,\n notesDeleted,\n notesSkipped,\n chunksCreated,\n durationMs: Date.now() - startedAt,\n error: message,\n };\n }\n}\n\nfunction insertWikilinks(\n vault: Vault,\n sourceNoteId: number,\n wikilinks: ParsedWikilink[],\n resolver?: WikilinkResolver,\n): void {\n if (wikilinks.length === 0) return;\n\n const r = resolver ?? new WikilinkResolver(vault);\n const inputs = wikilinks.map((wl) => {\n const target = r.resolve(wl.normalizedTarget);\n return {\n targetPath: wl.normalizedTarget,\n targetNoteId: target?.id ?? null,\n linkText: wl.alias,\n anchor: wl.anchor,\n lineNumber: wl.line,\n };\n });\n vault.db.wikilinks.insertBatch(sourceNoteId, inputs);\n // Phase 4 / 04-02 / GRA-04 / D-02 — the unified edge write is no\n // longer co-located here. `writeAllEdges` (called immediately\n // after this helper at every call site) produces the full typed\n // edge mix in a single pass, sharing this same `WikilinkResolver`\n // so cache lookups are not duplicated. The legacy `wikilinks`\n // table write above stays for v1 invariance per D-01.\n}\n\n/**\n * Phase 4 / 04-02 / GRA-04 / D-02 — emit all four typed edges into\n * `vault.db.edges`. Callers MUST have already issued\n * `vault.db.edges.deleteByNote(sourceNoteId)` for a clean replace;\n * the UNIQUE index on `(source_doc, target_doc, type, anchor)` +\n * `INSERT OR IGNORE` makes the write idempotent regardless.\n *\n * The full-index path passes its long-lived `firstPassResolver` so\n * the wikilink-edge resolution shares the same cache as the\n * frontmatter-ref rule-(a) lookups. Plan 04-01's second-pass broken-\n * link resolver (`secondPassResolver`) only mutates the `wikilinks`\n * table — Plan 04-03 will lift that into `edges` if needed.\n */\nfunction writeAllEdges(\n vault: Vault,\n sourceNoteId: number,\n parsed: ParsedNote,\n resolver: WikilinkResolver,\n): void {\n const edges = extractAllEdges(vault, parsed, resolver);\n if (edges.length > 0) vault.db.edges.insertBatch(sourceNoteId, edges);\n}\n\n/**\n * Resolve a wikilink target the way Obsidian does, in priority order:\n * 1) exact relative path match (with or without .md)\n * 2) filename-only match anywhere in the vault — shortest path wins\n * 3) alias match — looks up note_aliases (case-insensitive)\n *\n * Returns null if no candidate exists (true broken link).\n */\nexport function resolveWikilinkTarget(\n vault: Vault,\n normalizedTarget: string,\n): { id: number; path: string } | null {\n // API-compat wrapper. Single-call sites (e.g. single-note re-index) pay\n // the prepared-statement cost per call. The hot path (indexVault) goes\n // through a long-lived WikilinkResolver instance instead.\n return new WikilinkResolver(vault).resolve(normalizedTarget);\n}\n\n/**\n * Extract aliases from a parsed frontmatter object. Accepts the two common\n * shapes Obsidian writes:\n * aliases: [\"OWR\", \"Oliver\"]\n * alias: \"OWR\" (singular form, sometimes used)\n * aliases: \"OWR\" (string fallback)\n *\n * Anything else (numbers, objects) is ignored.\n */\nexport function extractAliases(frontmatter: Record<string, unknown> | null): string[] {\n if (!frontmatter) return [];\n const raw = frontmatter[\"aliases\"] ?? frontmatter[\"alias\"];\n if (raw == null) return [];\n if (typeof raw === \"string\") return [raw];\n if (Array.isArray(raw)) {\n return raw.filter((v): v is string => typeof v === \"string\");\n }\n return [];\n}\n\n/**\n * Phase 3 / 03-01: extract the `status` value from a parsed\n * frontmatter object. Accepts any string value; returns null when\n * absent / non-string. The denormalized `notes.status` column is\n * read by 03-05's SQL-level superseded filter.\n */\nexport function extractStatus(frontmatter: Record<string, unknown> | null): string | null {\n if (!frontmatter) return null;\n const raw = frontmatter[\"status\"];\n if (typeof raw === \"string\") return raw;\n return null;\n}\n\n/**\n * Phase 3 / 03-01: extract sections for a note and persist them.\n *\n * Sections are materialized from the SAME `notes.content` bytes the\n * chunker just consumed — `markdownToSectionBlocks` → `extractSections`\n * runs on the unmodified parsed body. The resulting `SectionInfo[]`\n * gets `chunk_id_first` / `chunk_id_last` filled in by walking the\n * inserted chunk IDs and binning each chunk into the section whose\n * source-offset window contains the chunk's `start_offset`.\n *\n * Sibling: `backfillSectionsFromChunks` does the same operation for\n * existing v1 notes at migration time. Both code paths run the same\n * pipeline against the same `content` bytes → identical anchors\n * (anchor-equivalence proven in\n * `src/sections/backfill.test.ts`).\n */\nexport function buildSectionsForNote(\n vault: Vault,\n noteId: number,\n content: string,\n insertedChunkIds: number[],\n): number {\n if (content.length === 0) return 0;\n const blocks = markdownToSectionBlocks(content);\n const sections = extractSections(blocks);\n if (sections.length === 0) return 0;\n\n // Hydrate just-inserted chunks so we can bin by start_offset. The\n // `getByNote` query returns chunks in `idx` order — same as\n // `insertedChunkIds`. We pass the chunk rows through to the helper\n // so the helper itself is pure (no DB dep).\n const chunkRows = vault.db.chunks.getByNote(noteId);\n // Defensive sanity: chunk count must match.\n if (chunkRows.length !== insertedChunkIds.length) {\n // This should never happen — chunkInputs went in via insertBatch\n // and we read them right back. If it does, the section ranges are\n // best-effort but the anchors are still correct.\n }\n\n const sectionRanges = computeSectionOffsetRanges(content, sections);\n const rangePairs = mapChunksToSections(chunkRows, sectionRanges);\n\n // Materialize rows: parent_id is filled in via the inserted-id map\n // (the in-memory `SectionInfo.parent_index` is an array index).\n // Duplicate-anchor sibling sections (two H2s GitHub-slugify to the same\n // anchor) would collide on UNIQUE(note_id, anchor). `insertOneResolving`\n // collapses later siblings into the first one's row and returns that\n // surviving id, so a single offending note can't abort the whole index\n // run (see ISSUE-indexer-duplicate-anchor.md). `null` is possible in\n // theory (insert ignored AND lookup miss) — mirror the backfill type.\n const insertedIds: Array<number | null> = [];\n for (let i = 0; i < sections.length; i++) {\n const s = sections[i]!;\n const parentId = s.parent_index === null ? null : (insertedIds[s.parent_index] ?? null);\n const pair = rangePairs[i] ?? { first: null, last: null };\n const row: InsertSectionRow = {\n note_id: noteId,\n anchor: s.anchor,\n heading_path: JSON.stringify(s.heading_path),\n heading_text: s.heading_text,\n level: s.level,\n parent_id: parentId,\n ord: s.ord,\n chunk_id_first: pair.first,\n chunk_id_last: pair.last,\n };\n insertedIds.push(vault.db.sections.insertOneResolving(row));\n }\n return insertedIds.length;\n}\n\n/**\n * Phase 3 / 03-01: pure helper that bins each chunk into the section\n * whose source-offset range contains its `start_offset`. Returns the\n * `{first, last}` chunk-id pair per section index (in the order of\n * the `sectionRanges` array). Sections with no contained chunks get\n * `{first: null, last: null}`.\n *\n * Exported for unit testing.\n */\nexport function mapChunksToSections(\n chunks: ChunkRow[],\n sectionRanges: Array<{ start: number; end: number }>,\n): Array<{ first: number | null; last: number | null }> {\n const out: Array<{ first: number | null; last: number | null }> = sectionRanges.map(() => ({\n first: null,\n last: null,\n }));\n for (const chunk of chunks) {\n const offset = chunk.start_offset;\n let chosenIdx: number | null = null;\n // Walk in reverse so the innermost (deepest) section wins.\n for (let i = sectionRanges.length - 1; i >= 0; i--) {\n const r = sectionRanges[i];\n if (!r) continue;\n if (offset >= r.start && offset < r.end) {\n chosenIdx = i;\n break;\n }\n }\n if (chosenIdx === null) continue;\n const slot = out[chosenIdx]!;\n if (slot.first === null || chunk.id < slot.first) slot.first = chunk.id;\n if (slot.last === null || chunk.id > slot.last) slot.last = chunk.id;\n }\n return out;\n}\n\n/**\n * Compute the [start, end) byte range for each section in `content`.\n * Mirrors `src/sections/backfill.ts:computeSectionOffsetRanges`. Kept\n * here (not imported) so the indexer doesn't reach into the\n * backfill module's private surface — both implementations share\n * `src/chunker/headings.ts:extractHeadings` as the canonical heading\n * source, which is what guarantees they agree byte-for-byte.\n */\nfunction computeSectionOffsetRanges(\n content: string,\n sections: SectionInfo[],\n): Array<{ start: number; end: number }> {\n const headings = extractHeadings(content);\n const ranges: Array<{ start: number; end: number }> = [];\n const hasPreamble =\n sections.length > 0 && sections[0]!.level === 0 && sections[0]!.heading_text === \"\";\n const firstHeadingOffset = headings.length === 0 ? content.length : headings[0]!.startOffset;\n if (hasPreamble) {\n ranges.push({ start: 0, end: firstHeadingOffset });\n }\n for (let h = 0; h < headings.length; h++) {\n const h0 = headings[h]!;\n let endOffset = content.length;\n for (let j = h + 1; j < headings.length; j++) {\n if (headings[j]!.level <= h0.level) {\n endOffset = headings[j]!.startOffset;\n break;\n }\n }\n ranges.push({ start: h0.startOffset, end: endOffset });\n }\n while (ranges.length < sections.length) {\n ranges.push({ start: 0, end: content.length });\n }\n return ranges;\n}\n\nfunction relativize(absPath: string, vaultRoot: string): string {\n // Reader produces forward-slash relative paths. We must do the same here\n // so deletion detection works on all platforms.\n let p = absPath;\n if (p.startsWith(vaultRoot)) {\n p = p.slice(vaultRoot.length);\n }\n if (p.startsWith(\"/\") || p.startsWith(\"\\\\\")) {\n p = p.slice(1);\n }\n return p.split(\"\\\\\").join(\"/\");\n}\n","/**\n * Single-Note Indexer — re-index one note efficiently.\n *\n * Used by the file-watcher (Phase 4) to react to individual file events\n * without paying the full-vault setup overhead (model probe, audit run,\n * scan, second-pass wikilink resolution).\n *\n * Behavioral contract: see `indexNote` JSDoc below.\n */\n\nimport * as path from \"node:path\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { OllamaClient } from \"../ollama/index.js\";\nimport { parseNote } from \"../adapters/source/obsidian-fs/parser.js\";\nimport { chunkNote } from \"../chunker/index.js\";\nimport { computeChunkIdFragment } from \"../chunker/chunk-id.js\";\nimport { extractAliases, buildSectionsForNote } from \"./indexer.js\";\nimport { WikilinkResolver } from \"./resolver.js\";\nimport { extractAllEdges } from \"./extract-edges.js\";\nimport type { ParsedNote, ParsedWikilink } from \"../types.js\";\n\nexport interface IndexNoteOptions {\n vault: Vault;\n /** Absolute file path. Must be inside the vault. */\n absolutePath: string;\n embeddingModel: string;\n /** Phase 7c: optional secondary (shadow) model name. When set AND the\n * model is already registered in the vault DB, the watcher / single\n * indexer also writes shadow embeddings so the secondary index stays\n * current with the primary. Unregistered names are ignored silently —\n * registration only happens via a full `indexVault` run. */\n secondaryEmbeddingModel?: string;\n /** Required when `embeddings !== \"none\"`. ContextFit vaults omit it. */\n ollama?: OllamaClient;\n /** ADR-008: \"none\" builds the SQLite content layer for this note but skips\n * embedding (ContextFit vaults). Default \"ollama\". */\n embeddings?: \"ollama\" | \"none\";\n}\n\nexport interface IndexNoteResult {\n status: \"indexed\" | \"unchanged\" | \"outside_vault\" | \"missing\" | \"parse_error\";\n notePath: string | null;\n noteId: number | null;\n chunksCreated: number;\n /** True if this was a brand-new note (vs. updated existing). */\n isNew: boolean;\n}\n\n/**\n * Re-index a single note. Cheaper than a full vault scan: no model probe,\n * no audit run wrapping, no second-pass wikilink resolution (so transient\n * unresolved aliases will be flagged broken — call the full indexer if you\n * need them resolved).\n *\n * Behavior:\n * - Path outside vault → status: \"outside_vault\"\n * - File missing → status: \"missing\" (caller should call removeNote instead)\n * - File hash unchanged → status: \"unchanged\" (no-op fast path; aliases\n * are still re-applied idempotently)\n * - Otherwise → full re-index of this note: parse, chunk, embed, persist,\n * update aliases, persist wikilinks\n */\nexport async function indexNote(options: IndexNoteOptions): Promise<IndexNoteResult> {\n const { vault, absolutePath, embeddingModel, ollama } = options;\n const secondaryName = options.secondaryEmbeddingModel;\n\n // 1. Validate path is inside the vault.\n if (!isInsideVault(absolutePath, vault.config.path)) {\n return emptyResult(\"outside_vault\");\n }\n\n // 2. Parse — handle missing-file fast path and invalid-frontmatter skip.\n let parsed;\n try {\n parsed = await parseNote(absolutePath, vault.config.path);\n } catch (err) {\n if (isENOENT(err)) {\n return emptyResult(\"missing\");\n }\n // Invalid frontmatter or other parse failure: skip silently so a single\n // bad note doesn't kill the watcher or break the indexer mid-run. Caller\n // can inspect `status === \"parse_error\"` if it wants to log.\n return emptyResult(\"parse_error\");\n }\n\n // 3. Look up existing note for hash check.\n const existing = vault.db.notes.getByPath(parsed.relativePath);\n\n // 4. Fast path: hash unchanged → still re-apply aliases idempotently.\n if (existing && existing.hash === parsed.hash) {\n vault.db.aliases.setForNote(existing.id, extractAliases(parsed.frontmatter));\n return {\n status: \"unchanged\",\n notePath: parsed.relativePath,\n noteId: existing.id,\n chunksCreated: 0,\n isNew: false,\n };\n }\n\n // 4b. Body-hash fast path (v0.9.1): combined hash differs but body is\n // unchanged → frontmatter-only edit. Update note row + aliases, but\n // KEEP chunks/embeddings as-is. Saves an Ollama roundtrip per chunk\n // (typically 5-15 per note) on every update_frontmatter call.\n //\n // Wikilinks: extracted from BOTH body and frontmatter. Frontmatter\n // wikilinks (e.g. participation: [\"[[X]]\"]) can change with a\n // frontmatter-only edit, so we still rewrite the wikilinks index.\n //\n // NULL guard: legacy rows pre-migration-006 have body_hash=NULL.\n // `null === parsed.bodyHash` is always false, so we fall through to\n // the full re-embed path. Self-heals on next touch.\n if (existing && existing.body_hash && existing.body_hash === parsed.bodyHash) {\n const upsert = vault.db.notes.upsertByPath({\n path: parsed.relativePath,\n content: parsed.content,\n frontmatter: parsed.frontmatter ? JSON.stringify(parsed.frontmatter) : null,\n title: parsed.title,\n hash: parsed.hash,\n bodyHash: parsed.bodyHash,\n mtime: parsed.mtime,\n wordCount: parsed.wordCount,\n });\n vault.db.aliases.setForNote(upsert.id, extractAliases(parsed.frontmatter));\n vault.db.wikilinks.deleteByNote(upsert.id);\n // ── Phase 4 / 04-02 / GRA-04 / D-02 ──\n // Clear all typed edges and re-extract via the unified extractor.\n // The body-hash fast path is NOT a shortcut around edge\n // re-extraction — frontmatter-only edits (e.g. a new `owner:`\n // wikilink-shape) flip the frontmatter-ref edge mix, so we MUST\n // re-run the extractor here. The legacy wikilinks-table write\n // stays in place per D-01 (v1 invariance).\n vault.db.edges.deleteByNote(upsert.id);\n insertWikilinks(vault, upsert.id, parsed.wikilinks);\n writeAllEdges(vault, upsert.id, parsed);\n return {\n status: \"indexed\",\n notePath: parsed.relativePath,\n noteId: upsert.id,\n chunksCreated: 0,\n isNew: false,\n };\n }\n\n // ADR-008: ContextFit vaults skip embedding (embedMode \"none\") — no model,\n // no Ollama. The classic path requires a registered active model.\n const embedMode = options.embeddings ?? \"ollama\";\n let activeModel: { id: number; name: string; dim: number } | null = null;\n if (embedMode === \"ollama\") {\n if (!ollama) {\n throw new Error(\"single-indexer: embeddings='ollama' requires an OllamaClient.\");\n }\n // 5. Active model lookup + dimension contract check. The full indexer\n // upserts the model row; here we require it to already exist (caller\n // should run a full index first if not).\n const am = vault.db.models.getActive();\n if (!am) {\n throw new Error(\n `single-indexer: no active embedding model in DB. ` +\n `Run a full index first to register \"${embeddingModel}\".`,\n );\n }\n if (am.name !== embeddingModel) {\n throw new Error(\n `single-indexer: active model \"${am.name}\" does not match ` +\n `requested \"${embeddingModel}\". Run a full re-index to switch models.`,\n );\n }\n activeModel = am;\n }\n\n // 6. Upsert note row + aliases.\n const upsert = vault.db.notes.upsertByPath({\n path: parsed.relativePath,\n content: parsed.content,\n frontmatter: parsed.frontmatter ? JSON.stringify(parsed.frontmatter) : null,\n title: parsed.title,\n hash: parsed.hash,\n bodyHash: parsed.bodyHash,\n mtime: parsed.mtime,\n wordCount: parsed.wordCount,\n });\n vault.db.aliases.setForNote(upsert.id, extractAliases(parsed.frontmatter));\n\n // 7. Wipe derived layer for this note. Sections FIRST — sections reference\n // chunks via chunk_id_first/last (no ON DELETE cascade), so deleting chunks\n // while sections still point at them trips a FOREIGN KEY constraint. (This\n // ordering matches the full indexer; single-indexer historically skipped\n // section maintenance — now fixed so live re-index keeps sections correct.)\n vault.db.sections.deleteByNote(upsert.id);\n vault.db.chunks.deleteByNote(upsert.id);\n vault.db.wikilinks.deleteByNote(upsert.id);\n // ── Phase 4 / 04-02 / GRA-04 / D-02 ──\n // Wipe typed edges; writeAllEdges below repopulates them via the\n // unified extractor (wikilink + mention + frontmatter-ref +\n // hyperlink) in one parse pass. The legacy wikilinks write keeps\n // running too per D-01 — single-indexer's `insertWikilinks` helper\n // still hits the v1 table for byte-stable backward compatibility.\n vault.db.edges.deleteByNote(upsert.id);\n\n // 8. Chunk + embed + persist.\n const chunks = chunkNote(parsed.indexedContent);\n\n if (chunks.length === 0) {\n insertWikilinks(vault, upsert.id, parsed.wikilinks);\n // ── Phase 4 / 04-02 / GRA-04 / D-02 ──\n // Empty-body branch still gets the full extractor pass: a note\n // with only frontmatter (e.g. a person stub with `owner:` /\n // `attendees:` arrays) can still emit frontmatter-ref edges.\n writeAllEdges(vault, upsert.id, parsed);\n return {\n status: \"indexed\",\n notePath: parsed.relativePath,\n noteId: upsert.id,\n chunksCreated: 0,\n isNew: upsert.isNew,\n };\n }\n\n const chunkIds = vault.db.chunks.insertBatch(\n upsert.id,\n chunks.map((c) => ({\n idx: c.idx,\n text: c.text,\n headingPath: c.headingPath,\n startOffset: c.startOffset,\n endOffset: c.endOffset,\n tokenCount: c.tokenCount,\n // Phase 5 / D-05: canonical chunk-fragment via the chunker helper\n // (single source of truth — see src/chunker/chunk-id.ts).\n chunkIdFragment: computeChunkIdFragment(c.text),\n })),\n );\n\n // Rebuild this note's sections (was previously skipped by the single-indexer,\n // so live-reindexed notes silently lost their section rows). Runs for BOTH\n // backends — sections power outline/search_sections/bundle and need no\n // embeddings. Defensive try/catch: one pathological note must not break the\n // watcher (mirrors the full indexer).\n try {\n buildSectionsForNote(vault, upsert.id, parsed.indexedContent, chunkIds);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `[single-indexer:${vault.config.name}] section build failed for ${parsed.relativePath}: ${message}\\n`,\n );\n }\n\n // Embed — Ollama path only. ContextFit vaults skip; the chunks + links +\n // edges persisted here power the SQLite-backed tools, and search runs via\n // the ContextFit engine (KB re-ingested by the watcher/write path).\n if (embedMode === \"ollama\") {\n const embedResult = await ollama!.embed({\n model: embeddingModel,\n texts: chunks.map((c) => c.text),\n });\n if (embedResult.dim !== activeModel!.dim) {\n throw new Error(\n `single-indexer: embedding dim ${embedResult.dim} does not match ` +\n `registered dim ${activeModel!.dim} for model \"${embeddingModel}\".`,\n );\n }\n\n vault.db.embeddings.insertBatch(\n chunkIds.map((chunkId, i) => ({\n chunkId,\n modelId: activeModel!.id,\n vector: embedResult.vectors[i]!,\n })),\n );\n\n // Phase 7c: keep the shadow index live. Only embed if the secondary model\n // is already registered (i.e. a full indexVault run has set it up). We\n // never register a new model from a single-note path — the dim probe is\n // a full-indexer responsibility.\n if (secondaryName) {\n const secondaryModel = vault.db.models.getByName(secondaryName);\n if (secondaryModel && secondaryModel.id !== activeModel!.id) {\n const secEmbed = await ollama!.embed({\n model: secondaryName,\n texts: chunks.map((c) => c.text),\n });\n if (secEmbed.dim !== secondaryModel.dim) {\n throw new Error(\n `single-indexer: shadow embedding dim ${secEmbed.dim} ` +\n `does not match registered dim ${secondaryModel.dim} for ` +\n `\"${secondaryName}\".`,\n );\n }\n vault.db.embeddings.insertBatch(\n chunkIds.map((chunkId, i) => ({\n chunkId,\n modelId: secondaryModel.id,\n vector: secEmbed.vectors[i]!,\n })),\n );\n }\n }\n }\n\n insertWikilinks(vault, upsert.id, parsed.wikilinks);\n // ── Phase 4 / 04-02 / GRA-04 / D-02 ──\n // Full re-embed branch — emit the typed-edge mix into `edges`.\n writeAllEdges(vault, upsert.id, parsed);\n\n return {\n status: \"indexed\",\n notePath: parsed.relativePath,\n noteId: upsert.id,\n chunksCreated: chunks.length,\n isNew: upsert.isNew,\n };\n}\n\n/**\n * Remove a note from the index (note row + cascade: chunks, embeddings,\n * wikilinks, aliases). Does NOT touch the file on disk.\n */\nexport function removeNote(\n vault: Vault,\n absolutePath: string,\n): { removed: boolean; notePath: string | null } {\n if (!isInsideVault(absolutePath, vault.config.path)) {\n return { removed: false, notePath: null };\n }\n const relativePath = toRelativePosix(absolutePath, vault.config.path);\n\n const existing = vault.db.notes.getByPath(relativePath);\n if (!existing) {\n return { removed: false, notePath: null };\n }\n vault.db.notes.deleteByPath(relativePath);\n return { removed: true, notePath: relativePath };\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// helpers\n// ───────────────────────────────────────────────────────────────────────────\n\nfunction emptyResult(status: \"outside_vault\" | \"missing\" | \"parse_error\"): IndexNoteResult {\n return {\n status,\n notePath: null,\n noteId: null,\n chunksCreated: 0,\n isNew: false,\n };\n}\n\nfunction isInsideVault(absolutePath: string, vaultRoot: string): boolean {\n const absResolved = path.resolve(absolutePath);\n const rootResolved = path.resolve(vaultRoot);\n const absPosix = absResolved.split(path.sep).join(\"/\");\n const rootPosix = rootResolved.split(path.sep).join(\"/\");\n const rootWithSep = rootPosix.endsWith(\"/\") ? rootPosix : `${rootPosix}/`;\n return absPosix === rootPosix || absPosix.startsWith(rootWithSep);\n}\n\nfunction toRelativePosix(absolutePath: string, vaultRoot: string): string {\n return path\n .relative(path.resolve(vaultRoot), path.resolve(absolutePath))\n .split(path.sep)\n .join(\"/\");\n}\n\nfunction isENOENT(err: unknown): boolean {\n return (\n typeof err === \"object\" &&\n err !== null &&\n \"code\" in err &&\n (err as { code: unknown }).code === \"ENOENT\"\n );\n}\n\n/**\n * Mirror of indexer.ts `insertWikilinks` — kept private to avoid widening\n * that module's public API. Single-indexer skips the second-pass resolution,\n * so unresolved targets remain broken until a full index runs.\n *\n * Phase 4 / 04-02: the dual-write into `edges` has moved into\n * `writeAllEdges` below so the wikilinks-edge resolution shares a\n * single `WikilinkResolver` instance with the mention + frontmatter-ref\n * extractors. This helper now writes ONLY the legacy `wikilinks`\n * table (D-01 byte-stability).\n */\nfunction insertWikilinks(vault: Vault, sourceNoteId: number, wikilinks: ParsedWikilink[]): void {\n if (wikilinks.length === 0) return;\n\n const resolver = new WikilinkResolver(vault);\n const inputs = wikilinks.map((wl) => {\n const target = resolver.resolve(wl.normalizedTarget);\n return {\n targetPath: wl.normalizedTarget,\n targetNoteId: target?.id ?? null,\n linkText: wl.alias,\n anchor: wl.anchor,\n lineNumber: wl.line,\n };\n });\n vault.db.wikilinks.insertBatch(sourceNoteId, inputs);\n}\n\n/**\n * Phase 4 / 04-02 / GRA-04 / D-02 — write all four edge types into\n * `vault.db.edges` via the unified extractor. Callers MUST have\n * already issued `vault.db.edges.deleteByNote(sourceNoteId)` so the\n * write is a clean replace; `INSERT OR IGNORE` + the UNIQUE index on\n * `(source_doc, target_doc, type, anchor)` makes re-extraction\n * idempotent in any case (Pattern C from PATTERNS.md).\n *\n * Constructs a single `WikilinkResolver` per call — the single-indexer\n * path indexes one note at a time, so cache amortization across notes\n * is not relevant. The full-index path (`indexer.ts`) uses a long-lived\n * resolver instead (see `firstPassResolver` there).\n */\nfunction writeAllEdges(vault: Vault, sourceNoteId: number, parsed: ParsedNote): void {\n const resolver = new WikilinkResolver(vault);\n const edges = extractAllEdges(vault, parsed, resolver);\n if (edges.length > 0) vault.db.edges.insertBatch(sourceNoteId, edges);\n}\n","/**\n * Catch-up scan: reconcile DB state with the vault's filesystem on demand.\n *\n * Used at server start before activating the file watcher. The watcher only\n * sees events from its `start()` onward — so anything edited while the server\n * was offline would silently drift. Catch-up does a cheap hash-based scan:\n *\n * - For every .md on disk: parse + hash → compare to DB.\n * - Hash unchanged → skip (no embeddings work).\n * - Hash changed or note absent → indexNote (full re-embed for this note).\n * - For every DB note whose path is no longer on disk → removeNote.\n *\n * Embeddings are only generated for the notes that actually changed.\n */\n\nimport { scanVault } from \"../adapters/source/obsidian-fs/scanner.js\";\nimport { parseNote } from \"../adapters/source/obsidian-fs/parser.js\";\nimport { indexNote, removeNote } from \"./single.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { OllamaClient } from \"../ollama/index.js\";\n\nexport interface CatchupOptions {\n vault: Vault;\n embeddingModel: string;\n /** Required for Ollama vaults; omitted for ContextFit vaults (ADR-008). */\n ollama?: OllamaClient;\n log?: (msg: string) => void;\n}\n\nexport interface CatchupResult {\n scanned: number;\n reindexed: number;\n removed: number;\n durationMs: number;\n}\n\nexport async function catchupVault(options: CatchupOptions): Promise<CatchupResult> {\n const started = Date.now();\n const log = options.log ?? (() => {});\n const { vault } = options;\n\n const files = await scanVault(vault.config.path, {\n excludeGlobs: vault.config.exclude_globs,\n });\n\n let reindexed = 0;\n const knownPaths = new Set<string>();\n // ADR-008: ContextFit vaults reconcile the SQLite layer without embeddings.\n const isContextFit = vault.config.backend === \"contextfit\";\n\n for (const file of files) {\n // Cheap path-relative computation — duplicates the reader's logic but\n // avoids a second filesystem hit.\n const parsed = await parseNote(file, vault.config.path).catch(() => null);\n if (!parsed) continue;\n knownPaths.add(parsed.relativePath);\n\n const dbRow = vault.db.notes.getByPath(parsed.relativePath);\n if (dbRow && dbRow.hash === parsed.hash) {\n continue;\n }\n\n const result = await indexNote({\n vault,\n absolutePath: file,\n embeddingModel: options.embeddingModel,\n ...(isContextFit ? { embeddings: \"none\" as const } : { ollama: options.ollama }),\n });\n if (result.status === \"indexed\") {\n reindexed++;\n log(`catch-up indexed ${parsed.relativePath} (${result.isNew ? \"new\" : \"updated\"})`);\n }\n }\n\n let removed = 0;\n for (const row of vault.db.notes.listAll()) {\n if (!knownPaths.has(row.path)) {\n const result = removeNote(vault, joinAbs(vault.config.path, row.path));\n if (result.removed) {\n removed++;\n log(`catch-up removed ${row.path}`);\n }\n }\n }\n\n // ADR-008: if a ContextFit vault changed during catch-up, rebuild its search\n // KB once so retrieval matches the reconciled SQLite layer. Issue #17: also\n // rebuild when a dirty flag was left behind (e.g. an ingest was skipped and\n // its holder crashed before the trailing pass) so a stranded flag is honored\n // at the latest on the next server start.\n if (isContextFit) {\n const cf = await import(\"../adapters/retrieval/contextfit/index.js\");\n const dirty = await (\n await import(\"../adapters/retrieval/contextfit/ingest-lock.js\")\n ).isIngestDirty(vault.config.name);\n if (reindexed > 0 || removed > 0 || dirty) {\n const r = await cf.indexVaultWithContextFit(vault.config, { onProgress: log });\n log(\n r.status === \"completed\"\n ? `catch-up: ContextFit KB rebuilt (${r.durationMs}ms)`\n : r.status === \"skipped\"\n ? `catch-up: ContextFit KB re-ingest already in progress; skipping`\n : `catch-up: ContextFit KB rebuild failed: ${r.error}`,\n );\n }\n }\n\n return {\n scanned: files.length,\n reindexed,\n removed,\n durationMs: Date.now() - started,\n };\n}\n\nfunction joinAbs(root: string, relative: string): string {\n // removeNote expects absolute. The simple join here mirrors scanVault's\n // output convention (POSIX slashes) and works on macOS/Linux; on Windows\n // single.ts's safeJoinInsideVault normalizes either way.\n if (root.endsWith(\"/\")) return `${root}${relative}`;\n return `${root}/${relative}`;\n}\n","/**\n * Shadow indexer (Phase 7c) — backfills embeddings for a secondary model\n * over chunks that already exist in the vault DB.\n *\n * Use case: a user runs vault-memory v0.6.x with model A. They want to test\n * model B's retrieval quality. Instead of destructively re-indexing (which\n * would break search while it runs), they kick off a shadow index:\n *\n * 1. Every chunk in the `chunks` table is embedded with model B.\n * 2. Vectors land in `embeddings_<dim_B>` next to the existing `embeddings_<dim_A>`.\n * 3. While running, model A stays active — search is uninterrupted.\n * 4. Once complete, `switch_active_model` flips the active flag atomically.\n *\n * Idempotent: a LEFT JOIN against the secondary dim's embeddings table\n * skips chunks that are already embedded. Safe to interrupt and resume.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { OllamaClient } from \"../ollama/index.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\nexport interface ShadowIndexOptions {\n vault: Vault;\n /** Secondary model name. Registered on demand if not yet in the DB. */\n model: string;\n ollama: OllamaClient;\n /** Embed batch size — capped at Ollama batch size in practice. Default 16. */\n batchSize?: number;\n log?: (msg: string) => void;\n}\n\nexport interface ShadowIndexResult {\n runId: string;\n modelId: number;\n modelName: string;\n dim: number;\n chunksTotal: number;\n chunksEmbedded: number;\n chunksSkipped: number;\n durationMs: number;\n}\n\ninterface PendingChunkRow {\n id: number;\n text: string;\n}\n\n/**\n * Backfill secondary embeddings for every chunk currently in the vault.\n * Skips chunks already embedded with this model (idempotent resume).\n *\n * Does NOT switch the active model. Use `switch_active_model` once the\n * caller has independently verified the shadow index is complete.\n */\nexport async function startShadowIndex(options: ShadowIndexOptions): Promise<ShadowIndexResult> {\n const { vault, model, ollama } = options;\n const log = options.log ?? (() => {});\n const batchSize = options.batchSize ?? 16;\n const runId = randomUUID();\n const started = Date.now();\n\n // 1. Probe Ollama for dim + existence. Fail fast if the model isn't pulled.\n if (!(await ollama.modelExists(model))) {\n throw new Error(`Shadow model \"${model}\" not found in Ollama. ` + `Run: ollama pull ${model}`);\n }\n const probe = await ollama.embed({ model, texts: [\"probe\"] });\n const dim = probe.dim;\n\n // 2. Register the model (active=false — primary stays active).\n const modelRow = vault.db.models.upsert({\n name: model,\n provider: \"ollama\",\n dim,\n active: false,\n });\n\n // The vec0 table for this model is created lazily by ensureTableForModel.\n vault.db.embeddings.ensureTableForModel(modelRow.id, dim);\n\n // 3. Audit run.\n vault.db.audit.startRun({\n runId,\n vaultName: vault.config.name,\n modelId: modelRow.id,\n trigger: \"shadow\",\n });\n\n // 4. Find chunks missing the shadow embedding.\n //\n // Phase 7e: each model owns its own vec0 table `embeddings_m<id>_d<dim>`.\n // The table name is interpolated from validated integers — safe.\n const embTable = `embeddings_m${modelRow.id}_d${dim}`;\n const pendingSql = `\n SELECT c.id AS id, c.text AS text\n FROM chunks c\n LEFT JOIN ${embTable} e ON e.chunk_id = c.id\n WHERE e.chunk_id IS NULL\n ORDER BY c.id\n `;\n const totalSql = `SELECT COUNT(*) AS c FROM chunks`;\n\n const pending = vault.db.handle.prepare<[], PendingChunkRow>(pendingSql).all();\n const totalRow = vault.db.handle.prepare<[], { c: number }>(totalSql).get();\n const chunksTotal = totalRow?.c ?? 0;\n const chunksSkipped = chunksTotal - pending.length;\n\n log(\n `shadow-index \"${model}\" (dim=${dim}): ${pending.length} pending, ` +\n `${chunksSkipped} already embedded`,\n );\n\n let chunksEmbedded = 0;\n try {\n for (let i = 0; i < pending.length; i += batchSize) {\n const batch = pending.slice(i, i + batchSize);\n const embedResp = await ollama.embed({\n model,\n texts: batch.map((c) => c.text),\n });\n if (embedResp.dim !== dim) {\n throw new Error(\n `Shadow embedding dim mismatch mid-run: expected ${dim}, ` +\n `got ${embedResp.dim} on batch starting chunk_id ${batch[0]?.id}`,\n );\n }\n vault.db.embeddings.insertBatch(\n batch.map((row, j) => ({\n chunkId: row.id,\n modelId: modelRow.id,\n vector: embedResp.vectors[j]!,\n })),\n );\n chunksEmbedded += batch.length;\n if (i % (batchSize * 8) === 0) {\n log(` ${chunksEmbedded}/${pending.length}…`);\n }\n }\n\n vault.db.audit.finishRun(runId, {\n notesIndexed: 0,\n chunksCreated: chunksEmbedded,\n notesUpdated: 0,\n notesDeleted: 0,\n });\n } catch (err) {\n const message = errorMessage(err);\n vault.db.audit.finishRun(runId, {\n notesIndexed: 0,\n chunksCreated: chunksEmbedded,\n notesUpdated: 0,\n notesDeleted: 0,\n error: message,\n });\n throw err;\n }\n\n return {\n runId,\n modelId: modelRow.id,\n modelName: model,\n dim,\n chunksTotal,\n chunksEmbedded,\n chunksSkipped,\n durationMs: Date.now() - started,\n };\n}\n\n/**\n * Inventory of all registered models in a vault with per-model\n * shadow-completeness data.\n */\nexport interface ModelInventoryEntry {\n id: number;\n name: string;\n provider: string;\n dim: number;\n active: boolean;\n embedded_chunk_count: number;\n}\n\nexport function listModels(vault: Vault): ModelInventoryEntry[] {\n const rows = vault.db.models.listAll();\n return rows.map((m) => {\n // Phase 7e: each model owns its own vec0 table — chunk count is COUNT(*).\n let count = 0;\n try {\n vault.db.embeddings.ensureTableForModel(m.id, m.dim);\n const row = vault.db.handle\n .prepare<[], { c: number }>(`SELECT COUNT(*) AS c FROM embeddings_m${m.id}_d${m.dim}`)\n .get();\n count = row?.c ?? 0;\n } catch {\n // Defensive: if the table somehow can't be queried (e.g. corrupt\n // schema), surface 0 rather than crashing the listing call.\n count = 0;\n }\n return {\n id: m.id,\n name: m.name,\n provider: m.provider,\n dim: m.dim,\n active: m.active === 1,\n embedded_chunk_count: count,\n };\n });\n}\n\nexport interface SwitchResult {\n ok: boolean;\n reason?: \"unknown_model\" | \"incomplete\" | \"already_active\";\n missing_chunks?: number;\n switched_from?: string;\n switched_to?: string;\n}\n\n/**\n * Atomically switch the active embedding model for a vault. Refuses to\n * switch if any chunk in the vault is missing an embedding for the target\n * model — partial switches would leave the new active model unable to\n * answer queries for those chunks.\n */\nexport function switchActiveModel(vault: Vault, targetModelName: string): SwitchResult {\n const target = vault.db.models.getByName(targetModelName);\n if (!target) {\n return { ok: false, reason: \"unknown_model\" };\n }\n\n const current = vault.db.models.getActive();\n if (current && current.id === target.id) {\n return {\n ok: false,\n reason: \"already_active\",\n switched_from: current.name,\n switched_to: target.name,\n };\n }\n\n // Completeness check: every chunk must have an embedding for the target\n // model's vec0 table. Phase 7e: per-model table eliminates the model_id\n // join condition — presence in the table is sufficient.\n vault.db.embeddings.ensureTableForModel(target.id, target.dim);\n const embTable = `embeddings_m${target.id}_d${target.dim}`;\n const missingRow = vault.db.handle\n .prepare<[], { c: number }>(\n `SELECT COUNT(*) AS c\n FROM chunks c\n LEFT JOIN ${embTable} e ON e.chunk_id = c.id\n WHERE e.chunk_id IS NULL`,\n )\n .get();\n const missing = missingRow?.c ?? 0;\n\n if (missing > 0) {\n return {\n ok: false,\n reason: \"incomplete\",\n missing_chunks: missing,\n switched_from: current?.name,\n switched_to: target.name,\n };\n }\n\n vault.db.models.setActive(target.id);\n return {\n ok: true,\n switched_from: current?.name,\n switched_to: target.name,\n };\n}\n","/**\n * vacuum_embeddings — drop orphaned embedding rows.\n *\n * Over time, a vault DB can accumulate embedding rows whose `chunk_id` no\n * longer exists in the `chunks` table. Sources of orphans:\n * - Pre-v0.7.0 schemas where note-deletion did not always cascade through\n * the derived layer (since fixed by Migration 003 + 7c plumbing).\n * - Manual SQL repair, partial migrations, interrupted shadow runs.\n * - The v0.6.x → v0.7.x migration kept legacy `embeddings` rows in\n * `embeddings_<dim>` even when the chunks had been deleted upstream.\n *\n * The vault we used for the v0.7.2 eval still carried 1541 such orphans\n * (qwen3 had 3088 embeddings, only 1547 live chunks → ~50% orphaned).\n *\n * Behavior:\n * - Walks every per-model embeddings table (`embeddings_m<id>_d<dim>`).\n * - Deletes rows whose `chunk_id` is not present in `chunks`.\n * - Returns a per-model count of (kept, removed, table) for the audit log.\n * - Never deletes rows in `chunks` itself; the raw layer is untouched.\n */\n\nimport type { Vault } from \"../vault/index.js\";\n\nexport interface VacuumPerModel {\n model_id: number;\n model_name: string;\n dim: number;\n table: string;\n removed: number;\n kept: number;\n}\n\nexport interface VacuumResult {\n total_removed: number;\n per_model: VacuumPerModel[];\n duration_ms: number;\n}\n\nexport function vacuumEmbeddings(vault: Vault): VacuumResult {\n const startedAt = Date.now();\n const models = vault.db.models.listAll();\n const per_model: VacuumPerModel[] = [];\n let total_removed = 0;\n\n // One transaction across all per-model tables so the result is all-or-nothing.\n vault.db.transaction(() => {\n for (const m of models) {\n // Materialise the table if it does not exist yet — `models` can list a\n // model that has not yet been embedded (e.g. a freshly registered\n // shadow model). In that case we'd skip cleanly with kept=0/removed=0.\n vault.db.embeddings.ensureTableForModel(m.id, m.dim);\n const table = `embeddings_m${m.id}_d${m.dim}`;\n\n const beforeRow = vault.db.handle\n .prepare<[], { c: number }>(`SELECT COUNT(*) AS c FROM ${table}`)\n .get();\n const before = beforeRow?.c ?? 0;\n\n // Two-step delete because sqlite-vec virtual tables do not support\n // `DELETE ... WHERE chunk_id NOT IN (subquery)` cleanly across all\n // builds. Collect the orphan IDs first, then delete by primary key.\n const orphans = vault.db.handle\n .prepare<[], { chunk_id: number }>(\n `SELECT chunk_id FROM ${table}\n WHERE chunk_id NOT IN (SELECT id FROM chunks)`,\n )\n .all();\n\n if (orphans.length > 0) {\n const stmt = vault.db.handle.prepare(`DELETE FROM ${table} WHERE chunk_id = ?`);\n for (const o of orphans) {\n stmt.run(BigInt(o.chunk_id));\n }\n }\n\n const removed = orphans.length;\n const kept = before - removed;\n total_removed += removed;\n per_model.push({\n model_id: m.id,\n model_name: m.name,\n dim: m.dim,\n table,\n removed,\n kept,\n });\n }\n });\n\n return {\n total_removed,\n per_model,\n duration_ms: Date.now() - startedAt,\n };\n}\n","export { indexVault, extractAliases, resolveWikilinkTarget } from \"./indexer.js\";\nexport type { IndexerOptions, IndexRunResult } from \"./indexer.js\";\nexport { indexNote, removeNote } from \"./single.js\";\nexport type { IndexNoteOptions, IndexNoteResult } from \"./single.js\";\nexport { catchupVault } from \"./catchup.js\";\nexport type { CatchupOptions, CatchupResult } from \"./catchup.js\";\nexport { startShadowIndex, listModels, switchActiveModel } from \"./shadow.js\";\nexport type {\n ShadowIndexOptions,\n ShadowIndexResult,\n ModelInventoryEntry,\n SwitchResult,\n} from \"./shadow.js\";\nexport { vacuumEmbeddings } from \"./vacuum.js\";\nexport type { VacuumResult, VacuumPerModel } from \"./vacuum.js\";\n","/**\n * Atomic filesystem helpers for the write module.\n *\n * Atomicity strategy: write to a sibling tmp file in the same directory as\n * the target, then `rename` it on top. On POSIX file systems, rename within\n * the same directory is atomic — this prevents readers (including Obsidian)\n * from ever observing a partially-written file.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport { dirname, isAbsolute, resolve, sep } from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\n\nexport class OutsideVaultError extends Error {\n constructor(relativePath: string, vaultRoot: string) {\n super(\n `Refused to operate on path outside vault: \"${relativePath}\" (vault root: \"${vaultRoot}\")`,\n );\n this.name = \"OutsideVaultError\";\n }\n}\n\n/**\n * Write `content` to `absPath` atomically. Creates parent directories if needed.\n *\n * The tmp file lives in the SAME directory as the target so the final rename\n * stays on the same filesystem (and therefore atomic).\n */\nexport async function atomicWriteFile(absPath: string, content: string): Promise<void> {\n if (!isAbsolute(absPath)) {\n throw new Error(`atomicWriteFile requires an absolute path: ${absPath}`);\n }\n const parent = dirname(absPath);\n await fs.mkdir(parent, { recursive: true });\n\n const suffix = randomBytes(8).toString(\"hex\");\n const tmpPath = `${absPath}.tmp.${suffix}`;\n try {\n await fs.writeFile(tmpPath, content, \"utf-8\");\n await fs.rename(tmpPath, absPath);\n } catch (err) {\n // Best-effort cleanup of the tmp file. Ignore failure of cleanup itself.\n try {\n await fs.unlink(tmpPath);\n } catch {\n /* swallow */\n }\n throw err;\n }\n}\n\n/**\n * Resolve `relativePath` against `vaultRoot` and verify the result stays\n * within the vault. Throws `OutsideVaultError` on any escape attempt\n * (e.g. `../../etc/passwd`, absolute paths, string-level traversal).\n *\n * This function ALSO follows symlinks via `fs.realpath` to defeat\n * symlink-escape attacks: if a directory inside the vault is a symlink\n * pointing outside (e.g. `Netzwerk/escape -> /etc`), any path beneath\n * it is rejected even though the joined string looks vault-internal.\n *\n * Realpath is applied to:\n * - the vault root, and\n * - the deepest existing ancestor of the target (since the target\n * itself may not exist yet for a create/write).\n *\n * Async because it touches the filesystem.\n */\nexport async function safeJoinInsideVault(\n vaultRoot: string,\n relativePath: string,\n): Promise<string> {\n if (typeof relativePath !== \"string\" || relativePath.length === 0) {\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n // Disallow absolute inputs outright — caller must pass vault-relative.\n if (isAbsolute(relativePath)) {\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n const root = resolve(vaultRoot);\n const target = resolve(root, relativePath);\n\n // String-level prefix check first — catches `../` traversal cheaply.\n const rootWithSep = root.endsWith(sep) ? root : root + sep;\n if (target !== root && !target.startsWith(rootWithSep)) {\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n if (target === root) {\n // The vault root itself is not a writable note path.\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n\n // Realpath both sides to defeat symlink-escape. The target may not exist\n // yet (creating a new note), so walk up to the deepest existing ancestor\n // and realpath that. Anything not yet on disk is by definition a fresh\n // path that cannot itself be a symlink.\n let realRoot: string;\n try {\n realRoot = await fs.realpath(root);\n } catch {\n // If the vault root itself cannot be resolved, refuse — we cannot\n // guarantee any boundary check is meaningful.\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n\n const realTarget = await resolveExistingAncestor(target);\n const realRootWithSep = realRoot.endsWith(sep) ? realRoot : realRoot + sep;\n if (realTarget !== realRoot && !realTarget.startsWith(realRootWithSep)) {\n throw new OutsideVaultError(relativePath, vaultRoot);\n }\n\n return target;\n}\n\n/**\n * Resolve the deepest existing ancestor of `absPath` via realpath, then\n * re-attach any non-existent trailing segments. This handles the common\n * case of writing a brand-new file whose parent (or grandparent) exists.\n */\nasync function resolveExistingAncestor(absPath: string): Promise<string> {\n let current = absPath;\n const trailing: string[] = [];\n // Walk up until realpath succeeds or we hit the filesystem root.\n // eslint-disable-next-line no-constant-condition\n while (true) {\n try {\n const real = await fs.realpath(current);\n return trailing.length === 0 ? real : resolve(real, ...trailing.reverse());\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code !== \"ENOENT\" && code !== \"ENOTDIR\") {\n throw err;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Reached filesystem root without ever resolving — fall back to\n // the original string. The caller's prefix check has already\n // verified string-level containment.\n return absPath;\n }\n // Track the non-existent leaf to re-attach after realpath.\n trailing.push(current.slice(parent.length + 1));\n current = parent;\n }\n }\n}\n","/**\n * write/write.ts — atomic vault writes with hash-based concurrency control.\n *\n * Both `writeNote` and `deleteNote` keep the file system and the vault DB\n * in sync: the file is written/removed first, then the DB is updated and\n * an audit row is inserted. If the on-disk hash does not match the\n * caller-provided `expectedHash`, the operation aborts BEFORE touching\n * either FS or DB and returns a structured conflict.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport matter from \"gray-matter\";\nimport type { Vault } from \"../../../vault/index.js\";\nimport { computeNoteHash, computeBodyHash } from \"../../source/obsidian-fs/hash.js\";\nimport { extractAliases } from \"../../../indexer/index.js\";\nimport { atomicWriteFile, safeJoinInsideVault } from \"./fs.js\";\nimport { formatDocId } from \"../../registry.js\";\nimport type { MemorySinkRegistry } from \"../../../memory/registry.js\";\n\nexport interface WriteSuccess {\n ok: true;\n newHash: string;\n noteId: number;\n /** True if a brand-new file/note was created. */\n created: boolean;\n}\n\nexport interface WriteConflict {\n ok: false;\n reason: \"hash_mismatch\" | \"permission_denied\" | \"sink_write_blocked\";\n currentHash?: string;\n currentContent?: string;\n message: string;\n /** Phase 2 envelope (sink_write_blocked). */\n sinkName?: string;\n /** Phase 2 envelope — actionable next-step hint. */\n suggestion?: string;\n}\n\nexport type WriteResult = WriteSuccess | WriteConflict;\n\nexport interface WriteNoteInput {\n vault: Vault;\n /** Vault-relative path with forward slashes, ending in .md */\n relativePath: string;\n /** Markdown body WITHOUT frontmatter delimiters. */\n content: string;\n /** Optional frontmatter object — will be serialized to YAML by the function. */\n frontmatter?: Record<string, unknown> | null;\n /**\n * Concurrency token. If the file's current hash on disk differs from\n * this, return a conflict instead of writing. If omitted: write\n * unconditionally only when the file does NOT exist yet; otherwise\n * return a conflict.\n */\n expectedHash?: string;\n /**\n * Audit-log attribution. Per D-02, this is captured by the\n * ObsidianFsDelivery facade from the MCP InitializeRequest.params.clientInfo\n * at server bootstrap (falling back to \"unknown\"). Per-call overrides via\n * the opts.clientId path beat the constructor default.\n *\n * Note: the v1 hardcoded `DEFAULT_CLIENT_ID` (a fixed client name) was\n * removed in plan 01-04 (the C-1 leak). Internal writeNote/deleteNote\n * now require the caller to supply the value explicitly via the facade.\n */\n clientId?: string;\n /**\n * Called exactly once, immediately before the filesystem write. Used by\n * the MCP server to mark the path on the watcher's SuppressionSet so the\n * watcher ignores the fs event triggered by our own atomic rename.\n *\n * If the operation aborts (hash conflict, permission denied) this hook\n * is NOT called — so a failed write cannot accidentally suppress a real\n * external edit that happens shortly after.\n */\n onBeforeFsWrite?: () => void;\n /**\n * Plan 02-03b: optional defense-in-depth entry-point Guard.\n *\n * When supplied AND the resolved target lands inside a registered\n * MemorySink (per `registry.findSinkContaining(docId)`), the write\n * is refused with `{ok:false, reason:\"sink_write_blocked\"}` BEFORE\n * any filesystem read. The authoritative chokepoint still lives at\n * the DeliveryAdapter (`ObsidianFsDelivery.preflight()` per ADR-002\n * §DeliveryAdapter); this v1 entry-point Guard is defense-in-depth\n * so that callers bypassing the facade hit a structured refusal\n * rather than silently dumping into a memory folder.\n *\n * When omitted (Phase 1 unit-test fixtures + any caller that has not\n * yet been threaded with the registry), the guard is silently\n * skipped — Phase 1 behavior is byte-for-byte preserved. The MCP\n * server bootstrap in Plan 02-03b always passes the registry, so\n * production callers are always guarded.\n */\n registry?: MemorySinkRegistry;\n /**\n * Plan 02-06 (MEM-08): whether this write is routed under a configured\n * `MemorySink`. The DeliveryAdapter facade derives the flag from\n * `opts.sink !== undefined` and forwards it; v1 `writeNote` callers\n * (e.g. `update_frontmatter`, raw `write_note`) leave it `false`. Stored\n * on the resulting `write_audit` row so `audit_log` can distinguish\n * agent-written memory documents from regular user writes.\n */\n isMemorySinkWrite?: boolean;\n}\n\nexport interface DeleteNoteInput {\n vault: Vault;\n relativePath: string;\n /** Required for delete — caller must prove they read the current state. */\n expectedHash: string;\n clientId?: string;\n /** See WriteNoteInput.onBeforeFsWrite. Called just before fs.unlink. */\n onBeforeFsWrite?: () => void;\n /** See WriteNoteInput.registry — same defense-in-depth Guard semantics\n * apply to deleteNote. The suggestion text references `supersede` per\n * Plan 02-03 truth: hard deletion of memory documents is forbidden in\n * v2.0.0; agents retire memory documents via supersede. */\n registry?: MemorySinkRegistry;\n /**\n * Plan 02-06 (MEM-08): whether this delete is routed under a configured\n * `MemorySink`. v2.0.0 forbids hard-deletion inside a sink (the\n * DeliveryAdapter facade and the entry-point Guard reject sink-resolved\n * paths) — this flag exists for symmetry with `WriteNoteInput` and for\n * audit-row stamping at any future delete path that does land inside a\n * sink (e.g. an admin-tier delete that bypasses the Guard). Defaults to\n * `false`; pre-Plan-02-06 call sites need no change.\n */\n isMemorySinkWrite?: boolean;\n}\n\n/**\n * Neutral fallback when no client_id is supplied at any level. Per D-02\n * + RESEARCH Pitfall 4: MCP InitializeRequest.params.clientInfo is\n * OPTIONAL in the spec, so older or non-conformant clients may not send\n * a name. This fallback is observably truthful (the previous hardcoded\n * default lied for any client that wasn't the assumed one).\n */\nconst UNKNOWN_CLIENT_ID = \"unknown\";\n\nfunction permissionDenied(vaultName: string): WriteConflict {\n return {\n ok: false,\n reason: \"permission_denied\",\n message: `Vault \"${vaultName}\" is read-only (write_enabled=false in config.toml)`,\n };\n}\n\n/**\n * Compute the canonical content-hash the way the reader does. Delegates to\n * `computeNoteHash` from reader/hash.ts (canonical, key-sorted JSON).\n */\nfunction computeHash(content: string, frontmatter: Record<string, unknown> | null): string {\n return computeNoteHash(content, frontmatter);\n}\n\nfunction extractTitle(content: string, relativePath: string): string {\n for (const line of content.split(\"\\n\")) {\n const m = /^#\\s+(.+?)\\s*$/.exec(line);\n if (m !== null && m[1] !== undefined) return m[1].trim();\n }\n return basename(relativePath, \".md\");\n}\n\nfunction countWords(content: string): number {\n if (content.length === 0) return 0;\n return content.split(/\\s+/).filter((s) => s.length > 0).length;\n}\n\nasync function readExistingFile(absPath: string): Promise<{\n raw: string;\n content: string;\n frontmatter: Record<string, unknown> | null;\n hash: string;\n} | null> {\n let raw: string;\n try {\n raw = await fs.readFile(absPath, \"utf-8\");\n } catch (err) {\n if (\n typeof err === \"object\" &&\n err !== null &&\n (err as NodeJS.ErrnoException).code === \"ENOENT\"\n ) {\n return null;\n }\n throw err;\n }\n const parsed = matter(raw);\n const fmData = parsed.data as Record<string, unknown> | undefined;\n const frontmatter: Record<string, unknown> | null =\n fmData !== undefined && Object.keys(fmData).length > 0 ? fmData : null;\n const hash = computeHash(parsed.content, frontmatter);\n return { raw, content: parsed.content, frontmatter, hash };\n}\n\nexport async function writeNote(input: WriteNoteInput): Promise<WriteResult> {\n const { vault, relativePath, content, registry } = input;\n const frontmatter = input.frontmatter ?? null;\n const clientId = input.clientId ?? UNKNOWN_CLIENT_ID;\n\n // Plan 02-03b — defense-in-depth entry-point Guard. Runs BEFORE the\n // write_enabled check and BEFORE any FS read. When the optional registry\n // is supplied (production path) AND the target lands inside a registered\n // sink, refuse with the structured `sink_write_blocked` envelope.\n if (registry) {\n const docId = formatDocId(\"obsidian-fs\", vault.config.name, relativePath);\n const sink = registry.findSinkContaining(docId);\n if (sink !== null) {\n return {\n ok: false,\n reason: \"sink_write_blocked\",\n sinkName: sink.name,\n message:\n `Target ${relativePath} resolves into MemorySink \"${sink.name}\". ` +\n `v1 write_note is refused for memory-sink targets.`,\n suggestion: `Use record_observation for sink '${sink.name}'.`,\n };\n }\n }\n\n if (vault.config.write_enabled !== true) {\n return permissionDenied(vault.config.name);\n }\n\n // Throws OutsideVaultError on traversal — intentional: callers should not\n // be able to construct invalid paths and silently get a \"conflict\".\n const absPath = await safeJoinInsideVault(vault.config.path, relativePath);\n\n const existing = await readExistingFile(absPath);\n const created = existing === null;\n\n if (existing !== null) {\n if (input.expectedHash === undefined) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n currentHash: existing.hash,\n currentContent: existing.raw,\n message:\n `File \"${relativePath}\" already exists. ` +\n `Pass expectedHash=\"${existing.hash}\" to overwrite intentionally.`,\n };\n }\n if (input.expectedHash !== existing.hash) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n currentHash: existing.hash,\n currentContent: existing.raw,\n message:\n `Hash mismatch for \"${relativePath}\": ` +\n `expected ${input.expectedHash}, got ${existing.hash}. ` +\n `The file was modified externally — re-read and retry.`,\n };\n }\n }\n\n // Serialize new content. gray-matter.stringify writes a `---` block only\n // when the data object is non-empty; we mirror that behavior explicitly.\n // Issue #14: pass lineWidth: -1 so js-yaml does NOT fold long string values\n // into a `>-` block scalar (its default is lineWidth: 80). Obsidian's\n // Properties editor mishandles block scalars; single-line values round-trip\n // cleanly. gray-matter forwards this option verbatim to js-yaml's dump()\n // (see gray-matter/lib/stringify.js), but @types/gray-matter's option type\n // doesn't list the js-yaml keys — hence the narrow cast.\n const yamlDumpOptions = { lineWidth: -1 } as Parameters<typeof matter.stringify>[2];\n const fileText =\n frontmatter !== null && Object.keys(frontmatter).length > 0\n ? matter.stringify(content, frontmatter, yamlDumpOptions)\n : content;\n\n input.onBeforeFsWrite?.();\n await atomicWriteFile(absPath, fileText);\n\n // Re-parse from disk to compute the canonical post-write hash. This also\n // protects us against any normalization gray-matter may apply on stringify.\n const written = await readExistingFile(absPath);\n if (written === null) {\n // Should never happen — we just wrote it.\n throw new Error(`Internal error: file disappeared after write: ${relativePath}`);\n }\n const stat = await fs.stat(absPath);\n\n const previousNote = vault.db.notes.getByPath(relativePath);\n const previousHash = previousNote?.hash ?? null;\n const title = extractTitle(written.content, relativePath);\n\n // Codex MEDIUM-1: wrap the three DB writes in a single transaction so they\n // either all land or none do. If the transaction throws, roll back the FS\n // write to the pre-write state — either by unlinking a freshly created\n // file, or restoring the previous on-disk content.\n let upsertId: number;\n try {\n upsertId = vault.db.transaction(() => {\n const up = vault.db.notes.upsertByPath({\n path: relativePath,\n content: written.content,\n frontmatter: written.frontmatter ? JSON.stringify(written.frontmatter) : null,\n title,\n hash: written.hash,\n bodyHash: computeBodyHash(written.content),\n mtime: Math.floor(stat.mtimeMs),\n wordCount: countWords(written.content),\n });\n vault.db.aliases.setForNote(up.id, extractAliases(written.frontmatter));\n vault.db.audit.recordWrite({\n noteId: up.id,\n op: created ? \"create\" : \"update\",\n previousHash,\n newHash: written.hash,\n expectedHash: input.expectedHash ?? null,\n clientId,\n diffSummary: null,\n // Plan 02-06 (MEM-08): stamp the audit row with the sink-routing\n // flag the facade derived from `opts.sink !== undefined`. v1 call\n // sites that haven't been threaded leave the field undefined →\n // recordWrite defaults to 0 (non-memory).\n isMemorySinkWrite: input.isMemorySinkWrite ?? false,\n });\n return up.id;\n });\n } catch (dbErr) {\n // Suppress the next watcher event from our rollback write/unlink too —\n // the watcher would otherwise re-index the rolled-back state and undo\n // the rollback's intent.\n input.onBeforeFsWrite?.();\n try {\n if (created) {\n await fs.unlink(absPath);\n } else if (existing !== null) {\n await atomicWriteFile(absPath, existing.raw);\n }\n } catch {\n // Rollback failed — leave the divergence visible by re-throwing the\n // original DB error. Catch-up reconciliation will eventually heal it.\n }\n throw dbErr;\n }\n\n return {\n ok: true,\n newHash: written.hash,\n noteId: upsertId,\n created,\n };\n}\n\nexport async function deleteNote(input: DeleteNoteInput): Promise<WriteResult> {\n const { vault, relativePath, expectedHash, registry } = input;\n const clientId = input.clientId ?? UNKNOWN_CLIENT_ID;\n\n // Plan 02-03b — defense-in-depth entry-point Guard. Same shape as\n // writeNote, but the suggestion text directs the caller to `supersede`\n // (hard deletion of memory documents is forbidden in v2.0.0 per Plan\n // 02-03 truth).\n if (registry) {\n const docId = formatDocId(\"obsidian-fs\", vault.config.name, relativePath);\n const sink = registry.findSinkContaining(docId);\n if (sink !== null) {\n return {\n ok: false,\n reason: \"sink_write_blocked\",\n sinkName: sink.name,\n message:\n `Target ${relativePath} resolves into MemorySink \"${sink.name}\". ` +\n `Hard deletion of memory documents is not permitted in v2.0.0.`,\n suggestion:\n \"Use supersede to retire memory documents. Hard deletion is not yet supported in v2.0.0.\",\n };\n }\n }\n\n if (vault.config.write_enabled !== true) {\n return permissionDenied(vault.config.name);\n }\n\n const absPath = await safeJoinInsideVault(vault.config.path, relativePath);\n\n const existing = await readExistingFile(absPath);\n if (existing === null) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n message: `File \"${relativePath}\" does not exist — nothing to delete.`,\n };\n }\n if (existing.hash !== expectedHash) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n currentHash: existing.hash,\n currentContent: existing.raw,\n message:\n `Hash mismatch for \"${relativePath}\": ` +\n `expected ${expectedHash}, got ${existing.hash}. ` +\n `The file was modified externally — re-read and retry.`,\n };\n }\n\n const previousNote = vault.db.notes.getByPath(relativePath);\n const previousHash = previousNote?.hash ?? existing.hash;\n\n input.onBeforeFsWrite?.();\n await fs.unlink(absPath);\n\n // Remove from DB. If the note was never indexed (e.g. file appeared and\n // was deleted between indexer runs) we still record a synthetic audit\n // entry — but only when we have a noteId. Without one, the audit row\n // can't be tied to a (now-gone) note.\n if (previousNote !== null) {\n // Since migration 003 the FKs do the right thing:\n // - chunks.note_id, note_aliases.note_id → ON DELETE CASCADE (auto-clear)\n // - wikilinks.source_note → ON DELETE CASCADE (outgoing links gone)\n // - wikilinks.target_note → ON DELETE SET NULL (incoming links become\n // broken; find_broken_links surfaces them correctly)\n // - write_audit.note_id → ON DELETE SET NULL (the audit row survives;\n // getAuditLog already resolves notePath=null for a vanished note)\n //\n // Wrap delete + audit insert in one transaction so a crash leaves\n // either both or neither.\n vault.db.transaction(() => {\n vault.db.audit.recordWrite({\n noteId: previousNote.id,\n op: \"delete\",\n previousHash,\n newHash: null,\n expectedHash,\n clientId,\n diffSummary: null,\n // Plan 02-06 (MEM-08): symmetric stamp on delete. Production\n // deletes targeting a sink are refused by the entry-point Guard\n // and the facade — so this flag is normally `false` on delete\n // rows. Pass-through retained for symmetry / future admin paths.\n isMemorySinkWrite: input.isMemorySinkWrite ?? false,\n });\n vault.db.notes.deleteByPath(relativePath);\n });\n return {\n ok: true,\n newHash: existing.hash,\n noteId: previousNote.id,\n created: false,\n };\n }\n\n return {\n ok: true,\n newHash: existing.hash,\n noteId: 0,\n created: false,\n };\n}\n","/**\n * `validateAgentWrite` — the SINGLE Phase 2 chokepoint per\n * ADR-002 §DeliveryAdapter and ADR-004 §Resolution.\n *\n * Adapters (`ObsidianFsDelivery`, `StubDelivery`, and any future\n * delivery adapter) call this pure function at the top of `write()`,\n * `update()`, and `delete()` BEFORE touching the backing store. The\n * function returns `null` on pass and a structured `GuardFailure` on\n * refusal — the adapter then returns the failure as a `WriteConflict`.\n *\n * Guard ordering (per the TSDoc on `WriteConflict`):\n *\n * 1. Guard B (cheap): inspect `properties.source` against sink\n * membership.\n * - `source === \"agent\"` AND `sink === null`\n * ⇒ `agent_write_outside_sink`.\n * - `source` set AND `source !== \"agent\"` AND `sink !== null`\n * ⇒ `non_agent_write_inside_sink`.\n * Pass-through cases:\n * - `source === undefined` and `sink === null` ⇒ ordinary\n * (non-memory) v1 write — pass.\n * - `source === \"user\"` and `sink === null` ⇒ user writing\n * outside any sink — pass.\n * - `source === \"agent\"` and `sink !== null` ⇒ proceed to\n * Guard A.\n *\n * 2. Guard A: when the target lands in a sink AND a contract is\n * bound, run `contract.propertiesSchema.safeParse(doc.properties)`.\n * Map the FIRST issue to one of `missing_provenance`,\n * `invalid_provenance`, or `supersede_mismatch` (cross-field).\n *\n * The sentinel check (`sentinel_missing`) and the delete-into-sink\n * refusal (`sink_write_blocked`) are adapter-level concerns — they\n * live inside the adapter's `write` / `delete` and do NOT round-trip\n * through this validator. The validator covers exactly the five\n * `GuardFailure` codes.\n *\n * Zod 4 issue-shape notes (verified against zod@4.4.3 at probe time):\n * - `code === \"invalid_type\"` is emitted for both genuine type\n * errors AND for missing-required keys (because Zod sees\n * `undefined` at that path). We disambiguate \"missing\" from\n * \"wrong type\" by inspecting the actual value at the path: if it\n * is `undefined`, it's `missing_provenance`; otherwise\n * `invalid_provenance`.\n * - `code === \"invalid_value\"` is emitted for enum mismatch.\n * - `code === \"invalid_format\"` is emitted for `.datetime()` etc.\n * - `code === \"custom\"` is emitted by `.superRefine` cross-field\n * rules in `DEFAULT_MEMORY_V1` (status=superseded invariants).\n *\n * No filesystem, no path joining, no gray-matter, no node:* — pure\n * data-in, data-out. Re-usable by both delivery adapters and the v1\n * entry-point Guards landing in Plan 02-03b.\n */\n\nimport type { DocId, Document, MemorySink } from \"../types.js\";\nimport type { WriteConflict } from \"../adapters/delivery/types.js\";\nimport type { MemoryContract } from \"./contract/index.js\";\n\n/**\n * The subset of `WriteConflict` codes this validator can emit.\n * Adapter-only codes (`sentinel_missing`, `sink_write_blocked`) are\n * deliberately excluded — they are filesystem/registry-level concerns.\n *\n * Implemented as `WriteConflict & { reason: <subset> }` rather than\n * `Extract<...>` because `WriteConflict` is a single interface (not a\n * union), so `Extract` would distribute incorrectly and produce\n * `never`. The intersection narrows the `reason` field to the subset\n * we actually emit.\n */\nexport type GuardFailure = WriteConflict & {\n reason:\n | \"missing_provenance\"\n | \"invalid_provenance\"\n | \"supersede_mismatch\"\n | \"agent_write_outside_sink\"\n | \"non_agent_write_inside_sink\";\n};\n\n/**\n * Safe key read on `Document.properties`. Returns `undefined` if\n * `props` is missing, the key is missing, or the property bag itself\n * is non-object.\n */\nfunction getAt(props: Record<string, unknown> | undefined, key: string): unknown {\n if (!props || typeof props !== \"object\") return undefined;\n return props[key];\n}\n\n/**\n * Run Guards B and A against a write target.\n *\n * @param id Document identity (carried in diagnostics).\n * @param doc Partial document being written / updated. The validator\n * inspects `doc.properties` only; blocks/title/etc. are ignored.\n * @param sink Resolved sink the target lands in, or `null` if the\n * target is outside every registered sink.\n * @param contract Contract bound to `sink.contractName`, or `null` if\n * `sink` is `null` (in which case Guard A is skipped).\n * @returns `null` on pass; a `GuardFailure` describing the first\n * detected violation otherwise.\n */\nexport function validateAgentWrite(\n id: DocId,\n doc: Partial<Document>,\n sink: MemorySink | null,\n contract: MemoryContract | null,\n): GuardFailure | null {\n const props = doc.properties as Record<string, unknown> | undefined;\n const source = getAt(props, \"source\");\n\n // ── Guard B (cheap; runs first) ──────────────────────────────────────────\n if (source === \"agent\" && sink === null) {\n return {\n ok: false,\n reason: \"agent_write_outside_sink\",\n message:\n `source:\"agent\" writes are only permitted under a configured ` +\n `MemorySink. Target ${id} does not resolve into any sink.`,\n suggestion:\n \"Use record_observation for memory writes; or change source to 'user' / 'imported'.\",\n };\n }\n if (source !== undefined && source !== \"agent\" && sink !== null) {\n return {\n ok: false,\n reason: \"non_agent_write_inside_sink\",\n sinkName: sink.name,\n message:\n `source:\"${String(source)}\" writes are not permitted into ` + `MemorySink \"${sink.name}\".`,\n suggestion:\n \"Memory sinks accept source:'agent' writes only. User notes belong in the surrounding vault.\",\n };\n }\n\n // ── Guard A (only when target lands in a sink AND a contract is bound) ──\n if (sink !== null && contract !== null) {\n const result = contract.propertiesSchema.safeParse(props ?? {});\n if (!result.success) {\n const issue = result.error.issues[0];\n if (!issue) return null;\n const pathHead = issue.path[0];\n const key = typeof pathHead === \"string\" ? pathHead : undefined;\n\n // Cross-field rules in DEFAULT_MEMORY_V1 emit `code === \"custom\"`\n // with the path pointing at `superseded_by` or `superseded_reason`.\n // Map either path to `supersede_mismatch`.\n if (key === \"superseded_reason\" || key === \"superseded_by\") {\n return {\n ok: false,\n reason: \"supersede_mismatch\",\n sinkName: sink.name,\n ...(key !== undefined ? { key } : {}),\n message: `Cross-field rule failed at \"${key}\": ${issue.message}`,\n suggestion:\n \"When status is 'superseded', set both superseded_by (DocId) and superseded_reason (non-empty string).\",\n };\n }\n\n // \"Missing required\" disambiguation: in Zod 4 a missing required\n // key surfaces with `code === \"invalid_type\"` for plain-string\n // schemas (received undefined) OR with `code === \"invalid_value\"`\n // for enum schemas (no enum option matches undefined). Either\n // way, the canonical signal that the key is MISSING (rather than\n // present-but-wrong-shape) is that the actual value at the path\n // is `undefined`.\n const observed = key !== undefined ? getAt(props, key) : undefined;\n if (observed === undefined) {\n return {\n ok: false,\n reason: \"missing_provenance\",\n sinkName: sink.name,\n ...(key !== undefined ? { key } : {}),\n message:\n `Required property \"${key ?? \"(unknown)\"}\" is missing for writes ` +\n `into MemorySink \"${sink.name}\".`,\n suggestion:\n `Set properties.${key ?? \"<key>\"} before retrying. ` +\n `See contract \"${contract.name}\" required keys: ${contract.requiredKeys.join(\", \")}.`,\n };\n }\n\n return {\n ok: false,\n reason: \"invalid_provenance\",\n sinkName: sink.name,\n ...(key !== undefined ? { key } : {}),\n observedValue: observed,\n message: `Property \"${key ?? \"(unknown)\"}\" failed validation: ${issue.message}`,\n suggestion: `See contract \"${contract.name}\" for valid values.`,\n };\n }\n }\n\n return null;\n}\n","/**\n * Hardcoded baseline `MemoryContract` for `default-memory-v1`.\n *\n * Mirrors the normative spec in `docs/v2/MEMORY_CONTRACT.md` and the\n * (post-amendment) `default-memory-v1` YAML example in\n * `docs/v2/adr/004-memory-sink-handles.md`. The seven required keys\n * (`source`, `confidence`, `evidence`, `status`, `observed_at`,\n * `superseded_by`, `type`) plus the optional `superseded_reason` and\n * the cross-field invariant (`status === \"superseded\"` ⇒\n * `superseded_reason` non-empty AND `superseded_by` non-null) are\n * baked in as a single Zod `.superRefine`-wrapped object schema.\n *\n * `passthrough()` keeps contract-extras (`expires_at`, `tags`, etc.)\n * from being silently dropped — D-02 (CONTEXT.md) escape hatch for\n * future contract-allowed fields.\n *\n * Phase 2 ships this hardcoded baseline so the validator works without\n * a disk read; the YAML loader (`./loader.ts`) handles named contracts\n * that ship in `_contracts/memory/<name>.yaml`.\n */\n\nimport { z } from \"zod\";\nimport type { MemoryContract } from \"./types.js\";\n\nconst requiredKeys = [\n \"source\",\n \"confidence\",\n \"evidence\",\n \"status\",\n \"observed_at\",\n \"superseded_by\",\n \"type\",\n] as const;\n\nconst baseShape = z\n .object({\n source: z.enum([\"agent\", \"user\", \"imported\"]),\n confidence: z.enum([\"direct\", \"inferred\", \"uncertain\"]),\n evidence: z.array(z.string()),\n status: z.enum([\"active\", \"superseded\", \"archived\"]).default(\"active\"),\n observed_at: z.string().datetime({ offset: true }),\n superseded_by: z.string().nullable().default(null),\n type: z.string().min(1),\n superseded_reason: z.string().optional(),\n })\n // D-02: unknown contract-extra keys pass through.\n .passthrough()\n // Cross-field invariant: when status is \"superseded\", BOTH\n // `superseded_by` (non-null DocId) AND `superseded_reason` (non-empty\n // string) are required. Other statuses leave both fields unconstrained\n // beyond their base types.\n .superRefine((data, ctx) => {\n if (data.status === \"superseded\") {\n if (data.superseded_by === null || data.superseded_by === undefined) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"superseded_by\"],\n message: \"Required (non-null DocId) when status is 'superseded'\",\n });\n }\n if (typeof data.superseded_reason !== \"string\" || data.superseded_reason.length === 0) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"superseded_reason\"],\n message: \"Required (non-empty string) when status is 'superseded'\",\n });\n }\n }\n });\n\nexport const DEFAULT_MEMORY_V1: MemoryContract = {\n name: \"default-memory-v1\",\n version: \"1.0\",\n propertiesSchema: baseShape,\n requiredKeys,\n naming: {\n strategy: \"date-slug\",\n pattern: \"{observed_at:YYYY-MM-DD}-{slug}.md\",\n },\n};\n","/**\n * Hardcoded baseline `MemoryContract` for `default-brief-v1`.\n *\n * Phase 5 (ADR-005 §\"New default-brief-v1 contract\"): briefs have a\n * distinct lifecycle from observations — they can be `\"stale\"` (a state\n * `default-memory-v1` does not allow). Rather than widen the Phase 2\n * contract's status enum (scope creep + mis-types non-brief documents),\n * we register a separate contract bound to the `_memory/_briefs/` sink.\n *\n * Mirrors the shape of `default-memory-v1` (`./default-v1.ts`) and\n * extends:\n * - Status enum: `active | stale | superseded | archived` (adds\n * `\"stale\"`).\n * - Required keys: the base seven plus `target, purpose,\n * compiled_from, compiled_at, source_hashes`.\n * - Cross-field invariant: when `status === \"stale\"`,\n * `source_hashes` MUST be present (the daemon needs hashes to\n * drive recompute). Inherits the `status === \"superseded\"`\n * invariant from `default-v1`.\n *\n * `passthrough()` keeps contract-extras (changed_sources, max_tokens,\n * etc.) from being silently dropped — D-02 escape hatch.\n *\n * Naming strategy is `caller-provided` because `compile_brief`\n * computes the timestamped slug itself per D-12 (the slug-timestamp\n * algorithm is not a `MemoryContract.naming.strategy` enum member;\n * see ADR-005 §\"Decision: Recompile chain auto-supersede\").\n *\n * Pure module. No fs / gray-matter / chokidar / path imports.\n */\n\nimport { z } from \"zod\";\nimport type { MemoryContract } from \"./types.js\";\n\nconst requiredKeys = [\n // Base seven (mirrors default-v1).\n \"source\",\n \"confidence\",\n \"evidence\",\n \"status\",\n \"observed_at\",\n \"superseded_by\",\n \"type\",\n // Brief-specific keys per ADR-005 / MEMORY_CONTRACT.md brief shape.\n \"target\",\n \"purpose\",\n \"compiled_from\",\n \"compiled_at\",\n \"source_hashes\",\n] as const;\n\nconst baseShape = z\n .object({\n // ── Base shape inherited from default-v1 ────────────────────────\n source: z.enum([\"agent\", \"user\", \"imported\"]),\n confidence: z.enum([\"direct\", \"inferred\", \"uncertain\"]),\n evidence: z.array(z.string()),\n // ── Status enum WIDENED for briefs: + \"stale\" ──────────────────\n status: z.enum([\"active\", \"stale\", \"superseded\", \"archived\"]).default(\"active\"),\n observed_at: z.string().datetime({ offset: true }),\n superseded_by: z.string().nullable().default(null),\n type: z.string().min(1),\n superseded_reason: z.string().optional(),\n\n // ── Brief-specific properties (D-11 brief shape) ───────────────\n target: z.string().min(1),\n /**\n * Brief purpose — free text but bounded at 500 chars so\n * `list_briefs` stays scannable. Lower bound `min(1)` matches\n * BRF-03 \"no empty purpose\".\n */\n purpose: z.string().min(1).max(500),\n /** DocId list of all sources the brief was compiled from. */\n compiled_from: z.array(z.string()).min(1),\n /** ISO-8601 datetime with offset (mirrors observed_at). */\n compiled_at: z.string().datetime({ offset: true }),\n /**\n * Record<ChunkId, BriefSourceHash> — staleness contract. The map\n * key is the public ChunkId (`<DocId>#chunk-<7-hex>`); the value\n * is `\"sha256:<hex>\"`. Marked optional at the type level because\n * the cross-field invariant below only REQUIRES it on stale; the\n * validator still rejects `status: \"stale\"` writes that omit it.\n */\n source_hashes: z.record(z.string(), z.string()).optional(),\n /**\n * Daemon-computed list of source DocIds whose hashes have\n * diverged. Populated when `status` flips to `\"stale\"`.\n */\n changed_sources: z.array(z.string()).optional(),\n })\n // D-02: unknown contract-extra keys pass through.\n .passthrough()\n // Cross-field invariants — inherits the `superseded` requirements\n // from default-v1 AND adds the brief-specific `stale` requirement.\n .superRefine((data, ctx) => {\n // Inherited from default-v1: when status is \"superseded\",\n // superseded_by MUST be non-null AND superseded_reason MUST be a\n // non-empty string. The recompile path (D-12) sets\n // `reason: \"recompiled\"` automatically; manual supersedes carry\n // a caller-supplied reason.\n if (data.status === \"superseded\") {\n if (data.superseded_by === null || data.superseded_by === undefined) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"superseded_by\"],\n message: \"Required (non-null DocId) when status is 'superseded'\",\n });\n }\n if (typeof data.superseded_reason !== \"string\" || data.superseded_reason.length === 0) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"superseded_reason\"],\n message: \"Required (non-empty string) when status is 'superseded'\",\n });\n }\n }\n // Brief-specific: when status is \"stale\", source_hashes MUST be\n // present (the daemon needs the recorded hashes to know which\n // sources diverged — without them recompile cannot be targeted).\n if (data.status === \"stale\") {\n if (!data.source_hashes) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"source_hashes\"],\n message: \"Required when status is 'stale' (daemon needs hashes to recompute)\",\n });\n }\n }\n });\n\nexport const DEFAULT_BRIEF_V1: MemoryContract = {\n name: \"default-brief-v1\",\n version: \"1.0\",\n propertiesSchema: baseShape,\n requiredKeys,\n // D-12 timestamped slug (`{target}--{compiled_at:YYYYMMDDTHHmm}.md`)\n // is computed by compile_brief itself — the caller (the brief layer)\n // hands the DeliveryAdapter a fully-formed DocId. The MemoryContract\n // naming strategy enum (`caller-provided | date-slug |\n // adapter-assigned`) does not include `slug-timestamp` as a value;\n // `caller-provided` is the closest match and signals \"the\n // implementation mints the DocId before write\".\n naming: {\n strategy: \"caller-provided\",\n },\n};\n","/**\n * Sink-aware path helpers for obsidian-fs.\n *\n * Per ADR-002 I-3 the `node:path` module is licensed only inside\n * `src/adapters/delivery/obsidian-fs/` (plus `src/cli.ts` and\n * `src/server.ts` for legacy bootstrap reasons). This file is the SOLE\n * licensed `path.join` site for sink/vault path resolution in Phase 2.\n *\n * Helpers split by consumer category (CR-03 / Plan 02-11):\n *\n * FS-bound (OS-native absolute) — for values that flow into `fs.*` calls:\n * - `joinVaultPath(vaultRoot, relPath)` — thin wrapper over `path.join`.\n * - `pathInSink(vaultAbsolutePath, sink, relativeSubpath?)` — absolute\n * path inside a memory sink.\n *\n * Comparison-bound (forward-slash, vault-relative) — for values that flow\n * into DocId-resource comparisons, SQL `LIKE '<prefix>%'` lookups against\n * `notes.path`, or `MemorySinkRegistry.findSinkContaining` matching:\n * - `joinVaultPathPosix(...segments)` — `path.posix.join` with defensive\n * backslash normalization.\n * - `vaultRelativeInSink(sink, relativeSubpath?)` — forward-slash form of\n * `<sink.resolveToRelativePath><relativeSubpath>`.\n *\n * Why split? `pathInSink`'s return value goes into `fs.access` / `fs.readFile`\n * / `fs.writeFile`, where OS-native separators are the convention. The\n * comparison-bound helpers must emit forward-slash on every OS, because the\n * DocId resource (governed by `DOC_ID_PATTERN` in `src/adapters/registry.ts`)\n * and `notes.path` storage (forward-slash by indexer convention) are both\n * forward-slash regardless of `process.platform`. On Windows, `path.join`\n * emits backslashes — silently breaking Guard B / `findSinkContaining` /\n * `lastMemoryWriteAtForPathPrefix` SQL lookups. The split is the seam-level\n * fix; FS-bound helpers retain OS-native semantics, comparison-bound helpers\n * lock forward-slash.\n *\n * All helpers are pure synchronous string ops — no `fs` calls, no I/O.\n */\n\nimport path from \"node:path\";\n\n/**\n * Join a vault-absolute path with a vault-relative subpath. Thin wrapper\n * over `path.join` — OS-native separators. Use for paths that flow into\n * `fs.*` calls (read / write / stat / access). Use this instead of\n * importing `node:path` so the seam-preservation CI grep stays happy.\n *\n * FS-bound — DO NOT use the return value for comparison against a DocId\n * resource or for SQL `LIKE` prefix lookups; use `joinVaultPathPosix` or\n * `vaultRelativeInSink` for those callers.\n */\nexport function joinVaultPath(vaultRoot: string, relPath: string): string {\n return path.join(vaultRoot, relPath);\n}\n\n/**\n * Structural shape required from a sink — only the `resolveToRelativePath`\n * field is consulted. Declared as a local interface (not a `Pick<MemorySink, ...>`)\n * so Task 0 can land independently of the broader `MemorySink` widening\n * in Task 1; once both are in place the broader `MemorySink` interface\n * matches this shape structurally and callers pass the full sink record.\n */\ninterface SinkLike {\n resolveToRelativePath: string;\n}\n\n/**\n * Compute an absolute path inside a memory sink. The caller supplies the\n * vault-absolute path (resolved through `VaultManager`); the sink record\n * contributes its vault-relative folder; the optional `relativeSubpath`\n * is appended inside.\n *\n * Example:\n * pathInSink(\"/v/atlas\", { resolveToRelativePath: \"_memory/\" }, \"obs/foo.md\")\n * → \"/v/atlas/_memory/obs/foo.md\"\n *\n * FS-bound — OS-native separators. Use the return value for `fs.*` calls\n * only. For DocId-resource comparisons or SQL `LIKE` prefix lookups, see\n * `vaultRelativeInSink`.\n */\nexport function pathInSink(\n vaultAbsolutePath: string,\n sink: SinkLike,\n relativeSubpath = \"\",\n): string {\n return path.join(vaultAbsolutePath, sink.resolveToRelativePath, relativeSubpath);\n}\n\n/**\n * Defensive: convert any backslash to forward-slash. Inputs are normally\n * byte-clean (the sink-handle parser at `src/memory/sink.ts` refuses\n * backslashes inside segments), but `relativeSubpath` and other callsites\n * may originate from caller-controlled inputs — normalize so the\n * forward-slash invariant always holds on output.\n */\nfunction normalizeToForwardSlash(s: string): string {\n return s.includes(\"\\\\\") ? s.replace(/\\\\/g, \"/\") : s;\n}\n\n/**\n * Vault-relative POSIX join — emits forward-slash regardless of\n * `process.platform`. Use for ANY value that will be compared against a\n * DocId resource, used as a SQL `LIKE` prefix against `notes.path`, or\n * threaded into `MemorySinkRegistry.findSinkContaining` lookups.\n *\n * On POSIX this is functionally equivalent to `path.join` minus the\n * leading vault root; on Windows it differs because `path.join` would\n * have emitted backslashes that downstream forward-slash comparisons\n * miss (CR-03 — silent Guard B no-op on Windows).\n *\n * Caller-supplied backslashes in any segment are normalized to forward-\n * slash defensively, so the output invariant holds even if a future\n * caller smuggles a backslash in.\n *\n * Comparison-bound — DO NOT pass the return value to `fs.*` calls; use\n * `joinVaultPath` or `pathInSink` for FS callers.\n */\nexport function joinVaultPathPosix(...segments: string[]): string {\n return path.posix.join(...segments.map(normalizeToForwardSlash));\n}\n\n/**\n * Forward-slash form of a path INSIDE a sink, relative to the vault root.\n * Used for any caller that compares against a DocId resource (always\n * forward-slash by `DOC_ID_PATTERN` invariant in\n * `src/adapters/registry.ts`) or feeds a SQL `LIKE '<prefix>%'` lookup\n * against `notes.path` (forward-slash by indexer convention).\n *\n * Round-trip property: for any `(vault, sink, subpath)` triple,\n * `vaultRelativeInSink(sink, subpath)` is byte-equal with the `resource`\n * portion of `decomposeDocId(formatDocId(\"obsidian-fs\", vault, rel))`.\n *\n * Edge cases:\n * - `relativeSubpath = \"\"` (default) returns the sink folder with its\n * trailing slash preserved (e.g. `\"_memory/\"`). This matches the\n * `findSinkContaining` policy where `sink.resolveToRelativePath`\n * includes its trailing slash so prefix matches respect folder\n * boundaries (`_memory/` matches `_memory/foo.md` but NOT\n * `_memory-staging/foo.md`).\n * - Caller-supplied backslashes in `relativeSubpath` are normalized to\n * forward-slash before joining.\n *\n * Comparison-bound — DO NOT pass the return value to `fs.*` calls; use\n * `pathInSink` for FS callers.\n */\nexport function vaultRelativeInSink(sink: SinkLike, relativeSubpath = \"\"): string {\n if (relativeSubpath === \"\") return normalizeToForwardSlash(sink.resolveToRelativePath);\n return joinVaultPathPosix(sink.resolveToRelativePath, relativeSubpath);\n}\n","/**\n * Read a memory-contract YAML file from disk.\n *\n * The disk read lives here (under `src/adapters/delivery/obsidian-fs/`)\n * because ADR-002 I-2 confines `node:fs` to the licensed adapter\n * directories. The pure contract logic (`src/memory/contract/`) calls\n * this helper through the `loader.ts` indirection so it remains\n * filesystem-ignorant.\n *\n * Path resolution uses `joinVaultPath` from this same directory's\n * `path.ts` so the seam-preservation CI grep stays happy.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { joinVaultPath } from \"./path.js\";\n\n/** Marker error: contract YAML file does not exist at the resolved path. */\nexport class ContractYamlNotFoundError extends Error {\n override readonly name = \"ContractYamlNotFoundError\";\n constructor(\n public readonly path: string,\n message?: string,\n ) {\n super(message ?? `Contract YAML not found at ${path}`);\n }\n}\n\n/**\n * Read `<vaultPath>/_contracts/memory/<contractName>.yaml` as a UTF-8\n * string. Throws `ContractYamlNotFoundError` on ENOENT (so the caller\n * can distinguish \"no file\" from \"file present but malformed\").\n *\n * Returns both the resolved absolute path (for diagnostics) and the\n * raw text contents.\n */\nexport async function readContractYaml(\n vaultPath: string,\n contractName: string,\n): Promise<{ path: string; text: string }> {\n const yamlPath = joinVaultPath(vaultPath, `_contracts/memory/${contractName}.yaml`);\n try {\n const text = await readFile(yamlPath, \"utf-8\");\n return { path: yamlPath, text };\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n throw new ContractYamlNotFoundError(yamlPath);\n }\n throw err;\n }\n}\n","/**\n * Zod schema for the `_contracts/memory/<name>.yaml` file format.\n *\n * The YAML on disk declares which property keys a `MemoryContract`\n * requires (with their allowed enum values, types, and defaults). The\n * loader (`./loader.ts`) reads the file, parses it via `yaml@^2.9.x`,\n * validates the parsed object against `MemoryContractYamlSchema`, then\n * walks the validated tree to BUILD a Zod `z.object(...)` schema for\n * validating `Document.properties` payloads at write time.\n *\n * The two-phase pipeline (validate-the-contract-shape, then\n * build-the-property-validator) keeps the contract grammar\n * declaratively validated by Zod itself — no hand-rolled walker.\n */\n\nimport { z } from \"zod\";\n\n/**\n * A single property rule. `type` is the field's Zod-mapped value type;\n * `allowed` is an optional enum constraint; `default` is a literal\n * default; `items` is the per-element rule for arrays; `min_length`\n * applies to strings or arrays.\n */\nexport const PropertyRuleSchema = z.object({\n type: z.enum([\"string\", \"datetime\", \"array\", \"doc_id\", \"number\", \"boolean\", \"reference\", \"date\"]),\n allowed: z.array(z.string()).optional(),\n default: z.unknown().optional(),\n items: z.object({ type: z.string() }).optional(),\n min_length: z.number().optional(),\n /** When true, the property accepts `null` as a sentinel value (in\n * addition to whatever `type` says). Used for required-but-null-by-\n * default properties like `superseded_by` on active observations. */\n nullable: z.boolean().optional(),\n});\n\nexport type PropertyRule = z.infer<typeof PropertyRuleSchema>;\n\n/**\n * A cross-field rule. `when` is a simple boolean expression on\n * properties (e.g. `status == 'superseded'`); `require` is a\n * comma-separated or `&&`-joined list of keys that MUST be present and\n * non-empty when `when` evaluates true.\n *\n * Phase 2 ships a hardcoded interpretation for the only currently\n * required rule (status=superseded → superseded_by + superseded_reason\n * both non-empty); the schema accepts the declarative form so future\n * contracts (Phase 5+) can add their own without code changes.\n */\nexport const CrossFieldRuleSchema = z.object({\n when: z.string(),\n require: z.string(),\n});\n\nexport type CrossFieldRule = z.infer<typeof CrossFieldRuleSchema>;\n\n/**\n * Top-level contract shape. Mirrors the YAML in\n * `_contracts/memory/default-memory-v1.yaml`.\n */\nexport const MemoryContractYamlSchema = z.object({\n name: z.string().min(1),\n version: z.string().default(\"1.0\"),\n required_properties: z.record(z.string(), PropertyRuleSchema),\n optional_properties: z.record(z.string(), PropertyRuleSchema).default({}),\n cross_field_rules: z.array(CrossFieldRuleSchema).default([]),\n naming: z.object({\n strategy: z.enum([\"caller-provided\", \"date-slug\", \"adapter-assigned\"]),\n pattern: z.string().optional(),\n }),\n});\n\nexport type MemoryContractYaml = z.infer<typeof MemoryContractYamlSchema>;\n","/**\n * YAML → Zod-validated → `MemoryContract` pipeline.\n *\n * The disk read is delegated to\n * `src/adapters/delivery/obsidian-fs/contract-yaml-read.ts` so this\n * module remains free of `node:fs` / `node:path` imports (ADR-002 I-2\n * confines those to the licensed adapter directory).\n *\n * Cache: contracts are cached by name on first successful load. The\n * cache key is the contract `name` (not the file path), so a contract\n * with the same name in different vaults would conflict — in Phase 2\n * this is fine because contracts are server-process global; Phase 5/6\n * may need to introduce per-vault scoping.\n *\n * Public symbols are re-exported from `./index.ts`.\n */\n\nimport { parse as parseYaml } from \"yaml\";\nimport { z, type ZodType } from \"zod\";\nimport {\n readContractYaml,\n ContractYamlNotFoundError,\n} from \"../../adapters/delivery/obsidian-fs/contract-yaml-read.js\";\nimport { MemoryContractYamlSchema, type MemoryContractYaml, type PropertyRule } from \"./schema.js\";\nimport type { MemoryContract } from \"./types.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public errors\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class MemoryContractNotFoundError extends Error {\n override readonly name = \"MemoryContractNotFoundError\";\n}\n\nexport class MemoryContractInvalidError extends Error {\n override readonly name = \"MemoryContractInvalidError\";\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Module-level cache (process lifetime)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst contractCache = new Map<string, MemoryContract>();\n\n/** Test-only: drop the cache so a `beforeEach` can re-seed contracts. */\nexport function __clearContractCache(): void {\n contractCache.clear();\n}\n\n/** Internal: insert a contract into the cache by name. Used by `index.ts`. */\nexport function __cacheContract(name: string, contract: MemoryContract): void {\n contractCache.set(name, contract);\n}\n\n/** Internal: read a contract from the cache by name. Returns `undefined`. */\nexport function __getCachedContract(name: string): MemoryContract | undefined {\n return contractCache.get(name);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Schema builder — converts a validated MemoryContractYaml into a Zod\n// schema for validating `Document.properties` payloads at write time.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Map a YAML `PropertyRule` to a Zod schema for a single property\n * value. The mapping is intentionally narrow — Phase 2 supports the\n * types listed in `PropertyRuleSchema` and nothing more. Future\n * contracts that need new types must extend `schema.ts` first.\n *\n * `key` is threaded in so fail-closed diagnostics (WR-01, WR-02) can\n * name the offending property in their error messages.\n */\nfunction ruleToZod(rule: PropertyRule, key: string): ZodType {\n let schema: ZodType;\n switch (rule.type) {\n case \"string\":\n schema = rule.min_length !== undefined ? z.string().min(rule.min_length) : z.string();\n break;\n case \"datetime\":\n case \"date\":\n schema = z.string().datetime({ offset: true });\n break;\n case \"array\": {\n // WR-01: Honor `items.type`. The shipped default-memory-v1\n // contract uses `items: { type: reference }` for the `evidence`\n // array, so `reference` (and its alias `doc_id`) is accepted and\n // mapped to `z.string()` at the element level — references are\n // structurally strings at the Zod layer; DocId parsing happens\n // separately when callers need branded values. `string` and\n // `number` are also supported. Any other element type (including\n // `date`, `datetime`, `boolean`, nested `array`) is rejected at\n // load time so contract authors get a fail-loud signal.\n //\n // When `items` is omitted entirely the default is `string` to\n // preserve the pre-WR-01 behavior on legacy contracts.\n const itemType = rule.items?.type ?? \"string\";\n switch (itemType) {\n case \"string\":\n schema = z.array(z.string());\n break;\n case \"number\":\n schema = z.array(z.number());\n break;\n case \"reference\":\n case \"doc_id\":\n schema = z.array(z.string());\n break;\n default:\n throw new MemoryContractInvalidError(\n `Property \"${key}\" has unsupported items.type \"${itemType}\". ` +\n `Phase 2 supports array items of type 'string', 'number', or 'reference'.`,\n );\n }\n break;\n }\n case \"reference\":\n case \"doc_id\":\n // Reference / doc_id is structurally a string at the Zod level;\n // the validator performs DocId parsing separately when callers\n // need branded values.\n schema = z.string();\n break;\n case \"number\":\n schema = z.number();\n break;\n case \"boolean\":\n schema = z.boolean();\n break;\n default:\n // The schema validator already constrained `rule.type` to the\n // enum above, so this branch is unreachable at runtime. The\n // exhaustive check helps the TypeScript compiler.\n schema = z.unknown();\n break;\n }\n if (rule.allowed && rule.allowed.length > 0) {\n // WR-02: `allowed` is declared as `z.array(z.string())` in\n // schema.ts — it is string-only by design. Silently overriding a\n // non-string declared type with a string enum produces semantic\n // type drift, so reject the combination at load time with a\n // diagnostic naming the offending key and declared type.\n if (rule.type !== \"string\") {\n throw new MemoryContractInvalidError(\n `Property \"${key}\" declares type \"${rule.type}\" with allowed=[...]. ` +\n `'allowed' is string-only — either declare type:'string' or remove 'allowed'.`,\n );\n }\n // `z.enum` requires a non-empty tuple, which the YAML schema does\n // not enforce at parse time, so we guard with a length check above.\n schema = z.enum(rule.allowed as [string, ...string[]]);\n }\n if (rule.nullable) {\n schema = schema.nullable();\n }\n if (rule.default !== undefined) {\n schema = schema.default(rule.default);\n }\n return schema;\n}\n\n/**\n * Build the `propertiesSchema` Zod schema from a validated YAML\n * contract. Required keys are added as required object members;\n * optional keys are wrapped in `.optional()`; cross-field rules are\n * encoded via `.superRefine()`.\n */\nfunction buildPropertiesSchema(yaml: MemoryContractYaml): ZodType {\n const shape: Record<string, ZodType> = {};\n for (const [key, rule] of Object.entries(yaml.required_properties)) {\n shape[key] = ruleToZod(rule, key);\n }\n for (const [key, rule] of Object.entries(yaml.optional_properties)) {\n shape[key] = ruleToZod(rule, key).optional();\n }\n let obj: ZodType = z.object(shape).passthrough();\n\n if (yaml.cross_field_rules.length > 0) {\n // WR-03: Validate every `when` expression eagerly at load time so\n // unsupported shapes (typos, `!=`, `=`, double-quoted values,\n // multi-clause) surface as a `MemoryContractInvalidError` instead\n // of being silently dropped at runtime. The Phase 2 DSL supports a\n // single declarative form: `<key> == '<value>'` (single-quoted\n // value, `==` operator).\n const WHEN_RE = /^([A-Za-z_][A-Za-z0-9_]*)\\s*==\\s*'([^']+)'$/;\n for (const rule of yaml.cross_field_rules) {\n if (!WHEN_RE.test(rule.when)) {\n throw new MemoryContractInvalidError(\n `Cross-field rule has unsupported 'when' expression: ${JSON.stringify(rule.when)}. ` +\n `Phase 2 supports a single form: \\`<key> == '<value>'\\` (single-quoted value, '==' operator). ` +\n `Rule: ${JSON.stringify(rule)}`,\n );\n }\n }\n\n obj = (obj as z.ZodObject<Record<string, ZodType>>).superRefine((data, ctx) => {\n for (const rule of yaml.cross_field_rules) {\n // `require` is `<key1> && <key2>` or single key. The\n // load-time check above guarantees `when` matches the regex,\n // so the exec below cannot fail — but we keep the defensive\n // `continue` to satisfy `noUncheckedIndexedAccess`.\n const whenMatch = WHEN_RE.exec(rule.when);\n if (!whenMatch) continue;\n const [, whenKey, whenValue] = whenMatch;\n if (whenKey === undefined || whenValue === undefined) continue;\n if ((data as Record<string, unknown>)[whenKey] !== whenValue) continue;\n const requiredKeys = rule.require\n .split(/&&|,/)\n .map((k) => k.trim())\n .filter(Boolean);\n for (const key of requiredKeys) {\n const value = (data as Record<string, unknown>)[key];\n if (value === undefined || value === null || value === \"\") {\n ctx.addIssue({\n code: \"custom\",\n path: [key],\n message: `Required when ${whenKey} == '${whenValue}'`,\n });\n }\n }\n }\n });\n }\n return obj;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public loader\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Load a `MemoryContract` from `<vaultPath>/_contracts/memory/<name>.yaml`.\n * Cached on success; re-loading the same name returns the cached\n * instance (referential equality holds).\n *\n * Errors:\n * - `MemoryContractNotFoundError` — file does not exist.\n * - `MemoryContractInvalidError` — file exists but cannot be parsed\n * or fails Zod validation. The error message includes the file\n * path for diagnostics.\n */\nexport async function loadContractFromDisk(\n name: string,\n vaultPath: string,\n): Promise<MemoryContract> {\n const cached = contractCache.get(name);\n if (cached) return cached;\n\n let yamlPath: string;\n let text: string;\n try {\n const read = await readContractYaml(vaultPath, name);\n yamlPath = read.path;\n text = read.text;\n } catch (err) {\n if (err instanceof ContractYamlNotFoundError) {\n throw new MemoryContractNotFoundError(`Memory contract \"${name}\" not found at ${err.path}`);\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = parseYaml(text);\n } catch (err) {\n throw new MemoryContractInvalidError(\n `Failed to parse YAML at ${yamlPath}: ${(err as Error).message}`,\n );\n }\n\n let validated: MemoryContractYaml;\n try {\n validated = MemoryContractYamlSchema.parse(parsed);\n } catch (err) {\n throw new MemoryContractInvalidError(\n `Contract at ${yamlPath} failed validation: ${(err as Error).message}`,\n );\n }\n\n const propertiesSchema = buildPropertiesSchema(validated);\n const contract: MemoryContract = {\n name: validated.name,\n version: validated.version,\n propertiesSchema,\n requiredKeys: Object.keys(validated.required_properties),\n naming: validated.naming,\n };\n contractCache.set(name, contract);\n // ALSO cache under the contract's declared `name` field (which may\n // differ from the file-stem `name` parameter — e.g. the shipped\n // `default-memory-v1` YAML always self-declares as\n // `default-memory-v1` regardless of the file name used to load it).\n if (validated.name !== name) {\n contractCache.set(validated.name, contract);\n }\n return contract;\n}\n","/**\n * Public surface for the `MemoryContract` subsystem.\n *\n * Phase 2 ships:\n * - `DEFAULT_MEMORY_V1` — hardcoded baseline matching MEMORY_CONTRACT.md.\n * - `getContract(name)` — synchronous lookup from the in-process\n * cache; returns the baseline for `\"default-memory-v1\"`, or any\n * previously-`loadContractFromDisk`-ed contract; throws otherwise.\n * - `loadContractFromDisk(name, vaultPath)` — async YAML loader.\n * - `MemoryContract` type.\n * - `MemoryContractNotFoundError` / `MemoryContractInvalidError`\n * classes for `instanceof` checks.\n *\n * Phase 5+ may add per-vault scoping, mtime-based cache invalidation,\n * or a higher-level \"contracts directory\" loader; this surface stays\n * stable until then.\n */\n\nimport { DEFAULT_MEMORY_V1 } from \"./default-v1.js\";\nimport { DEFAULT_BRIEF_V1 } from \"./default-brief-v1.js\";\nimport {\n __cacheContract,\n __getCachedContract,\n loadContractFromDisk,\n MemoryContractInvalidError,\n MemoryContractNotFoundError,\n} from \"./loader.js\";\nimport type { MemoryContract } from \"./types.js\";\n\n// Pre-seed the cache with the hardcoded baselines so `getContract` can\n// look them up uniformly without a `if (name === \"default-memory-v1\")`\n// special case at every call site.\n__cacheContract(\"default-memory-v1\", DEFAULT_MEMORY_V1);\n// Phase 5 / Pitfall 1 resolution: register a separate contract for\n// briefs with widened status enum and brief-specific required keys.\n// See ADR-005 §\"New default-brief-v1 contract\".\n__cacheContract(\"default-brief-v1\", DEFAULT_BRIEF_V1);\n\n/**\n * Synchronous lookup. Returns the named contract from the in-process\n * cache:\n * - `\"default-memory-v1\"` — always available (pre-seeded).\n * - Any name previously loaded via `loadContractFromDisk(name, ...)`.\n *\n * Throws a helpful diagnostic when the name is unknown.\n */\nexport function getContract(name: string): MemoryContract {\n const cached = __getCachedContract(name);\n if (cached) return cached;\n throw new Error(\n `Unknown memory contract: \"${name}\". ` +\n `Known contracts: default-memory-v1${otherCachedNames(name)}. ` +\n `Call loadContractFromDisk(name, vaultPath) first to register a contract.`,\n );\n}\n\nfunction otherCachedNames(excluding: string): string {\n // For diagnostics only — list any names cached besides\n // default-memory-v1 / default-brief-v1 and the excluded name;\n // helps users notice typos when they have many contracts loaded.\n const names: string[] = [];\n // Pull from the cache through the loader's internal accessor — the\n // cache map itself is intentionally not exported.\n for (const candidate of [\"default-memory-v1\", \"default-brief-v1\"]) {\n if (candidate === excluding) continue;\n if (__getCachedContract(candidate)) names.push(candidate);\n }\n return names.length > 0 ? `, ${names.join(\", \")}` : \"\";\n}\n\n// IN-02 closure: `__clearContractCache` is intentionally NOT exported from\n// this public barrel. The test-only symbol lives at\n// `./__testing__.ts` and is imported via the deep path from test files only.\n// Production callers that import from this barrel cannot accidentally clear\n// the cache at runtime — the import path itself is the access marker.\n\nexport {\n DEFAULT_MEMORY_V1,\n DEFAULT_BRIEF_V1,\n loadContractFromDisk,\n MemoryContractInvalidError,\n MemoryContractNotFoundError,\n};\nexport type { MemoryContract };\n","/**\n * `MemorySinkHandle` parser + sentinel filename constant.\n *\n * Per ADR-004 §\"MemorySink handle shape\", a `MemorySinkHandle` is a\n * fully-formed URI of the shape `obsidian-fs://<vault>/<resource>/` —\n * lowercase scheme, non-empty authority, non-empty resource, **trailing\n * slash required** (per ADR-001 §I-6 canonical-serialization). The\n * trailing slash distinguishes a sink handle (a folder address) from a\n * `DocId` (a file address); a folder handle that did not require a\n * trailing slash could be confused with a parent-directory `DocId`.\n *\n * The brand-cast escape hatch lives ONLY inside the IIFE below; this\n * file is the SOLE module that performs it for `MemorySinkHandle`.\n * Only the validating `parseMemorySinkHandle` is exported. The IIFE\n * pattern is identical to `parseDocId` in `src/adapters/registry.ts`.\n *\n * `SENTINEL_FILENAME` is the single canonical name for the sink\n * sentinel file (`.memory-sink`). The sentinel mechanics live in\n * `src/adapters/delivery/obsidian-fs/sentinel.ts` (the only place\n * `node:fs` is licensed for sentinel work, per ADR-002 I-2); this\n * module just declares the filename so other modules don't have to\n * hard-code the string.\n *\n * Phase 2 scope: only `obsidian-fs://` handles are accepted. Future\n * adapters (notion-api, etc.) may add their own schemes; until then,\n * a non-obsidian-fs handle is a config error and the parser rejects.\n */\n\nimport type { MemorySinkHandle } from \"../types.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle pattern + IIFE-closed mint\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Canonical MemorySinkHandle shape: `obsidian-fs://<vault>/<resource>/`.\n *\n * - scheme: `obsidian-fs` (Phase 2 scope; future adapters add their own).\n * - authority: lowercase ASCII alphanumeric + dashes; starts alphanumeric.\n * - resource: at least one non-whitespace character segment;\n * - MUST end with a `/`.\n *\n * Examples that PASS: `obsidian-fs://atlas/_memory/`,\n * `obsidian-fs://atlas/_memory/inbox/`.\n * Examples that FAIL: `obsidian-fs://atlas/_memory` (no trailing slash),\n * `OBSIDIAN-FS://x/y/` (uppercase scheme),\n * `obsidian-fs:/atlas/_memory/` (single slash),\n * `notion-api://...` (non-obsidian-fs scheme, Phase 2).\n */\nexport const MEMORY_SINK_HANDLE_PATTERN = /^obsidian-fs:\\/\\/[a-z0-9][a-z0-9-]*\\/[^\\s]+\\/$/;\n\n/**\n * Allowed characters inside a single path segment of the resource portion\n * of a `MemorySinkHandle`. ASCII alphanumeric plus the three filename-safe\n * punctuation characters (`.`, `_`, `-`). Critically, the literal `.` is\n * permitted INSIDE a segment (so file extensions and dotfiles are fine),\n * but the per-segment whitelist used in `parseMemorySinkHandle` rejects\n * the bare-dot (`.`) and bare-dot-dot (`..`) segments that `path.normalize`\n * / `path.join` would otherwise collapse and let a sink escape its vault.\n *\n * The character class is intentionally narrower than the top-level\n * `MEMORY_SINK_HANDLE_PATTERN` (which only refuses whitespace) so that\n * the parser refuses anything `path.join` could reshape: backslashes,\n * leading-slash empties, control characters, and Unicode lookalikes.\n *\n * Per CR-01 (Plan 02-09): this is the substrate the memory-namespace\n * safety invariant rests on. Downstream `pathInSink` is safe-by-construction\n * precisely because the parser refuses any traversal-shaped input here.\n */\nconst SEGMENT_PATTERN = /^[A-Za-z0-9._\\-]+$/;\n\nconst { parseMemorySinkHandle } = (() => {\n // `mint` is the unsafe brand cast; closed inside this IIFE so it\n // cannot escape. We export only the validating `parse`.\n const mint = (s: string): MemorySinkHandle => s as MemorySinkHandle;\n const parse = (rawInput: string): MemorySinkHandle => {\n // Normalize to NFC BEFORE the regex test. This forecloses Unicode\n // tricks where decomposed-vs-precomposed equivalents differ\n // byte-for-byte: an attacker cannot smuggle a `..` past the parser\n // by spelling it with a combining sequence that re-composes inside\n // the per-segment check. For ASCII inputs NFC is a fixed point, so\n // this is a no-op on the positive controls.\n const s = typeof rawInput === \"string\" ? rawInput.normalize(\"NFC\") : rawInput;\n if (!MEMORY_SINK_HANDLE_PATTERN.test(s)) {\n throw new Error(\n `Invalid MemorySinkHandle: ${JSON.stringify(s)}. ` +\n `Expected obsidian-fs://<vault>/<path>/ (trailing slash required).`,\n );\n }\n // Extract the resource portion: everything after the authority's\n // trailing slash and before the handle's trailing slash. The regex\n // above guarantees the shape, so the slice math is safe.\n //\n // obsidian-fs://<authority>/<resource>/\n // ^ ^ ^\n // authStart authEnd trailing\n //\n // `authStart` is fixed at the length of \"obsidian-fs://\". `authEnd`\n // is the first `/` AT OR AFTER `authStart` (the regex guarantees\n // one exists). The resource is `[authEnd+1, length-1)` so the\n // trailing slash is excluded.\n const authStart = \"obsidian-fs://\".length;\n const authEnd = s.indexOf(\"/\", authStart);\n const resource = s.slice(authEnd + 1, s.length - 1);\n for (const segment of resource.split(\"/\")) {\n if (\n segment.length === 0 ||\n segment === \".\" ||\n segment === \"..\" ||\n !SEGMENT_PATTERN.test(segment)\n ) {\n throw new Error(\n `Invalid MemorySinkHandle: ${JSON.stringify(s)}. ` +\n `Resource path segment ${JSON.stringify(segment)} is not allowed: ` +\n `only [A-Za-z0-9._-]+ segments are permitted ` +\n `(no \"..\", no \".\", no empty segments, no backslashes, no control characters).`,\n );\n }\n }\n return mint(s);\n };\n return { parseMemorySinkHandle: parse };\n})();\n\nexport { parseMemorySinkHandle };\n\n/**\n * Construct a `MemorySinkHandle` from its parts and validate via\n * `parseMemorySinkHandle`. Convenience helper so callers do not\n * concatenate by hand. The caller is responsible for ensuring\n * `resource` ends with a trailing slash; the parser rejects otherwise.\n */\nexport function formatMemorySinkHandle(\n scheme: string,\n authority: string,\n resource: string,\n): MemorySinkHandle {\n return parseMemorySinkHandle(`${scheme}://${authority}/${resource}`);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sentinel filename — canonical declaration\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The single canonical filename for the memory-sink sentinel. Per\n * ADR-004 §\"Sentinel file — `.memory-sink`\", every folder serving as a\n * memory sink MUST contain a file with this name; the registry refuses\n * to resolve a sink against a folder that lacks the sentinel.\n *\n * The sentinel's contents are informational only (timestamp + sink\n * name); the *presence* is the gate. The actual file write/read\n * mechanics live in `src/adapters/delivery/obsidian-fs/sentinel.ts`\n * (the only file licensed to call `node:fs` for sentinel work per\n * ADR-002 I-2).\n */\nexport const SENTINEL_FILENAME = \".memory-sink\";\n","/**\n * Memory-sink sentinel mechanics.\n *\n * Per ADR-004 §\"Sentinel file — `.memory-sink`\", every folder serving\n * as a memory sink MUST contain a `.memory-sink` file at its root.\n * The handle parser refuses to resolve a sink against a folder that\n * lacks the sentinel. This module is the SOLE file licensed to call\n * `node:fs` for sentinel-write / sentinel-check work (ADR-002 I-2 +\n * I-3 confine `node:fs` and `node:path` to\n * `src/adapters/delivery/obsidian-fs/`).\n *\n * Provisioning policy (ADR-004 §\"Provisioning\"; tightened by Plan 02-10\n * to close CR-02):\n * - Empty folder OR folder with only sink-expected content\n * (observations/, _briefs/, status-updates/, .memory-sink): write\n * the sentinel. Plain `.md` files at the sink root are NOT in the\n * allow-list — they almost certainly are user notes and the sink\n * must refuse to absorb them.\n * - Folder with unrelated content (any plain `.md`, `.txt`, etc.):\n * throw `SinkProvisioningError`. The user must either move the\n * foreign content out or change the configured sink handle.\n * - Sentinel already exists: no-op (idempotent).\n * - Folder does not exist: create with `recursive: true`, then\n * write the sentinel.\n *\n * Path joins go through `pathInSink` / `joinVaultPath` from this\n * directory's `path.ts` — the SOLE licensed `path.join` site for\n * sink/vault path resolution in Phase 2.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport type { MemorySink } from \"../../../types.js\";\nimport { SENTINEL_FILENAME as SINK_SENTINEL_FILENAME } from \"../../../memory/sink.js\";\nimport { pathInSink } from \"./path.js\";\n\n/** Re-export so `src/server.ts` / tests can import from this barrel. */\nexport const SENTINEL_FILENAME = SINK_SENTINEL_FILENAME;\n\n/**\n * Provisioning error — thrown when a folder cannot be safely labeled as\n * a memory sink because it already contains unrelated user content.\n */\nexport class SinkProvisioningError extends Error {\n override readonly name = \"SinkProvisioningError\";\n readonly code = \"SINK_PROVISION_UNSAFE\";\n constructor(\n public readonly sinkName: string,\n public readonly absoluteFolderPath: string,\n public readonly offendingEntries: readonly string[],\n ) {\n super(\n `Memory sink \"${sinkName}\" target folder ${absoluteFolderPath} ` +\n `contains unrelated user content (${offendingEntries.join(\", \")}). ` +\n `Refusing to label as a sink. Move user content out, or change the ` +\n `[[memory_sinks]] handle.`,\n );\n }\n}\n\n/**\n * Heuristic: returns true if an entry name \"looks like\" expected\n * memory-sink content. The allowed list is intentionally narrow:\n * - the `.memory-sink` sentinel itself,\n * - the three known sink subfolders (`observations`, `_briefs`,\n * `status-updates`).\n *\n * Plain `.md` files at the sink root are NOT expected — they are\n * almost certainly user notes. Forcing a SinkProvisioningError here\n * surfaces the misconfiguration loudly instead of silently absorbing\n * the folder (CR-02 — gap-closure Plan 02-10).\n */\nfunction isExpectedSinkContent(entry: string): boolean {\n if (entry === SENTINEL_FILENAME) return true;\n if (entry === \"observations\" || entry === \"_briefs\" || entry === \"status-updates\") {\n return true;\n }\n return false;\n}\n\n/**\n * Build the three-line sentinel content per RESEARCH §Q10.\n * Format is informational only — the parser does not validate\n * contents; the *presence* of the file is the gate.\n */\nfunction formatSentinelContent(args: { sinkName: string; version: string }): string {\n const ts = new Date().toISOString();\n return [\n `created_at: ${ts}`,\n `sink_name: ${args.sinkName}`,\n `vault_memory_version: ${args.version}`,\n \"\",\n ].join(\"\\n\");\n}\n\n/**\n * Provision (or no-op) a memory sink at `<vaultAbsolutePath>/<sink.resolveToRelativePath>`.\n *\n * - If the sentinel already exists, return immediately.\n * - If the folder does not exist, create it recursively and write the sentinel.\n * - If the folder exists and is empty (or contains only expected content),\n * write the sentinel.\n * - If the folder exists and contains foreign content, throw `SinkProvisioningError`.\n */\nexport async function provisionSink(\n sink: MemorySink,\n vaultAbsolutePath: string,\n opts: { version: string },\n): Promise<void> {\n const folder = pathInSink(vaultAbsolutePath, sink);\n const sentinelPath = pathInSink(vaultAbsolutePath, sink, SENTINEL_FILENAME);\n\n // Fast path: sentinel already in place.\n try {\n await fs.access(sentinelPath);\n return;\n } catch {\n // Sentinel missing — fall through to creation logic.\n }\n\n let folderExists = true;\n let entries: string[] = [];\n try {\n entries = await fs.readdir(folder);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n folderExists = false;\n } else {\n throw err;\n }\n }\n\n if (!folderExists) {\n await fs.mkdir(folder, { recursive: true });\n await fs.writeFile(\n sentinelPath,\n formatSentinelContent({ sinkName: sink.name, version: opts.version }),\n \"utf-8\",\n );\n return;\n }\n\n // Folder exists. Check it contains only expected sink content.\n const foreign = entries.filter((e) => !isExpectedSinkContent(e));\n if (foreign.length > 0) {\n throw new SinkProvisioningError(sink.name, folder, foreign);\n }\n await fs.writeFile(\n sentinelPath,\n formatSentinelContent({ sinkName: sink.name, version: opts.version }),\n \"utf-8\",\n );\n}\n\n/**\n * Sentinel-check failure for non-ENOENT errno codes. Distinct from the\n * \"sentinel missing\" case so the caller (preflight in\n * `ObsidianFsDelivery`) can surface the underlying errno (EACCES, EIO,\n * ENAMETOOLONG, EPERM, …) instead of the misleading \"restart the\n * server\" suggestion attached to `sentinel_missing` (WR-06 — gap-closure\n * Plan 02-10). Consumed via `WriteConflict.reason = \"sentinel_check_failed\"`\n * (literal declared by Plan 02-13 in wave 9 in `../types.ts`).\n */\nexport class SinkSentinelCheckError extends Error {\n override readonly name = \"SinkSentinelCheckError\";\n readonly code = \"SINK_SENTINEL_CHECK_FAILED\";\n constructor(\n public readonly sinkName: string,\n public readonly underlyingCode: string,\n message: string,\n ) {\n super(message);\n }\n}\n\n/**\n * Return true iff the sentinel exists under the resolved sink folder.\n * Cheap (one `fs.access`) — safe to call on every write per ADR-004\n * §\"Runtime check on every write\".\n *\n * Errno discipline (WR-06 closure):\n * - ENOENT → return `false` (sentinel literally absent).\n * - Anything else (EACCES, EIO, ENAMETOOLONG, EPERM, …) → throw a\n * `SinkSentinelCheckError` carrying the original errno code, so\n * the caller can report it accurately rather than collapsing to\n * \"sentinel missing — restart the server\".\n */\nexport async function assertSentinelExists(\n sink: MemorySink,\n vaultAbsolutePath: string,\n): Promise<boolean> {\n const sentinelPath = pathInSink(vaultAbsolutePath, sink, SENTINEL_FILENAME);\n try {\n await fs.access(sentinelPath);\n return true;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") return false;\n throw new SinkSentinelCheckError(\n sink.name,\n code ?? \"UNKNOWN\",\n `Sentinel check for MemorySink \"${sink.name}\" at ${sentinelPath} failed: ${(err as Error).message}`,\n );\n }\n}\n\n/**\n * Lower-level discovery probe used by server bootstrap auto-discovery\n * (Plan 02-03b). Returns true iff `<vaultRoot>/<relPath>/.memory-sink`\n * exists. Confined to this adapter directory because it touches `node:fs`\n * (ADR-002 I-2). Server bootstrap calls this through `joinVaultPath` so\n * the path-join stays inside the licensed adapter dir too.\n */\nexport async function sentinelExistsAt(vaultRoot: string, relPath: string): Promise<boolean> {\n // We intentionally do NOT use pathInSink here — auto-discovery probes a\n // candidate folder BEFORE any sink record exists, so the join must\n // operate on a plain relative path.\n const probe = `${vaultRoot.endsWith(\"/\") ? vaultRoot.slice(0, -1) : vaultRoot}/${relPath.replace(/^\\//, \"\")}/${SENTINEL_FILENAME}`;\n try {\n await fs.access(probe);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * ObsidianFsDelivery — v2 DeliveryAdapter implementation. Wraps the relocated\n * write/atomic-write modules (writeNote, deleteNote) and exposes them under\n * the ADR-002 §DeliveryAdapter contract.\n *\n * I-2/I-3/I-4/I-6 (raw fs.*, raw path.*, gray-matter, fs.writeFile/unlink/rename)\n * are ALLOWED here — this directory is the only legitimate home for write-side\n * filesystem operations on obsidian-fs vaults.\n *\n * Phase 2 (MEM-01..12) will inject MemorySink guards A (provenance required)\n * and B (source:agent outside configured sink rejected) at the entry of\n * `write()` WITHOUT changing the public method shape. The TSDoc note on\n * `DeliveryAdapter.write()` (see ../types.ts) signals that seam; this Phase 1\n * implementation has ONLY the existing `write_enabled` flag + safeJoinInsideVault\n * path safety.\n *\n * Backwards-compat: the legacy `writeNote` / `deleteNote` / `atomicWriteFile` /\n * `safeJoinInsideVault` / `OutsideVaultError` symbols are still re-exported\n * for v1 handlers that haven't been refactored to call the facade directly.\n * The DeliveryAdapter facade is the preferred entry point for v2 consumers.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport matter from \"gray-matter\";\nimport type {\n DeliveryAdapter,\n DeliveryCapabilities,\n WriteOptions,\n WriteResult as V2WriteResult,\n UpdateResult as V2UpdateResult,\n DeleteResult as V2DeleteResult,\n} from \"../types.js\";\nimport type { Document, DocId, MemorySink, SourceHandle } from \"../../../types.js\";\nimport type { Vault } from \"../../../vault/index.js\";\nimport { parseSourceHandle } from \"../../registry.js\";\nimport {\n writeNote as writeNoteInternal,\n deleteNote as deleteNoteInternal,\n type WriteResult as V1WriteResult,\n} from \"./write.js\";\nimport { safeJoinInsideVault } from \"./fs.js\";\nimport { validateAgentWrite } from \"../../../memory/validator.js\";\nimport { getContract } from \"../../../memory/contract/index.js\";\nimport type { MemorySinkRegistry } from \"../../../memory/registry.js\";\nimport { assertSentinelExists, SinkSentinelCheckError } from \"./sentinel.js\";\n\n// ─── Legacy re-exports (v1 callers + tests) ─────────────────────────────────\n//\n// Existing handlers in src/server.ts and tests import these directly. They\n// continue to work; new code should construct an ObsidianFsDelivery and call\n// the DeliveryAdapter methods instead.\n\nexport { writeNote, deleteNote } from \"./write.js\";\nexport type {\n WriteResult,\n WriteSuccess,\n WriteConflict,\n WriteNoteInput,\n DeleteNoteInput,\n} from \"./write.js\";\nexport { atomicWriteFile, safeJoinInsideVault, OutsideVaultError } from \"./fs.js\";\n\n// ─── DeliveryAdapter facade ─────────────────────────────────────────────────\n\nconst SCHEME = \"obsidian-fs\";\n\n/**\n * Map a v1 internal `WriteResult` (with `noteId: number`) to the v2\n * `WriteResult` shape (with `doc_id: DocId`). The facade owns this\n * boundary mapping so the internal write.ts can keep its v1 shape and\n * caller-facing handlers can keep deriving v1 `noteId` from the DB.\n */\nfunction v1ToV2WriteResult(id: DocId, v1: V1WriteResult): V2WriteResult {\n if (!v1.ok) {\n return v1.currentHash !== undefined\n ? { ok: false, reason: v1.reason, currentHash: v1.currentHash, message: v1.message }\n : { ok: false, reason: v1.reason, message: v1.message };\n }\n return { ok: true, doc_id: id, newHash: v1.newHash, created: v1.created };\n}\n\nfunction v1ToV2UpdateResult(id: DocId, v1: V1WriteResult): V2UpdateResult {\n if (!v1.ok) {\n return v1.currentHash !== undefined\n ? { ok: false, reason: v1.reason, currentHash: v1.currentHash, message: v1.message }\n : { ok: false, reason: v1.reason, message: v1.message };\n }\n return { ok: true, doc_id: id, newHash: v1.newHash };\n}\n\nexport class ObsidianFsDelivery implements DeliveryAdapter {\n readonly handle: SourceHandle;\n\n readonly capabilities: DeliveryCapabilities = {\n atomic: true,\n hashProtected: \"strong\",\n enforcedSchema: false,\n naming: \"caller-provided\",\n };\n\n /**\n * @param vault The Vault unit-of-access (config + db handle).\n * @param clientId Default audit-log attribution. Per D-02, captured from\n * MCP InitializeRequest.params.clientInfo (via the SDK's\n * `Server.getClientVersion()?.name`) at server bootstrap. May be a static\n * string OR a lazy getter — the getter form lets the server construct\n * deliveries BEFORE the initialize handshake completes and have the\n * handshake value flow through automatically on the first write.\n * Falls back to \"unknown\" at the call site if no value is supplied at\n * any level (per RESEARCH Pitfall 4: clientInfo is OPTIONAL in the MCP\n * spec, so older or non-conformant clients may not send it).\n * @param memorySinkRegistry Optional Phase 2 sink registry. When supplied,\n * the adapter runs Guards A/B + sentinel check at the entry of\n * `write` / `update` / `delete` per ADR-002 §DeliveryAdapter. When\n * omitted (Phase 1 fixture tests + back-compat), the validator is\n * silently skipped — production paths in Plan 02-03b's server\n * bootstrap always pass the registry, so production is always\n * guarded.\n */\n constructor(\n private readonly vault: Vault,\n private readonly clientIdSource: string | (() => string),\n private readonly memorySinkRegistry?: MemorySinkRegistry,\n ) {\n this.handle = parseSourceHandle(`${SCHEME}://${vault.config.name}`);\n }\n\n private get clientId(): string {\n return typeof this.clientIdSource === \"function\" ? this.clientIdSource() : this.clientIdSource;\n }\n\n /**\n * Resolve the sink that \"owns\" a write target.\n *\n * Resolution order (per ADR-004 §Resolution + Plan 02-03 <action>):\n * 1. If `opts.sink` is supplied AND the registry knows it, use it.\n * The caller explicitly routed the write under that sink.\n * 2. Else, ask the registry `findSinkContaining(id)` — for DocIds\n * whose vault-relative path lies inside a registered sink, this\n * returns the enclosing sink. Used for guarding writes that\n * target memory paths WITHOUT an explicit `opts.sink` (e.g. v1\n * `writeNote` against `_memory/...`).\n * 3. Else, the target is outside every sink — return `null`.\n *\n * Returns `null` when no registry is configured (Phase 1 fixture\n * tests + back-compat). The validator then silently passes.\n */\n private resolveTargetSink(id: DocId, opts?: WriteOptions): MemorySink | null {\n const registry = this.memorySinkRegistry;\n if (!registry) return null;\n if (opts?.sink !== undefined) {\n try {\n return registry.resolveMemorySink(opts.sink);\n } catch {\n // Fall through to path-based lookup; surfaces as\n // `agent_write_outside_sink` if the caller declared the wrong\n // sink and the path also doesn't land in any registered sink.\n }\n }\n return registry.findSinkContaining(id);\n }\n\n /**\n * Derive the `is_memory_sink_write` flag for the audit row.\n *\n * WR-08 (Plan 02-14): this MUST use the resolved truth\n * (`registry.findSinkContaining(id)`), NOT the caller-intent signal\n * (`opts.sink !== undefined`). The two signals diverge when a write\n * lands inside a sink WITHOUT the caller having routed through the\n * sink-aware path (legacy `writeNote` bypass, future code paths). The\n * audit must reflect what the disk says, not what the caller said.\n *\n * When no registry is configured (Phase 1 fixture constructors), the\n * flag falls back to `false` — preserves back-compat fixture tests.\n */\n private isMemorySinkWriteFor(id: DocId): boolean {\n const sink = this.memorySinkRegistry?.findSinkContaining(id);\n return sink !== null && sink !== undefined;\n }\n\n /**\n * Run Guards A/B + sentinel for a write or update. Returns the\n * conflict to short-circuit on, or `null` to proceed.\n *\n * Order: Guard B (cheap) → sentinel (fail-closed) → Guard A.\n * The sentinel check is filesystem-specific and intentionally lives\n * here, not in the validator.\n */\n private async preflight(\n id: DocId,\n doc: Partial<Document>,\n opts?: WriteOptions,\n ): Promise<V2WriteResult | null> {\n if (!this.memorySinkRegistry) return null;\n const sink = this.resolveTargetSink(id, opts);\n const contract = sink ? getContract(sink.contractName) : null;\n\n // Guard B (and partial Guard A for source mismatch) — runs first.\n // Guard A short-circuits if source-check fails.\n const sourceCheck = validateAgentWrite(id, doc, sink, null);\n if (sourceCheck) return sourceCheck;\n\n // Sentinel check (filesystem-specific) — only when target lands in a sink.\n // WR-06 (gap-closure Plan 02-10): ENOENT distinguishes from other errno\n // codes. The literal `\"sentinel_check_failed\"` was added to the\n // WriteConflict.reason union by Plan 02-13 Task 1 in wave 9; this plan\n // CONSUMES that literal here.\n if (sink !== null) {\n let ok: boolean;\n try {\n ok = await assertSentinelExists(sink, this.vault.config.path);\n } catch (err) {\n if (err instanceof SinkSentinelCheckError) {\n return {\n ok: false,\n reason: \"sentinel_check_failed\",\n sinkName: sink.name,\n message: err.message,\n suggestion:\n `Check filesystem permissions / disk health for ` +\n `${this.vault.config.name}/${sink.resolveToRelativePath}. ` +\n `Underlying errno: ${err.underlyingCode}.`,\n };\n }\n throw err;\n }\n if (!ok) {\n return {\n ok: false,\n reason: \"sentinel_missing\",\n sinkName: sink.name,\n message:\n `MemorySink \"${sink.name}\" refuses to resolve: ` +\n `'.memory-sink' sentinel file is missing under ${this.vault.config.name}/${sink.resolveToRelativePath}.`,\n suggestion:\n \"Restart the server (it re-provisions automatically) or restore .memory-sink manually.\",\n };\n }\n }\n\n // Guard A (full Zod schema validation) — only when target lands in a sink\n // and a contract is bound.\n if (sink !== null && contract !== null) {\n const guardA = validateAgentWrite(id, doc, sink, contract);\n if (guardA) return guardA;\n }\n return null;\n }\n\n async write(id: DocId, doc: Partial<Document>, opts?: WriteOptions): Promise<V2WriteResult> {\n const guard = await this.preflight(id, doc, opts);\n if (guard) return guard;\n const path = this.docIdToPath(id);\n const { body, frontmatter } = extractBodyAndFrontmatter(doc);\n const effectiveClientId = opts?.clientId ?? this.clientId;\n // Plan 02-14 (MEM-08 follow-up, WR-08): the audit row's\n // `is_memory_sink_write` flag is derived from\n // `registry.findSinkContaining(id)` — the resolved-target truth, not\n // caller intent. A write that lands inside a sink without `opts.sink`\n // (legacy `writeNote` bypass, future code paths) is still correctly\n // flagged. When no registry is configured (Phase 1 fixture tests), the\n // flag falls back to `false`.\n const v1 = await writeNoteInternal({\n vault: this.vault,\n relativePath: path,\n content: body,\n frontmatter,\n ...(opts?.expectedHash !== undefined ? { expectedHash: opts.expectedHash } : {}),\n clientId: effectiveClientId,\n isMemorySinkWrite: this.isMemorySinkWriteFor(id),\n });\n return v1ToV2WriteResult(id, v1);\n }\n\n /**\n * Replace-or-merge update. Reads current document via the filesystem,\n * applies `patch.properties` (shallow-merged into existing frontmatter)\n * and/or `patch.blocks` (replaces body), then writes via writeNote with\n * the OCC token.\n *\n * Returns `{ ok: false, reason: \"not_found\" }` when the file is absent\n * (matches DeliveryAdapter contract — no implicit create on update).\n *\n * WR-05 (Plan 02-14): callers MUST supply `opts.expectedHash`. Omitting\n * it returns `{ ok: false, reason: \"hash_mismatch\" }` — symmetric with\n * `delete()`'s existing behavior. The previous implementation silently\n * fabricated `expectedHash` from the on-disk hash, racing with concurrent\n * edits and downgrading the `hashProtected: \"strong\"` capability to\n * best-effort.\n *\n * The v1 MCP `update_frontmatter` handler continues to route through\n * `src/frontmatter/update.ts` (merge-DSL semantics + diff emission). This\n * `update()` path exists primarily for conformance and for non-merge-DSL\n * callers (Phase 2+).\n */\n async update(id: DocId, patch: Partial<Document>, opts?: WriteOptions): Promise<V2UpdateResult> {\n const guard = await this.preflight(id, patch, opts);\n if (guard) return guard;\n\n // WR-05 (Plan 02-14): refuse if expectedHash is missing. The OCC token\n // is mandatory for hashProtected=\"strong\" adapters; silently fabricating\n // it from the on-disk hash (the previous behavior) downgraded the\n // contract to best-effort and raced with concurrent edits.\n if (opts?.expectedHash === undefined) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n message: `update() requires opts.expectedHash for hashProtected=\"strong\" adapters`,\n };\n }\n\n const path = this.docIdToPath(id);\n\n // Resolve absolute path with safety check; on traversal this throws,\n // which surfaces upstream — that is intentional, matching v1 writeNote.\n const abs = await safeJoinInsideVault(this.vault.config.path, path);\n\n let raw: string;\n try {\n raw = await fs.readFile(abs, \"utf-8\");\n } catch (err) {\n if (\n typeof err === \"object\" &&\n err !== null &&\n (err as NodeJS.ErrnoException).code === \"ENOENT\"\n ) {\n return {\n ok: false,\n reason: \"not_found\",\n message: `Document not found: ${id}`,\n };\n }\n throw err;\n }\n const parsed = matter(raw);\n const existingFm = (parsed.data ?? {}) as Record<string, unknown>;\n const existingBody = parsed.content;\n\n // Merge properties (shallow). If patch.blocks is supplied, replace body\n // with the concatenation of paragraph-block text; otherwise preserve.\n const patchProps = patch.properties as Record<string, unknown> | undefined;\n const nextFm =\n patchProps !== undefined ? { ...existingFm, ...stripWikilinks(patchProps) } : existingFm;\n const nextBody =\n patch.blocks !== undefined\n ? patch.blocks\n .map((b) => (b.kind === \"paragraph\" ? b.text : \"\"))\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\")\n : existingBody;\n\n // WR-05 (Plan 02-14): expectedHash is mandatory (checked above). The OCC\n // contract is honored by passing opts.expectedHash straight through;\n // writeNoteInternal surfaces a hash_mismatch for stale tokens.\n const effectiveClientId = opts?.clientId ?? this.clientId;\n // Plan 02-14 (MEM-08 follow-up, WR-08): symmetric with write() —\n // update() derives the audit-row `is_memory_sink_write` flag from\n // `registry.findSinkContaining(id)` (resolved-target truth), not\n // from `opts.sink !== undefined` (caller intent). supersede routes\n // through update() against a DocId inside a sink, so the audit row\n // is correctly stamped regardless of opts.sink presence.\n const v1 = await writeNoteInternal({\n vault: this.vault,\n relativePath: path,\n content: nextBody,\n frontmatter: Object.keys(nextFm).length > 0 ? nextFm : null,\n expectedHash: opts.expectedHash,\n clientId: effectiveClientId,\n isMemorySinkWrite: this.isMemorySinkWriteFor(id),\n });\n return v1ToV2UpdateResult(id, v1);\n }\n\n async delete(id: DocId, opts?: WriteOptions): Promise<V2DeleteResult> {\n // Hard-deletion of memory documents is forbidden in v2.0.0\n // (per Plan 02-03 truths + RESEARCH Pitfall 5). If the DocId\n // resolves into ANY registered sink (regardless of opts.sink),\n // refuse with sink_write_blocked. Use `supersede` instead.\n if (this.memorySinkRegistry) {\n const enclosing = this.memorySinkRegistry.findSinkContaining(id);\n if (enclosing !== null) {\n return {\n ok: false,\n reason: \"sink_write_blocked\",\n sinkName: enclosing.name,\n message:\n `Hard deletion of MemorySink \"${enclosing.name}\" documents is ` +\n `not permitted in v2.0.0.`,\n suggestion:\n \"Use supersede to retire memory documents. Hard deletion is not yet supported in v2.0.0.\",\n };\n }\n }\n\n const path = this.docIdToPath(id);\n\n // The v1 deleteNote requires expectedHash. If the caller did not\n // supply one, surface a permission-denied-style failure so the\n // hashProtected=\"strong\" guarantee holds.\n if (opts?.expectedHash === undefined) {\n // Use a \"not_found\"-style probe via the FS to distinguish a\n // missing-doc case from a missing-hash case (the conformance test\n // expects `not_found` when deleting an unknown DocId).\n try {\n const abs = await safeJoinInsideVault(this.vault.config.path, path);\n await fs.stat(abs);\n } catch {\n return {\n ok: false,\n reason: \"not_found\",\n message: `Document not found: ${id}`,\n };\n }\n return {\n ok: false,\n reason: \"hash_mismatch\",\n message: `delete() requires opts.expectedHash for hashProtected=\"strong\" adapters`,\n };\n }\n\n const effectiveClientId = opts?.clientId ?? this.clientId;\n // Plan 02-14 (MEM-08 follow-up, WR-08): symmetric flag for delete.\n // The `is_memory_sink_write` audit flag is derived from\n // `registry.findSinkContaining(id)` (resolved-target truth), not\n // from `opts.sink !== undefined` (caller intent). Sink-resolved\n // deletes are normally refused upstream with `sink_write_blocked`\n // (hard-delete of memory documents is forbidden in v2.0.0; callers\n // use `supersede`); this code path is reached only for non-sink\n // targets or future admin bypasses, but the flag derivation stays\n // symmetric with write/update so any bypass that DOES reach audit\n // is truthfully flagged.\n const v1 = await deleteNoteInternal({\n vault: this.vault,\n relativePath: path,\n expectedHash: opts.expectedHash,\n clientId: effectiveClientId,\n isMemorySinkWrite: this.isMemorySinkWriteFor(id),\n });\n if (!v1.ok) {\n // v1 returns hash_mismatch when the file is absent. Re-shape to\n // not_found for the v2 contract.\n if (v1.reason === \"hash_mismatch\" && v1.currentHash === undefined) {\n return {\n ok: false,\n reason: \"not_found\",\n message: v1.message,\n };\n }\n return v1.currentHash !== undefined\n ? { ok: false, reason: v1.reason, currentHash: v1.currentHash, message: v1.message }\n : { ok: false, reason: v1.reason, message: v1.message };\n }\n return { ok: true, doc_id: id };\n }\n\n // ── helpers ───────────────────────────────────────────────────────────────\n\n /**\n * Parse the URI authority + resource off a DocId. Asserts the authority\n * matches the configured vault name — mirrors ObsidianFsSource.docIdToPath\n * to prevent cross-vault forgery.\n */\n private docIdToPath(id: DocId): string {\n const prefix = `${SCHEME}://`;\n if (!id.startsWith(prefix)) {\n throw new Error(`DocId scheme mismatch: expected \"${SCHEME}://…\", got ${JSON.stringify(id)}`);\n }\n const rest = id.slice(prefix.length);\n const slash = rest.indexOf(\"/\");\n if (slash < 0) {\n throw new Error(`Invalid DocId shape: missing resource path in ${JSON.stringify(id)}`);\n }\n const authority = rest.slice(0, slash);\n const resource = rest.slice(slash + 1);\n if (authority !== this.vault.config.name) {\n throw new Error(\n `DocId vault mismatch: id authority \"${authority}\" does not match ` +\n `this adapter's configured vault \"${this.vault.config.name}\"`,\n );\n }\n if (resource.length === 0) {\n throw new Error(`Invalid DocId: empty resource path in ${JSON.stringify(id)}`);\n }\n return resource;\n }\n}\n\n// ─── Partial<Document> → body + frontmatter ─────────────────────────────────\n\nfunction stripWikilinks(props: Record<string, unknown>): Record<string, unknown> {\n // D-05: ObsidianFsSource surfaces wikilinks as Document.properties.wikilinks\n // when READING. We must NOT write that field back into the user's frontmatter.\n const { wikilinks: _w, ...rest } = props as { wikilinks?: unknown } & Record<string, unknown>;\n return rest;\n}\n\nfunction extractBodyAndFrontmatter(doc: Partial<Document>): {\n body: string;\n frontmatter: Record<string, unknown> | null;\n} {\n // body: concatenate flat-text paragraph blocks. Phase 1 only emits\n // single-paragraph blocks anyway (ADR-003 BodyShape=\"flat-text\").\n const body = (doc.blocks ?? [])\n .map((b) => (b.kind === \"paragraph\" ? b.text : \"\"))\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n\n const props = doc.properties;\n if (props === undefined || props === null) {\n return { body, frontmatter: null };\n }\n const stripped = stripWikilinks(props as Record<string, unknown>);\n return {\n body,\n frontmatter: Object.keys(stripped).length > 0 ? stripped : null,\n };\n}\n","/**\n * updateFrontmatter — merge-style frontmatter editor.\n *\n * Modifies only the YAML frontmatter of a markdown note. The body is\n * preserved bytegenau. Writes are atomic and audited.\n *\n * Merge DSL (top-level keys of `merge`):\n * <key>: <value> → set / overwrite\n * <key>: { $unset: true } → delete the key\n * <key>: { $push: x } → push x onto array (create if absent)\n * <key>: { $pull: x } → remove x from array (no-op if absent)\n * <key>: { ...plainObj } → shallow-merge into existing object (or set)\n *\n * Concurrency: optional `expectedHash` is checked against the current\n * note hash (sha256 of `content + JSON.stringify(frontmatter ?? {})`).\n * Mismatch → conflict, no write.\n *\n * NOTE: gray-matter's stringify preserves the existing serialization\n * style for fields it knows about, but YAML key order for *new* keys is\n * insertion order. We do not guarantee a stable global key order.\n *\n * Plan 01-04 task 05: this module no longer imports `gray-matter` or\n * `node:fs` directly. The READ path goes through the v2 SourceConnector\n * (`registry.resolveSource(handle).readDocument(id)`) and the WRITE\n * path goes through the v2 DeliveryAdapter (`registry.resolveDelivery\n * (handle).write(id, partial, opts)`). The merge-DSL semantics + diff\n * emission are UNCHANGED.\n */\n\nimport type { Vault } from \"../vault/index.js\";\nimport type { Document, DocId, SourceHandle, WikilinkRef } from \"../types.js\";\nimport type { AdapterRegistry } from \"../adapters/registry.js\";\nimport { formatDocId, parseSourceHandle } from \"../adapters/registry.js\";\nimport type { MemorySinkRegistry } from \"../memory/registry.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public API\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface UpdateFrontmatterInput {\n vault: Vault;\n /**\n * Adapter registry — Source for read, Delivery for write. Optional for\n * backwards-compatibility with v1 callers that haven't been migrated;\n * when omitted, the function falls back to constructing per-call\n * adapters from `vault` (delegated by the server handler in Phase 1).\n */\n registry?: AdapterRegistry;\n /**\n * Plan 02-03b — defense-in-depth entry-point Guard. When supplied AND\n * the target lands inside a registered MemorySink, the update is\n * refused with `{ok:false, reason:\"sink_write_blocked\"}` BEFORE any\n * filesystem read. When omitted (Phase 1 unit-test fixtures + back-\n * compat callers), the guard is silently skipped. See\n * `src/adapters/delivery/obsidian-fs/write.ts:WriteNoteInput.registry`\n * for the full rationale (the authoritative chokepoint lives at the\n * DeliveryAdapter; this is defense-in-depth).\n */\n memorySinkRegistry?: MemorySinkRegistry;\n relativePath: string;\n merge: Record<string, unknown>;\n expectedHash?: string;\n clientId?: string;\n /** Called once, immediately before the filesystem write. See\n * `WriteNoteInput.onBeforeFsWrite`. Not called when the update is a\n * no-op (empty merge or no effective change) since no fs event will\n * occur. */\n onBeforeFsWrite?: () => void;\n}\n\nexport type DiffOp = \"set\" | \"unset\" | \"push\" | \"pull\";\n\nexport interface DiffEntry {\n key: string;\n op: DiffOp;\n before?: unknown;\n after?: unknown;\n}\n\nexport interface UpdateSuccess {\n ok: true;\n newHash: string;\n noteId: number;\n diff: DiffEntry[];\n}\n\nexport interface UpdateConflict {\n ok: false;\n reason: \"hash_mismatch\" | \"permission_denied\" | \"note_not_found\" | \"sink_write_blocked\";\n currentHash?: string;\n message: string;\n /** Phase 2 envelope (sink_write_blocked). */\n sinkName?: string;\n /** Phase 2 envelope — actionable next-step hint. */\n suggestion?: string;\n}\n\nexport type UpdateResult = UpdateSuccess | UpdateConflict;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction isUnsetDirective(v: unknown): v is { $unset: true } {\n return isPlainObject(v) && v[\"$unset\"] === true;\n}\n\nfunction isPushDirective(v: unknown): v is { $push: unknown } {\n return isPlainObject(v) && \"$push\" in v;\n}\n\nfunction isPullDirective(v: unknown): v is { $pull: unknown } {\n return isPlainObject(v) && \"$pull\" in v;\n}\n\nfunction hasDirective(v: unknown): boolean {\n if (!isPlainObject(v)) return false;\n return Object.keys(v).some((k) => k.startsWith(\"$\"));\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const ak = Object.keys(a);\n const bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (!deepEqual(a[k], b[k])) return false;\n }\n return true;\n }\n return false;\n}\n\nfunction applyMerge(\n data: Record<string, unknown>,\n merge: Record<string, unknown>,\n): { next: Record<string, unknown>; diff: DiffEntry[] } {\n const next: Record<string, unknown> = { ...data };\n const diff: DiffEntry[] = [];\n\n for (const [key, instr] of Object.entries(merge)) {\n const before = next[key];\n\n if (isUnsetDirective(instr)) {\n if (key in next) {\n delete next[key];\n diff.push({ key, op: \"unset\", before });\n }\n continue;\n }\n\n if (isPushDirective(instr)) {\n const value = (instr as { $push: unknown }).$push;\n if (Array.isArray(before)) {\n const arr = [...before, value];\n next[key] = arr;\n diff.push({ key, op: \"push\", before, after: arr });\n } else if (before === undefined) {\n next[key] = [value];\n diff.push({ key, op: \"push\", before: undefined, after: [value] });\n } else {\n // Treat non-array existing scalar as wrapping into a new array\n next[key] = [value];\n diff.push({ key, op: \"push\", before, after: [value] });\n }\n continue;\n }\n\n if (isPullDirective(instr)) {\n const value = (instr as { $pull: unknown }).$pull;\n if (Array.isArray(before)) {\n const filtered = before.filter((v) => !deepEqual(v, value));\n if (filtered.length !== before.length) {\n next[key] = filtered;\n diff.push({ key, op: \"pull\", before, after: filtered });\n }\n }\n // else: no-op\n continue;\n }\n\n // Plain set or shallow-merge nested object\n if (isPlainObject(instr) && !hasDirective(instr) && isPlainObject(before)) {\n const merged = { ...before, ...instr };\n if (!deepEqual(before, merged)) {\n next[key] = merged;\n diff.push({ key, op: \"set\", before, after: merged });\n }\n } else {\n if (!deepEqual(before, instr)) {\n next[key] = instr;\n diff.push({ key, op: \"set\", before, after: instr });\n }\n }\n }\n\n return { next, diff };\n}\n\n/**\n * Strip the adapter-injected `wikilinks: WikilinkRef[]` property that\n * `ObsidianFsSource.readDocument` puts on `Document.properties` (D-05).\n * The user's frontmatter never contained this key — we must NOT carry\n * it through the merge or re-write.\n */\nfunction stripWikilinks(props: Record<string, unknown>): Record<string, unknown> {\n const { wikilinks: _w, ...rest } = props as { wikilinks?: WikilinkRef[] } & Record<\n string,\n unknown\n >;\n return rest;\n}\n\n/**\n * Extract the flat-text body string from `Document.blocks`. Phase 1 only\n * emits single-paragraph blocks (BodyShape=\"flat-text\") so this is\n * trivially the first block's text. Matches the inverse of\n * `extractBodyAndFrontmatter` in ObsidianFsDelivery.\n */\nfunction blocksToBody(doc: Document): string {\n return doc.blocks\n .map((b) => (b.kind === \"paragraph\" ? b.text : \"\"))\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n}\n\nexport async function updateFrontmatter(input: UpdateFrontmatterInput): Promise<UpdateResult> {\n const {\n vault,\n relativePath,\n merge,\n expectedHash,\n clientId,\n registry,\n memorySinkRegistry,\n onBeforeFsWrite,\n } = input;\n\n // Plan 02-03b — defense-in-depth entry-point Guard. Runs BEFORE the\n // write_enabled check and BEFORE any DB / FS read. When the optional\n // MemorySinkRegistry is supplied (production path) AND the target lands\n // inside a registered sink, refuse with the structured `sink_write_blocked`\n // envelope. The suggestion directs the caller to `record_observation +\n // supersede` per Plan 02-03b action notes.\n if (memorySinkRegistry) {\n const docId = formatDocId(\"obsidian-fs\", vault.config.name, relativePath);\n const sink = memorySinkRegistry.findSinkContaining(docId);\n if (sink !== null) {\n return {\n ok: false,\n reason: \"sink_write_blocked\",\n sinkName: sink.name,\n message:\n `Target ${relativePath} resolves into MemorySink \"${sink.name}\". ` +\n `v1 update_frontmatter is refused for memory-sink targets.`,\n suggestion: \"Use record_observation + supersede for memory updates.\",\n };\n }\n }\n\n if (vault.config.write_enabled !== true) {\n return {\n ok: false,\n reason: \"permission_denied\",\n message: \"Vault is not write-enabled. Set write_enabled=true in config.\",\n };\n }\n\n const noteRow = vault.db.notes.getByPath(relativePath);\n if (noteRow === null) {\n return {\n ok: false,\n reason: \"note_not_found\",\n message: `No indexed note at path: ${relativePath}`,\n };\n }\n\n // Resolve the adapter triple. Phase 1 fallback (no registry supplied):\n // construct one inline from `vault` so existing callers (handlers that\n // haven't been migrated to registry-based dispatch yet) keep working.\n const { source, delivery } = await resolveAdapters(vault, registry);\n const handle = parseSourceHandle(`obsidian-fs://${vault.config.name}`);\n void handle;\n const docId: DocId = formatDocId(\"obsidian-fs\", vault.config.name, relativePath);\n\n // ── READ via Source ────────────────────────────────────────────────────────\n let doc: Document;\n try {\n doc = await source.readDocument(docId);\n } catch (err) {\n const msg = errorMessage(err);\n return {\n ok: false,\n reason: \"note_not_found\",\n message: `Failed to read document: ${msg}`,\n };\n }\n\n const body = blocksToBody(doc);\n const existingFm = stripWikilinks(doc.properties as Record<string, unknown>);\n // The current hash on disk is exactly `doc.hash` (ObsidianFsSource uses\n // `computeNoteHash(body, fm)`). The wikilinks injection happens AFTER\n // hash computation in the parser, so doc.hash matches the gray-matter\n // round-trip the v1 code computed.\n const currentHash = doc.hash;\n\n if (expectedHash !== undefined && expectedHash !== currentHash) {\n return {\n ok: false,\n reason: \"hash_mismatch\",\n currentHash,\n message: `Expected hash ${expectedHash} but current is ${currentHash}.`,\n };\n }\n\n // Empty merge → no-op\n if (Object.keys(merge).length === 0) {\n return {\n ok: true,\n newHash: currentHash,\n noteId: noteRow.id,\n diff: [],\n };\n }\n\n const { next, diff } = applyMerge(existingFm, merge);\n\n if (diff.length === 0) {\n // Nothing actually changed (e.g. $pull on absent value)\n return {\n ok: true,\n newHash: currentHash,\n noteId: noteRow.id,\n diff: [],\n };\n }\n\n // ── WRITE via Delivery ─────────────────────────────────────────────────────\n // Pass the suppression hook through opts? — DeliveryAdapter does not\n // expose it on the v2 surface. Instead, call it directly before\n // dispatching; this matches the v1 ordering (hook fires immediately\n // before the fs write).\n onBeforeFsWrite?.();\n\n const partial: Partial<Document> = {\n blocks: [{ kind: \"paragraph\", text: body }],\n properties: Object.keys(next).length > 0 ? next : {},\n };\n const writeOpts: {\n expectedHash: string;\n clientId?: string;\n } = {\n expectedHash: currentHash,\n };\n if (clientId !== undefined) writeOpts.clientId = clientId;\n\n const writeRes = await delivery.write(docId, partial, writeOpts);\n if (!writeRes.ok) {\n // Shape-map Delivery v2 conflict reasons back to v1 update result.\n if (writeRes.reason === \"permission_denied\") {\n return {\n ok: false,\n reason: \"permission_denied\",\n message: writeRes.message ?? \"Write rejected by delivery adapter.\",\n };\n }\n return {\n ok: false,\n reason: \"hash_mismatch\",\n ...(writeRes.currentHash !== undefined ? { currentHash: writeRes.currentHash } : {}),\n message: writeRes.message ?? \"Write conflict.\",\n };\n }\n\n return {\n ok: true,\n newHash: writeRes.newHash,\n noteId: noteRow.id,\n diff,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Phase 1 adapter resolution\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Phase 1: when callers don't supply an AdapterRegistry, construct an\n * inline source + delivery for the vault. The eventual end-state (after\n * task 06's server.ts wiring) is that every caller supplies the\n * registry; this fallback is for the legacy `updateFrontmatter({vault,\n * ...})` shape during the migration window.\n *\n * The dynamic imports avoid pulling the obsidian-fs adapter into the\n * module-load graph for callers that never reach this branch — there\n * are no current consumers besides src/server.ts which WILL pass a\n * registry post-task-06.\n */\nasync function resolveAdapters(\n vault: Vault,\n registry: AdapterRegistry | undefined,\n): Promise<{\n source: { readDocument: (id: DocId) => Promise<Document> };\n delivery: {\n write: (\n id: DocId,\n doc: Partial<Document>,\n opts?: { expectedHash?: string; clientId?: string },\n ) => Promise<\n | { ok: true; newHash: string; doc_id: DocId; created: boolean }\n | { ok: false; reason: string; currentHash?: string; message?: string }\n >;\n };\n}> {\n const handle: SourceHandle = parseSourceHandle(`obsidian-fs://${vault.config.name}`);\n if (registry !== undefined) {\n return {\n source: registry.resolveSource(handle),\n delivery: registry.resolveDelivery(handle),\n };\n }\n // Fallback path — instantiate inline. clientId=\"unknown\" because the\n // server bootstrap has not threaded an actual MCP client_info value\n // through to this call; the caller's `clientId` arg wins in writeOpts.\n const { ObsidianFsSource } = await import(\"../adapters/source/obsidian-fs/index.js\");\n const { ObsidianFsDelivery } = await import(\"../adapters/delivery/obsidian-fs/index.js\");\n return {\n source: new ObsidianFsSource(vault.config),\n delivery: new ObsidianFsDelivery(vault, \"unknown\"),\n };\n}\n","export { queryFrontmatter } from \"./query.js\";\nexport type { QueryFrontmatterInput, Predicate } from \"./query.js\";\nexport { updateFrontmatter } from \"./update.js\";\nexport type {\n UpdateFrontmatterInput,\n UpdateResult,\n UpdateSuccess,\n UpdateConflict,\n DiffEntry,\n DiffOp,\n} from \"./update.js\";\n","/**\n * `MemorySinkRegistry` — the SOLE resolver for `MemorySink` handles\n * per ADR-004 §Resolution + ADR-002 §Registry M-1.\n *\n * Responsibilities:\n * - Hold the runtime map of registered sinks keyed by handle.\n * - Track the default sink (configured by `[memory].default_sink`\n * in TOML, or the first registered sink as a fallback).\n * - Provide name-OR-handle dual lookup via `resolveMemorySink`.\n * - Expose `findSinkContaining(docId)` for entry-point Guard A\n * refusals in v1 write tools (MEM-07).\n *\n * The registry is filesystem-ignorant: provisioning (sentinel writes)\n * is delegated to a `provisioner` callback supplied by the server\n * bootstrap. In production the callback wraps\n * `provisionSink(...)` from\n * `src/adapters/delivery/obsidian-fs/sentinel.ts`; in tests it is a\n * spy. This keeps `src/memory/` free of `node:fs` per ADR-002 I-2.\n *\n * The registry uses `decomposeDocId` from `src/adapters/registry.ts`\n * for splitting `DocId`s into `(scheme, authority, resource)` — no\n * ad-hoc regex. Handle-resource splitting uses a small private helper\n * because `MemorySinkHandle` is a distinct brand from `DocId`.\n */\n\nimport { decomposeDocId } from \"../adapters/registry.js\";\nimport { getContract } from \"./contract/index.js\";\nimport { parseMemorySinkHandle } from \"./sink.js\";\nimport type { DocId, MemorySink, MemorySinkHandle } from \"../types.js\";\n\n/** TOML-shape entry from `[[memory_sinks]]`. Validated by config/loader.ts. */\nexport interface MemorySinkConfig {\n name: string;\n handle: string;\n contract: string;\n}\n\n/** Options for `registerMemorySinks`. */\nexport interface RegisterMemorySinksOptions {\n /** Resolve a vault name (handle authority) to the vault-absolute path. */\n resolveVaultAbsolutePath: (vaultName: string) => string;\n /** Name of the configured default sink (from `[memory].default_sink`). */\n defaultSinkName?: string;\n /**\n * Optional getter override (defaults to `getContract` from\n * `./contract/index.js`); test-injectable.\n */\n contractGetter?: (name: string) => { name: string };\n /**\n * Provision the sink on disk (writes the sentinel). In production\n * this wraps `provisionSink(...)` from\n * `src/adapters/delivery/obsidian-fs/sentinel.ts`; in tests it is\n * a spy. The registry must not import `node:fs` directly.\n */\n provisioner: (sink: MemorySink, vaultAbsolutePath: string) => Promise<void>;\n}\n\n/**\n * Split a `MemorySinkHandle` into `(scheme, authority, resource)`.\n * Pure string split — the handle is already validated by\n * `parseMemorySinkHandle` so the shape is guaranteed.\n */\nfunction decomposeMemorySinkHandle(handle: MemorySinkHandle): {\n scheme: string;\n authority: string;\n resource: string;\n} {\n const schemeEnd = handle.indexOf(\"://\");\n const scheme = handle.slice(0, schemeEnd);\n const rest = handle.slice(schemeEnd + 3);\n const authoritySlash = rest.indexOf(\"/\");\n const authority = rest.slice(0, authoritySlash);\n const resource = rest.slice(authoritySlash + 1);\n return { scheme, authority, resource };\n}\n\nexport class MemorySinkRegistry {\n private readonly sinks = new Map<MemorySinkHandle, MemorySink>();\n /** Insertion order — used for the \"first registered\" default fallback. */\n private readonly order: MemorySinkHandle[] = [];\n private defaultHandle: MemorySinkHandle | null = null;\n\n /**\n * Register a batch of configured sinks. Validates each handle, looks\n * up the named contract, invokes the provisioner, and stores the\n * resolved `MemorySink` record.\n *\n * Throws on the first failure — server bootstrap should treat any\n * registration error as fatal per ADR-004 §Provisioning fail-fast.\n */\n async registerMemorySinks(\n configs: MemorySinkConfig[],\n opts: RegisterMemorySinksOptions,\n ): Promise<void> {\n const getC = opts.contractGetter ?? getContract;\n for (const cfg of configs) {\n const handle = parseMemorySinkHandle(cfg.handle);\n const parts = decomposeMemorySinkHandle(handle);\n if (parts.scheme !== \"obsidian-fs\") {\n throw new Error(\n `MemorySink \"${cfg.name}\" has unsupported scheme \"${parts.scheme}\". ` +\n `Phase 2 supports only obsidian-fs sinks.`,\n );\n }\n const vaultName = parts.authority;\n const resolveToRelativePath = parts.resource;\n const contract = getC(cfg.contract);\n const isFirst = this.sinks.size === 0;\n const isExplicitDefault = opts.defaultSinkName === cfg.name;\n const isDefault = isExplicitDefault || (opts.defaultSinkName === undefined && isFirst);\n const sink: MemorySink = {\n name: cfg.name,\n handle,\n vault: vaultName,\n resolveToRelativePath,\n contractName: contract.name,\n isDefault,\n };\n await opts.provisioner(sink, opts.resolveVaultAbsolutePath(vaultName));\n this.sinks.set(handle, sink);\n this.order.push(handle);\n if (isDefault) this.defaultHandle = handle;\n }\n }\n\n /** Return all registered sinks in insertion order. */\n listMemorySinks(): MemorySink[] {\n const out: MemorySink[] = [];\n for (const handle of this.order) {\n const s = this.sinks.get(handle);\n if (s) out.push(s);\n }\n return out;\n }\n\n /**\n * Resolve a sink by EITHER its short `name` OR its full handle\n * string. Throws with a helpful diagnostic on miss — mirrors the\n * `AdapterRegistry.resolveSource` message style.\n */\n resolveMemorySink(nameOrHandle: string): MemorySink {\n // Name lookup first (most common case).\n for (const handle of this.order) {\n const s = this.sinks.get(handle);\n if (s && s.name === nameOrHandle) return s;\n }\n // Then handle lookup (string-equal to a registered handle).\n for (const handle of this.order) {\n if (handle === nameOrHandle) {\n const s = this.sinks.get(handle);\n if (s) return s;\n }\n }\n const known =\n this.order\n .map((h) => this.sinks.get(h)?.name)\n .filter(Boolean)\n .join(\", \") || \"(none)\";\n throw new Error(`Unknown memory sink: \"${nameOrHandle}\". Registered sinks: ${known}`);\n }\n\n /** Return the default sink. Throws if no sinks are registered. */\n getDefaultMemorySink(): MemorySink {\n if (this.defaultHandle === null) {\n throw new Error(\n \"No memory sinks are registered; cannot resolve the default sink. \" +\n \"Configure [[memory_sinks]] in config.toml.\",\n );\n }\n const sink = this.sinks.get(this.defaultHandle);\n if (!sink) {\n throw new Error(\n `Internal error: default memory sink handle \"${this.defaultHandle}\" not found in registry.`,\n );\n }\n return sink;\n }\n\n /**\n * Find the sink that encloses a given `DocId`, or `null` if the\n * DocId is outside every configured sink. Used by v1 write tools\n * (MEM-07) for entry-point Guard A refusals.\n *\n * Match policy: the DocId's authority must equal the sink's vault,\n * and the DocId's resource must start with the sink's\n * `resolveToRelativePath` (which includes its trailing slash, so\n * `_memory/observations/foo.md` matches sink `_memory/` but\n * `_memory-staging/...` does not).\n */\n findSinkContaining(docId: DocId): MemorySink | null {\n const { scheme, authority, resource } = decomposeDocId(docId);\n if (scheme !== \"obsidian-fs\") return null;\n for (const handle of this.order) {\n const sink = this.sinks.get(handle);\n if (!sink) continue;\n if (sink.vault !== authority) continue;\n if (resource.startsWith(sink.resolveToRelativePath)) {\n return sink;\n }\n }\n return null;\n }\n}\n","/**\n * `vault-memory://memory/sinks` — MCP Resource enumerating the\n * configured + auto-discovered MemorySinks (Plan 02-06, MEM-09).\n *\n * Resource, not tool: agents that want to discover where they may\n * write memory documents read this URI instead of invoking a tool.\n * Polled-only — there is NO `notifyResourceUpdated` integration in\n * v2.0.0 (CONTEXT D-Q4, Deferred Ideas).\n *\n * The handler is a pure function over the `MemorySinkRegistry`. It\n * touches neither the filesystem nor the DB — the single resolver\n * rule from ADR-004 §Resolution applies.\n */\n\nimport type { MemorySinkRegistry } from \"../registry.js\";\n\nexport interface ListSinksResource {\n /** Total number of registered sinks across all vaults. */\n total: number;\n sinks: ListSinkEntry[];\n}\n\nexport interface ListSinkEntry {\n /** Short name (resolution key). */\n name: string;\n /** Full `obsidian-fs://<vault>/<path>/` URI. */\n handle: string;\n /** Owning vault name. */\n vault: string;\n /** Name of the bound `MemoryContract`. */\n contract: string;\n /** True iff this is the vault's default sink. */\n default: boolean;\n /** Vault-relative folder the sink resolves to (e.g. \"_memory/\"). */\n resolves_to: string;\n}\n\n/**\n * Pure handler — builds the resource payload from the registry's\n * `listMemorySinks()` snapshot.\n */\nexport function readListSinks(registry: MemorySinkRegistry): ListSinksResource {\n const sinks = registry.listMemorySinks();\n return {\n total: sinks.length,\n sinks: sinks.map(\n (s): ListSinkEntry => ({\n name: s.name,\n handle: s.handle,\n vault: s.vault,\n contract: s.contractName,\n default: s.isDefault,\n resolves_to: s.resolveToRelativePath,\n }),\n ),\n };\n}\n","/**\n * `vault-memory://memory/stats` — MCP Resource exposing per-sink document\n * counts and last-write timestamps (Plan 02-06, MEM-09).\n *\n * Resource, not tool. Polled-only. Build cost is a small handful of SQL\n * queries per registered sink — bounded by the number of sinks (tens at\n * most in v2.0.0), so the resource is cheap to re-read.\n *\n * Aggregation strategy:\n * - `doc_count` ← `NotesQueries.countByPathPrefix(sink.resolveToRelativePath)`\n * - `by_type` ← scan `frontmatter.type` over rows returned by\n * `NotesQueries.listByPathPrefix(...)`\n * - `by_status` ← scan `frontmatter.status` over the same rows\n * - `last_write_at` ← `AuditQueries.lastMemoryWriteAtForPathPrefix(...)`\n * (uses the v9 partial index)\n *\n * The resource is filesystem-ignorant — it pulls everything from the per-\n * vault SQLite DB via the existing Queries classes. ADR-002 I-2/I-3/I-4\n * remain satisfied (no fs / path / gray-matter imports here).\n */\n\nimport type { MemorySinkRegistry } from \"../registry.js\";\nimport type { VaultManager } from \"../../vault/manager.js\";\nimport { LIST_BY_PATH_PREFIX_DEFAULT_LIMIT } from \"../../db/queries/notes.js\";\n\nexport interface MemoryStatsResource {\n /** Aggregate document count across all sinks. */\n total_docs: number;\n sinks: MemoryStatsEntry[]; // vault-memory:no-telemetry-ok\n}\n\nexport interface MemoryStatsEntry {\n // vault-memory:no-telemetry-ok\n name: string;\n vault: string;\n handle: string;\n doc_count: number;\n by_type: Record<string, number>;\n by_status: Record<string, number>;\n /** Epoch ms of the most recent memory-sink write into this sink, or null. */\n last_write_at: number | null;\n /**\n * IN-03: True iff the `by_type` / `by_status` aggregation hit the\n * `LIST_BY_PATH_PREFIX_DEFAULT_LIMIT` cap. When true, `doc_count`\n * still reflects the accurate row count (it comes from\n * `countByPathPrefix`, which is unbounded), but the `by_type` /\n * `by_status` sums undercount by\n * `doc_count - LIST_BY_PATH_PREFIX_DEFAULT_LIMIT`. Omitted when\n * the cap was not hit. Consumers detecting this can either widen\n * the sink configuration or accept the undercount.\n */\n truncated?: boolean;\n}\n\n/**\n * Build the resource payload. Returns an empty resource (`total_docs: 0`,\n * `sinks: []`) when no sinks are registered — the empty case is a valid\n * response, not an error.\n */\nexport function readMemoryStats(\n registry: MemorySinkRegistry,\n manager: VaultManager,\n): MemoryStatsResource {\n const sinks = registry.listMemorySinks();\n let totalDocs = 0;\n const entries: MemoryStatsEntry[] = []; // vault-memory:no-telemetry-ok\n\n for (const sink of sinks) {\n // Sink may reference a vault that is no longer mounted (e.g. config\n // edited at runtime). Surface zero counts in that case rather than\n // throwing — keeps the resource readable for diagnostic purposes.\n let vault;\n try {\n vault = manager.require(sink.vault);\n } catch {\n entries.push({\n name: sink.name,\n vault: sink.vault,\n handle: sink.handle,\n doc_count: 0,\n by_type: {},\n by_status: {},\n last_write_at: null,\n });\n continue;\n }\n\n const prefix = sink.resolveToRelativePath;\n const doc_count = vault.db.notes.countByPathPrefix(prefix);\n totalDocs += doc_count;\n\n const by_type: Record<string, number> = {};\n const by_status: Record<string, number> = {};\n // Bounded scan — see TSDoc on listByPathPrefix; sinks in v2.0.0 hold\n // tens of documents, not thousands. The `truncated` marker (IN-03)\n // surfaces the rare case where the cap was hit so consumers can\n // detect the doc_count vs by_type/by_status inconsistency.\n const rows = vault.db.notes.listByPathPrefix(prefix);\n for (const row of rows) {\n const fm = parseFrontmatter(row.frontmatter);\n const type = stringField(fm, \"type\");\n const status = stringField(fm, \"status\");\n if (type !== null) by_type[type] = (by_type[type] ?? 0) + 1;\n if (status !== null) by_status[status] = (by_status[status] ?? 0) + 1;\n }\n const truncated = rows.length >= LIST_BY_PATH_PREFIX_DEFAULT_LIMIT;\n\n const last_write_at = vault.db.audit.lastMemoryWriteAtForPathPrefix(prefix);\n\n entries.push({\n name: sink.name,\n vault: sink.vault,\n handle: sink.handle,\n doc_count,\n by_type,\n by_status,\n last_write_at,\n ...(truncated ? { truncated: true } : {}),\n });\n }\n\n return {\n total_docs: totalDocs,\n sinks: entries,\n };\n}\n\nfunction parseFrontmatter(raw: string | null): Record<string, unknown> {\n if (raw === null || raw.length === 0) return {};\n try {\n const parsed: unknown = JSON.parse(raw);\n if (parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n return {};\n } catch {\n // Stored frontmatter that fails to JSON-parse is silently treated as\n // empty for stats purposes. The indexer writes well-formed JSON; a\n // corrupted row should not crash the Resource.\n return {};\n }\n}\n\nfunction stringField(fm: Record<string, unknown>, key: string): string | null {\n const v = fm[key];\n return typeof v === \"string\" && v.length > 0 ? v : null;\n}\n","/**\n * Memory MCP Resources — barrel.\n *\n * Plan 02-06 (MEM-09):\n * - `vault-memory://memory/sinks` → readListSinks\n * - `vault-memory://memory/stats` → readMemoryStats\n *\n * Polled-only (no `notifyResourceUpdated` integration in v2.0.0).\n * Registered through `server.registerResource(...)` at bootstrap.\n */\n\nexport { readListSinks } from \"./list-sinks.js\";\nexport type { ListSinksResource, ListSinkEntry } from \"./list-sinks.js\";\n\nexport { readMemoryStats } from \"./memory-stats.js\";\nexport type { MemoryStatsResource, MemoryStatsEntry } from \"./memory-stats.js\"; // vault-memory:no-telemetry-ok\n\n/** Canonical resource URIs. */\nexport const RESOURCE_URI_LIST_SINKS = \"vault-memory://memory/sinks\";\nexport const RESOURCE_URI_MEMORY_STATS = \"vault-memory://memory/stats\";\n/**\n * Phase 5 / BRF-09: brief discovery via MCP Resource. Registered by\n * slice 4 (Plan 05-04); the URI constant lands in slice 1 so later\n * slices can import it without scaffolding work.\n */\nexport const RESOURCE_URI_LIST_BRIEFS = \"vault-memory://briefs\";\n\n/**\n * Phase 6 / Plan 06-04 (CON-04 + D-A2b): contract discovery + verb-usage\n * Resources. The `{vault}` suffix is appended at registration time per\n * the SDK 1.29 Resource template pattern.\n */\nexport const RESOURCE_URI_LIST_CONTRACTS = \"vault-memory://contracts\";\nexport const RESOURCE_URI_LIST_CONTRACT_VERBS = \"vault-memory://contract-verbs\";\n\n/**\n * SOURCES-REGISTRY.md §5 (Stage 2): first-class peer-MCP source\n * discovery. Vault-independent (the PeerMcpRegistry is one global\n * instance across vaults), so `sources` has no `{vault}` segment.\n * `sources/{name}/tools` and `sources/{name}/tools/{tool}` append their\n * variables at registration time.\n */\nexport const RESOURCE_URI_SOURCES = \"vault-memory://sources\";\n\n/**\n * Phase 8 / Plan 08-05 (REL-08): 5 list-style v1 tools promoted to MCP\n * Resources to land the canonical (non-deprecated) tool surface at 32.\n *\n * The original tools (list_vaults, list_models, recent_notes, vault_stats,\n * list_backlinks) remain callable through v2.x with a DEPRECATED notice in\n * their `description`. Each Resource read handler delegates to the existing\n * internal handler function — no logic duplication (GAT-01 seam preservation).\n *\n * URIs are the BASE form here; templated forms append `/{vault}` (and\n * `/{+docId}` for backlinks) at `registerResource` time. The `+` in\n * `{+docId}` is RFC 6570 reserved-character expansion: it allows the\n * variable to include `/`, so multi-segment docIds (e.g. `notes/sub/file.md`)\n * parse as a single value instead of being truncated at the first `/`.\n */\nexport const RESOURCE_URI_VAULTS = \"vault-memory://vaults\";\nexport const RESOURCE_URI_MODELS = \"vault-memory://models\";\nexport const RESOURCE_URI_RECENT = \"vault-memory://recent\";\nexport const RESOURCE_URI_STATS = \"vault-memory://stats\";\nexport const RESOURCE_URI_BACKLINKS = \"vault-memory://backlinks\";\n","/**\n * Public surface of the memory subsystem.\n *\n * Phase 2 Plan 02-02 ships the substrate layer:\n * - `parseMemorySinkHandle` / `formatMemorySinkHandle` / SENTINEL_FILENAME\n * from `./sink.js`.\n * - `MemorySinkRegistry` from `./registry.js` (sole resolver per\n * ADR-004 §Resolution).\n * - `getContract` / `loadContractFromDisk` / `DEFAULT_MEMORY_V1` /\n * `MemoryContract` from `./contract/index.js`.\n *\n * Downstream plans (02-03..02-08) add the validator, MCP tools,\n * MCP resources, and audit-log integration; their public symbols\n * will be re-exported here.\n */\n\nexport {\n formatMemorySinkHandle,\n MEMORY_SINK_HANDLE_PATTERN,\n parseMemorySinkHandle,\n SENTINEL_FILENAME,\n} from \"./sink.js\";\n\nexport { MemorySinkRegistry } from \"./registry.js\";\nexport type { MemorySinkConfig, RegisterMemorySinksOptions } from \"./registry.js\";\n\nexport {\n DEFAULT_MEMORY_V1,\n getContract,\n loadContractFromDisk,\n MemoryContractInvalidError,\n MemoryContractNotFoundError,\n} from \"./contract/index.js\";\nexport type { MemoryContract } from \"./contract/index.js\";\n\n// Plan 02-05 — citation packet shape (D-01); shared with Phase 3 ASM-05.\nexport { displayUrlFor, toCitationPacket } from \"./citation-packet.js\";\nexport type { CitationPacket } from \"./citation-packet.js\";\n\n// Plan 02-06 (MEM-09) — MCP Resources for sink listing + per-sink stats.\n// Plan 06-04 (CON-04 + D-A2b) — contract Resource URI constants live alongside.\nexport {\n readListSinks,\n readMemoryStats,\n RESOURCE_URI_LIST_SINKS,\n RESOURCE_URI_LIST_BRIEFS,\n RESOURCE_URI_MEMORY_STATS,\n RESOURCE_URI_LIST_CONTRACTS,\n RESOURCE_URI_LIST_CONTRACT_VERBS,\n RESOURCE_URI_SOURCES,\n RESOURCE_URI_VAULTS,\n RESOURCE_URI_MODELS,\n RESOURCE_URI_RECENT,\n RESOURCE_URI_STATS,\n RESOURCE_URI_BACKLINKS,\n} from \"./resources/index.js\";\nexport type {\n ListSinksResource,\n ListSinkEntry,\n MemoryStatsResource,\n MemoryStatsEntry, // vault-memory:no-telemetry-ok\n} from \"./resources/index.js\";\n","/**\n * Canonical RESOURCES literal — the single source of truth for the\n * MCP `resources/list` surface.\n *\n * Mirrors src/tool-registry.ts (TOOLS). Consumed by:\n * - evals/v1-baseline/dump-resources.mjs (snapshot generation)\n * - evals/v1-baseline/baseline.test.ts (snapshot equality + length === 13)\n * - src/server.ts (registerResource metadata source)\n *\n * Plan 08-05 (REL-08): 10 entries — 5 pre-existing (memory-sinks,\n * memory-stats, briefs, contracts, contract-verbs) + 5 newly promoted\n * from v1 tools (vaults, models, recent, stats, backlinks).\n *\n * SOURCES-REGISTRY.md §5 (Stage 2): +3 peer-MCP source discovery\n * resources (sources, source-tools, source-tool) → 13 entries.\n *\n * Two URI shapes appear here:\n * - Static URI (e.g. `vault-memory://memory/sinks`, `vault-memory://vaults`):\n * a single concrete URI; no template variables.\n * - Templated URI (e.g. `vault-memory://models/{vault}`): SDK 1.29\n * ResourceTemplate variables expand at read time.\n *\n * The `list_backlinks` entry uses **RFC 6570 reserved expansion** on the\n * `docId` variable — `vault-memory://backlinks/{vault}/{+docId}` — so a\n * docId like `notes/sub/file.md` (with embedded `/`) parses as a single\n * value instead of being truncated at the first `/`. Without the leading\n * `+`, default expansion matches only one path segment.\n */\n\nexport interface ResourceEntry {\n readonly name: string;\n readonly uriTemplate: string;\n readonly description: string;\n readonly mimeType: \"application/json\";\n}\n\nexport const RESOURCES: readonly ResourceEntry[] = [\n // ─── Phase 2 (Plan 02-06 / MEM-09) ──────────────────────────────────────\n {\n name: \"memory-sinks\",\n uriTemplate: \"vault-memory://memory/sinks\",\n description:\n \"Configured + auto-discovered MemorySinks (name, handle, vault, contract, default). \" +\n \"Read to discover where memory documents (record_observation, supersede) land.\",\n mimeType: \"application/json\",\n },\n {\n name: \"memory-stats\",\n uriTemplate: \"vault-memory://memory/stats\",\n description:\n \"Per-sink document counts, by_type / by_status breakdowns, and last memory-write timestamp. \" +\n \"Polled — re-read to refresh.\",\n mimeType: \"application/json\",\n },\n // ─── Phase 5 (Plan 05-04 / BRF-09) ──────────────────────────────────────\n {\n name: \"briefs\",\n uriTemplate: \"vault-memory://briefs\",\n description:\n \"Discovery of compiled briefs by target. Supports optional `?target=<pattern>` \" +\n \"substring filter on `properties.target`. Includes `active`, `stale`, and \" +\n \"`superseded` entries so callers can build their own filter / inspect the \" +\n \"supersede chain. BRF-09.\",\n mimeType: \"application/json\",\n },\n // ─── Phase 6 (Plan 06-04 / CON-04 + D-A2b) ──────────────────────────────\n {\n name: \"contracts\",\n uriTemplate: \"vault-memory://contracts/{vault}\",\n description:\n \"Discovery of task contracts available in a vault (CON-04). Each entry \" +\n \"carries name, description, source/sink counts, and write_back boolean. \" +\n \"Optional `?source=<prefix>` filters to contracts declaring a source \" +\n \"whose handle starts with the given prefix.\",\n mimeType: \"application/json\",\n },\n {\n name: \"contract-verbs\",\n uriTemplate: \"vault-memory://contract-verbs/{vault}\",\n description:\n \"List baseline assembly verbs + custom (mcp://) verbs in use, with \" +\n \"invocation_count + last_seen aggregated from contract_audit (D-A2b). \" +\n \"Baseline verbs are constant per ADR-006 §Decision 3.\",\n mimeType: \"application/json\",\n },\n // ─── SOURCES-REGISTRY.md §5 (Stage 2) — peer-MCP source discovery ───────\n {\n name: \"sources\",\n uriTemplate: \"vault-memory://sources\",\n description:\n \"List peer MCP servers vault-memory connects to, with per-source status \" +\n \"(connected/unavailable/unreachable), tool_count, and last_refreshed. \" +\n \"vault-memory itself is not included. SOURCES-REGISTRY §5.1.\",\n mimeType: \"application/json\",\n },\n {\n name: \"source-tools\",\n uriTemplate: \"vault-memory://sources/{name}/tools\",\n description:\n \"List the cached tools/list for one peer MCP source. Empty when the \" +\n \"source is not connected. SOURCES-REGISTRY §5.2.\",\n mimeType: \"application/json\",\n },\n {\n name: \"source-tool\",\n uriTemplate: \"vault-memory://sources/{name}/tools/{tool}\",\n description:\n \"Read a single tool's schema from one peer MCP source, inlined from the \" +\n \"cached tools/list. SOURCES-REGISTRY §5.3.\",\n mimeType: \"application/json\",\n },\n // ─── Phase 8 (Plan 08-05 / REL-08) — promoted from v1 tools ─────────────\n {\n name: \"vaults\",\n uriTemplate: \"vault-memory://vaults\",\n description:\n \"List configured vaults with their status (note count, last indexed run). \" +\n \"Promoted from the `list_vaults` MCP tool in v2.0.0; the tool remains callable \" +\n \"through v2.x.\",\n mimeType: \"application/json\",\n },\n {\n name: \"models\",\n uriTemplate: \"vault-memory://models/{vault}\",\n description:\n \"List all embedding models registered for a vault, with dim, active flag, and \" +\n \"how many chunks have been embedded under each. Promoted from the `list_models` \" +\n \"MCP tool in v2.0.0; the tool remains callable through v2.x.\",\n mimeType: \"application/json\",\n },\n {\n name: \"recent\",\n uriTemplate: \"vault-memory://recent/{vault}\",\n description:\n \"List recently modified notes (mtime DESC) for a vault. Use for agent \" +\n \"self-orientation: 'what has the user been working on lately?'. Promoted from \" +\n \"the `recent_notes` MCP tool in v2.0.0; the tool remains callable through v2.x.\",\n mimeType: \"application/json\",\n },\n {\n name: \"stats\",\n uriTemplate: \"vault-memory://stats/{vault}\",\n description:\n \"Vault overview for agent self-orientation: note/word counts, top tags, top \" +\n \"frontmatter keys, embedding model, last index run. Promoted from the \" +\n \"`vault_stats` MCP tool in v2.0.0; the tool remains callable through v2.x.\",\n mimeType: \"application/json\",\n },\n {\n name: \"backlinks\",\n // RFC 6570 reserved expansion on docId: `{+docId}` preserves `/` in the\n // variable so multi-segment paths like `notes/sub/file.md` parse as a\n // single value. Without the `+`, the default expansion stops at the\n // first `/`. See Plan 08-05 §B2 for the acceptance test.\n uriTemplate: \"vault-memory://backlinks/{vault}/{+docId}\",\n description:\n \"Find all notes that link TO a given note. The `docId` segment uses RFC 6570 \" +\n \"reserved expansion ({+docId}) so multi-segment paths (e.g. `notes/sub/file.md`) \" +\n \"are preserved verbatim. Promoted from the `list_backlinks` MCP tool in v2.0.0; \" +\n \"the tool remains callable through v2.x.\",\n mimeType: \"application/json\",\n },\n];\n","/**\n * `handleRecordObservation` — the MEM-02 controller.\n *\n * Authors a new memory observation under a labeled `MemorySink`. Sugar\n * arguments (`claim`, `evidence`, `confidence`, `type`) pre-fill the\n * contract-required keys. The caller-supplied `properties` bag is\n * filtered to drop the 8 provenance-critical keys\n * (`source`, `evidence`, `confidence`, `observed_at`, `type`, `status`,\n * `superseded_by`, `superseded_reason`) BEFORE merge, then the sugar\n * values are applied LAST. Result: contract-allowed extras (tags,\n * expires_at, priority, etc.) flow through unchanged — D-02\n * escape-hatch preserved — but the provenance trail can never be\n * weakened by the caller (WR-07 closure).\n *\n * The controller never pre-validates beyond required-args presence;\n * contract enforcement is the validator's job at the\n * `DeliveryAdapter.write()` chokepoint (Plan 02-03 wired). When the\n * delivery returns a `WriteConflict`, the controller returns it\n * unchanged so the caller observes the structured Phase 2 envelope\n * (sinkName / key / observedValue / suggestion).\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. Slug derivation is pure string ops;\n * `node:crypto` is used for the 6-char hash suffix to avoid same-day\n * collisions.\n */\n\nimport { createHash, randomBytes } from \"node:crypto\";\nimport type { DeliveryAdapter, WriteResult } from \"../../adapters/delivery/types.js\";\nimport { formatDocId } from \"../../adapters/registry.js\";\nimport type { SourceConnector } from \"../../adapters/source/types.js\";\nimport type { Document } from \"../../types.js\";\nimport type { VaultManager } from \"../../vault/index.js\";\nimport type { MemorySinkRegistry } from \"../registry.js\";\n\n/** Naming subfolder used by the default-memory-v1 contract. */\nconst OBSERVATIONS_SUBFOLDER = \"observations/\";\n\n/** Max number of times we retry the DocId-collision avoidance loop. */\nconst MAX_COLLISION_RETRIES = 3;\n\n/**\n * Provenance-critical keys that callers MAY NOT override via the\n * `properties` escape-hatch. The validator at the DeliveryAdapter\n * chokepoint trusts these values; allowing caller override would let\n * a malicious or buggy agent weaken its own provenance trail.\n *\n * WR-07 closure + D-02 refinement: D-02's \"caller keys win over sugar\n * defaults\" rule is EXPLICITLY scoped to non-provenance extras (e.g.\n * tags, expires_at, priority). Provenance keys (the 8 listed below)\n * come exclusively from validated MCP args. The validator at\n * `DeliveryAdapter.write()` (Guard A/B, Plan 02-03) remains the single\n * source of truth for which non-protected keys the contract accepts.\n */\nconst PROTECTED_PROVENANCE_KEYS = new Set<string>([\n \"source\",\n \"evidence\",\n \"confidence\",\n \"observed_at\",\n \"type\",\n \"status\",\n \"superseded_by\",\n \"superseded_reason\",\n]);\n\n/**\n * Dependencies — supplied by the server bootstrap. Pure interface so\n * tests can wire fakes without touching the file system seam.\n */\nexport interface RecordObservationDeps {\n memorySinkRegistry: MemorySinkRegistry;\n manager: VaultManager;\n /**\n * Resolve the `DeliveryAdapter` instance for a vault name. The\n * controller never instantiates adapters itself — bootstrap owns\n * adapter lifetimes.\n */\n deliveryAdapterFor: (vaultName: string) => DeliveryAdapter;\n /**\n * Resolve the `SourceConnector` instance for a vault name. The\n * controller uses `connector.exists(docId)` to detect path\n * collisions on the same-day same-claim retry path. Bootstrap\n * (Plan 02-03b) supplies this closure.\n */\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\nexport interface RecordObservationArgs {\n vault: string;\n claim: string;\n evidence: string[];\n confidence: \"direct\" | \"inferred\" | \"uncertain\";\n type: string;\n /** Bare sink name OR full `obsidian-fs://…` handle. Defaults to the vault's default sink. */\n sink?: string;\n /**\n * Escape-hatch: contract-allowed extras merged AFTER sugar args.\n * Caller-supplied keys win — D-02.\n */\n properties?: Record<string, unknown>;\n}\n\n/**\n * Slugify a `claim` string for use in the date-slug naming pattern.\n *\n * Rules:\n * - lowercase\n * - strip accents via `normalize(\"NFD\")` + combining-mark removal\n * - replace non-ASCII-alnum with hyphens\n * - collapse repeated hyphens\n * - trim leading/trailing hyphens\n * - cap at 60 chars (without breaking mid-word past the cap)\n */\nfunction slugify(claim: string): string {\n const stripped = claim\n .normalize(\"NFD\")\n // Strip combining diacritical marks (U+0300–U+036F). IN-04: explicit\n // Unicode-escape form is source-stable; some editors / log\n // aggregators silently drop literal combining characters and\n // produce an empty char-class.\n .replace(/[\\u0300-\\u036F]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n if (stripped.length <= 60) return stripped || \"observation\";\n return stripped.slice(0, 60).replace(/-+$/g, \"\") || \"observation\";\n}\n\n/**\n * Compute a 6-character hex hash suffix for collision avoidance within\n * the same day. Mixes `claim`, `observed_at`, and an optional `salt`\n * (retry counter) so consecutive retries produce different suffixes.\n */\nfunction hashSuffix(claim: string, observedAt: string, salt = \"\"): string {\n return createHash(\"sha256\")\n .update(`${claim}\\x00${observedAt}\\x00${salt}`)\n .digest(\"hex\")\n .slice(0, 6);\n}\n\n/**\n * Extract the `YYYY-MM-DD` portion of an ISO-8601 timestamp.\n * Works for both `Z`-suffixed and `+HH:MM` variants because the date\n * prefix is always the first 10 characters of an ISO string.\n */\nfunction dateSlug(isoTimestamp: string): string {\n return isoTimestamp.slice(0, 10);\n}\n\n/**\n * Record a new memory observation. See file header for D-02 / D-03\n * semantics.\n *\n * Returns the `WriteResult` discriminated union from the delivery\n * adapter UNCHANGED — never renames `newHash` to `hash`, never re-\n * shapes a `WriteConflict`.\n */\nexport async function handleRecordObservation(\n deps: RecordObservationDeps,\n args: RecordObservationArgs,\n): Promise<WriteResult> {\n // ── Resolve the target sink ──────────────────────────────────────────────\n const registry = deps.memorySinkRegistry;\n const sink =\n args.sink !== undefined\n ? registry.resolveMemorySink(args.sink)\n : registry.getDefaultMemorySink();\n\n if (sink.vault !== args.vault) {\n throw new Error(`Sink \"${sink.name}\" belongs to vault \"${sink.vault}\", not \"${args.vault}\"`);\n }\n\n // ── Build the property bag ───────────────────────────────────────────────\n //\n // WR-07 closure + D-02 refinement: strip provenance-critical keys from\n // caller-supplied `properties` BEFORE merging, then place sugar LAST so\n // the 8 protected keys (source / evidence / confidence / observed_at /\n // type / status / superseded_by / superseded_reason) cannot be weakened\n // by the caller. Non-provenance extras (tags, expires_at, priority,\n // custom_tag, etc.) still win over absent sugar defaults — the D-02\n // escape-hatch is preserved for contract-allowed extras.\n const observedAtDefault = new Date().toISOString();\n const sugarProps: Record<string, unknown> = {\n source: \"agent\",\n observed_at: observedAtDefault,\n status: \"active\",\n confidence: args.confidence,\n evidence: args.evidence,\n type: args.type,\n superseded_by: null,\n };\n const callerExtras: Record<string, unknown> = {};\n if (args.properties !== undefined) {\n for (const [k, v] of Object.entries(args.properties)) {\n if (!PROTECTED_PROVENANCE_KEYS.has(k)) {\n callerExtras[k] = v;\n }\n }\n }\n // callerExtras FIRST, sugarProps LAST — defensive ordering means even\n // if the filter is ever bypassed, sugar still wins for provenance keys.\n const properties: Record<string, unknown> = {\n ...callerExtras,\n ...sugarProps,\n };\n\n // ── Mint a DocId, retrying with fresh hash suffix on path collision ─────\n const observedAtForNaming =\n typeof properties.observed_at === \"string\" ? properties.observed_at : observedAtDefault;\n const slug = slugify(args.claim);\n\n const delivery = deps.deliveryAdapterFor(args.vault);\n const source = deps.sourceConnectorFor(args.vault);\n\n let attempt = 0;\n while (attempt < MAX_COLLISION_RETRIES) {\n // WR-04 (b): per-retry salt is cryptographically random — six hex\n // chars of fresh entropy. Two same-millisecond calls with identical\n // claim/observed_at no longer produce identical collision chains.\n const suffix = hashSuffix(args.claim, observedAtForNaming, randomBytes(3).toString(\"hex\"));\n const filename = `${dateSlug(observedAtForNaming)}-${slug}-${suffix}.md`;\n // `sink.resolveToRelativePath` already ends in \"/\" (enforced by the\n // MemorySinkHandle regex in Plan 02-02). Safe to concatenate.\n const relativeResource = sink.resolveToRelativePath + OBSERVATIONS_SUBFOLDER + filename;\n const docId = formatDocId(\"obsidian-fs\", args.vault, relativeResource);\n\n // Path-collision check: if the candidate DocId already resolves to\n // an existing file, retry with a fresh hash6 salt rather than\n // overwriting. The delivery would otherwise create-or-overwrite per\n // its `naming: \"caller-provided\"` capability.\n const collides = await source.exists(docId);\n if (collides) {\n attempt += 1;\n continue;\n }\n\n const partialDoc: Partial<Document> = {\n id: docId,\n title: args.claim.slice(0, 80),\n properties,\n blocks: [{ kind: \"paragraph\", text: args.claim }],\n };\n\n // Delegate to the delivery — the validator at the chokepoint runs\n // Guard A + Guard B + sentinel. WriteConflicts (including\n // contract-validator rejections like non_agent_write_inside_sink)\n // are returned UNCHANGED.\n return await delivery.write(docId, partialDoc, { sink: sink.handle });\n }\n\n // WR-04 (a): distinct reason on retry exhaustion so callers can branch\n // on a meaningful recovery path (vary the claim text, the observed_at\n // timestamp, or retry later) — `permission_denied` everywhere else\n // means \"vault is read-only\" and would mislead automatic retry logic.\n return {\n ok: false,\n reason: \"collision_retry_exhausted\",\n message:\n `Failed to mint unique DocId after ${MAX_COLLISION_RETRIES} attempts. ` +\n `Vary the claim text, the observed_at timestamp, or retry the call.`,\n };\n}\n","/**\n * `handleSupersede` — the MEM-04 controller.\n *\n * Marks an existing memory document as superseded by a replacement\n * document. Forward-only per D-03: this controller writes\n * `status: \"superseded\"`, `superseded_by: <replacement_doc_id>`, and\n * `superseded_reason: <reason>` on the OLD doc ONLY — the replacement\n * is never touched. Back-link materialization is deferred to the\n * Phase 4 graph layer (it can be derived from a single property scan).\n *\n * Single OCC `delivery.update()` call. The OLD doc's read-side hash\n * (`Document.hash`) is fetched via `SourceConnector.readDocument` and\n * passed as `opts.expectedHash` so concurrent edits surface as a\n * `hash_mismatch` WriteConflict — returned UNCHANGED.\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. The DocId parsing chain\n * (`parseDocId` / `decomposeDocId`) lives in `src/adapters/registry.ts`.\n */\n\nimport type { DeliveryAdapter, UpdateResult } from \"../../adapters/delivery/types.js\";\nimport type { SourceConnector } from \"../../adapters/source/types.js\";\nimport { decomposeDocId, parseDocId } from \"../../adapters/registry.js\";\nimport type { Document } from \"../../types.js\";\nimport type { VaultManager } from \"../../vault/index.js\";\nimport type { MemorySinkRegistry } from \"../registry.js\";\n\nexport interface SupersedeDeps {\n memorySinkRegistry: MemorySinkRegistry;\n manager: VaultManager;\n deliveryAdapterFor: (vaultName: string) => DeliveryAdapter;\n /** Reads the OLD doc's current hash via `connector.readDocument(id)`. */\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\nexport interface SupersedeArgs {\n /** DocId of the document being superseded. */\n doc_id: string;\n /** DocId of the replacement document. */\n replacement_doc_id: string;\n /** Non-empty rationale; written to `superseded_reason` on the OLD doc. */\n reason: string;\n}\n\n/**\n * Mark the OLD doc as superseded. See file header for D-03 semantics.\n * Returns the `UpdateResult` from the delivery adapter UNCHANGED —\n * `newHash` is the post-update hash; never re-shaped to `hash`.\n */\nexport async function handleSupersede(\n deps: SupersedeDeps,\n args: SupersedeArgs,\n): Promise<UpdateResult> {\n // Parse both DocIds at the controller boundary so malformed values\n // surface as helpful diagnostics. The replacement DocId is parsed\n // for validation only — we never dereference it (D-03).\n const oldId = parseDocId(args.doc_id);\n parseDocId(args.replacement_doc_id);\n\n // Identify the OLD doc's owning vault from its authority component.\n const { authority: vaultName } = decomposeDocId(oldId);\n\n // Confirm OLD doc lives inside a memory sink. Supersede applies only\n // to memory documents; user notes are immutable from the agent's\n // perspective per the Phase 2 safety invariant.\n const sink = deps.memorySinkRegistry.findSinkContaining(oldId);\n if (sink === null) {\n throw new Error(\n `supersede() target ${oldId} is not inside any configured MemorySink; ` +\n `supersede applies to memory documents only.`,\n );\n }\n\n // Fetch the OLD doc's current Document.hash via the read-side seam.\n // This is the canonical content hash (distinct from\n // WriteSuccess.newHash) that the OCC contract consumes. We also use\n // the read result to merge the supersede triple onto the OLD doc's\n // existing property bag — the delivery chokepoint validator runs the\n // contract schema against the PATCH ALONE (per Plan 02-03's\n // conformance test 17: \"update() routes through the SAME validator\n // (missing observed_at refused)\"), so a minimal patch like\n // `{status, superseded_by, superseded_reason}` would falsely fail\n // missing_provenance on `source`/`observed_at`/etc. We therefore\n // hand the delivery a \"full\" patch — existing props with the three\n // supersede keys layered on top — so the on-disk frontmatter\n // semantics are unchanged (the delivery itself ALSO shallow-merges\n // with disk before writing, so this is idempotent), and the\n // validator's standalone schema check passes.\n const source = deps.sourceConnectorFor(vaultName);\n const oldDoc = await source.readDocument(oldId);\n\n // Strip the adapter-injected `wikilinks` array (D-05): obsidian-fs\n // surfaces wikilinks via `Document.properties.wikilinks` when\n // reading, but the field is never written back into frontmatter.\n // The delivery's `stripWikilinks` runs the same trim later, but\n // keeping the patch clean avoids a no-op diff in the merged set.\n const { wikilinks: _w, ...existingProps } = oldDoc.properties as { wikilinks?: unknown } & Record<\n string,\n unknown\n >;\n\n // Forward-only — writes ONLY on the OLD doc. The replacement doc is\n // never touched (D-03; back-link materialization is the Phase 4\n // graph layer's responsibility).\n const patch: Partial<Document> = {\n properties: {\n ...existingProps,\n status: \"superseded\",\n superseded_by: args.replacement_doc_id,\n superseded_reason: args.reason,\n },\n };\n\n return await deps.deliveryAdapterFor(vaultName).update(oldId, patch, {\n expectedHash: oldDoc.hash,\n sink: sink.handle,\n });\n}\n","/**\n * `handleRecall` — the MEM-03 controller.\n *\n * Retrieves memory documents from one or more labeled `MemorySinks`,\n * filtered by provenance (`min_confidence`, `types`, `max_age_days`)\n * and ranked by recency (`observed_at` DESC, `mtime` DESC tiebreak).\n * Returns the Phase 3 citation-packet floor: an 8-field\n * `CitationPacket` per result (D-01).\n *\n * Pipeline (per RESEARCH §Q7 — the recommended approach):\n *\n * 1. Resolve sinks: single sink (when args.sink set) or all configured.\n * 2. Run `searchHybrid` with a generous `top_k` (200) across the\n * sinks' owning vaults.\n * 3. Post-filter the candidates to those whose path begins with one\n * of the resolved sinks' `resolveToRelativePath` prefixes.\n * 4. De-duplicate by (vault, notePath) — a single doc may surface as\n * multiple chunks; we keep the best-scoring chunk's identity.\n * 5. Load each candidate's full `Document` via the SourceConnector\n * seam so we get the canonical `Document.hash` + full property\n * bag including provenance keys.\n * 6. Apply filters in this exact order (CONTEXT.md D-01):\n * a. Hide `status: \"superseded\"` (always; opt-in retrieval is\n * Phase 3 ASM-08 territory).\n * b. `min_confidence` — ordinal compare (direct=3, inferred=2,\n * uncertain=1).\n * c. `types` — exact match against `properties.type`.\n * d. `max_age_days` — `now - Date.parse(observed_at)` ≤ window.\n * 7. Sort `observed_at` DESC with `mtime` DESC tiebreak.\n * 8. Slice to `args.limit ?? 20` AFTER filter+sort (per D-01).\n * 9. Map each surviving Document → CitationPacket.\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. All access goes through the registry\n * (sink resolution), the SourceConnector (property reads via\n * `readDocument`), and the search service.\n *\n * Contingency (NOT shipped in Phase 2): if benchmarks ever show the\n * post-filter is too slow on a large vault, the user-approved fallback\n * is to add an optional `include_paths?: string[]` parameter to\n * `search_hybrid` and pass the sinks' resolved path prefixes. That\n * change is purely additive (does not break v1.x callers). Phase 2\n * ships the post-filter approach; the fallback is documented here for\n * the day the benchmark requires it.\n */\n\nimport type { SourceConnector } from \"../../adapters/source/types.js\";\nimport { decomposeDocId, formatDocId } from \"../../adapters/registry.js\";\nimport type { Document, SearchHit } from \"../../types.js\";\nimport type { Vault, VaultManager } from \"../../vault/index.js\";\nimport type { MemorySinkRegistry } from \"../registry.js\";\nimport { type CitationPacket, displayUrlFor, toCitationPacket } from \"../citation-packet.js\";\n\n/** Default limit when the caller does not specify one. */\nconst DEFAULT_LIMIT = 20;\n/** Generous top_k for the inner hybrid search; post-filter narrows. */\nconst RECALL_HYBRID_TOP_K = 200;\n\n/**\n * Input shape for the inner `searchHybrid` call. Mirrors the subset of\n * Phase 1's `HybridSearchOptions` that recall actually uses; passed as\n * a closure rather than imported directly so unit tests can stub.\n */\nexport interface RecallSearchHybridInput {\n query: string;\n vaults: readonly Vault[];\n topK: number;\n}\n\nexport interface RecallDeps {\n memorySinkRegistry: MemorySinkRegistry;\n manager: VaultManager;\n /** Resolve the `SourceConnector` instance for a vault name. */\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n /** Hybrid-search entry point. Bootstrap supplies the production closure. */\n searchHybrid: (input: RecallSearchHybridInput) => Promise<SearchHit[]>;\n}\n\nexport interface RecallArgs {\n query: string;\n min_confidence?: \"direct\" | \"inferred\" | \"uncertain\";\n types?: string[];\n max_age_days?: number;\n sink?: string;\n limit?: number;\n vaults?: string[];\n}\n\n/** Ordinal rank for `confidence`. Unknown / undefined → 0. */\nfunction confidenceRank(c?: string): number {\n switch (c) {\n case \"direct\":\n return 3;\n case \"inferred\":\n return 2;\n case \"uncertain\":\n return 1;\n default:\n return 0;\n }\n}\n\n/**\n * Coerce a property value into an `observed_at` ISO timestamp string\n * suitable for both `Date.parse` (for age math) and string-comparison\n * sort (lexicographic ISO ordering).\n *\n * YAML frontmatter can surface ISO-8601 timestamps as either:\n * - JS `Date` objects (when js-yaml / gray-matter parses canonical\n * ISO strings via the `tag:yaml.org,2002:timestamp` rule), or\n * - raw strings (when quoted or schema-coerced).\n *\n * Returns `null` when the value is missing or unparseable. Callers use\n * `null` as the signal to drop the doc (a doc without a parseable\n * `observed_at` cannot be ranked by recency).\n */\nfunction observedAtIso(value: unknown): string | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString();\n }\n if (typeof value === \"string\") {\n const t = Date.parse(value);\n return Number.isNaN(t) ? null : new Date(t).toISOString();\n }\n return null;\n}\n\n/**\n * Retrieve memory docs as citation packets. See the file header for\n * the full pipeline; this function is the public entry point.\n */\nexport async function handleRecall(deps: RecallDeps, args: RecallArgs): Promise<CitationPacket[]> {\n // 1) Resolve sinks. Throws on unknown name — the server wraps the\n // exception in errorResponse() at the dispatch boundary.\n const sinks = args.sink\n ? [deps.memorySinkRegistry.resolveMemorySink(args.sink)]\n : deps.memorySinkRegistry.listMemorySinks();\n if (sinks.length === 0) return [];\n\n // 2) Compute the set of vaults to search: the distinct sink.vault\n // values, optionally intersected with args.vaults.\n const sinkVaultNames = new Set(sinks.map((s) => s.vault));\n const allowedVaultNames = args.vaults\n ? new Set(args.vaults.filter((v) => sinkVaultNames.has(v)))\n : sinkVaultNames;\n if (allowedVaultNames.size === 0) return [];\n\n const vaults: Vault[] = [];\n for (const name of allowedVaultNames) {\n vaults.push(deps.manager.require(name));\n }\n\n // 3) Inner hybrid search with a generous top_k.\n const candidates = await deps.searchHybrid({\n query: args.query,\n vaults,\n topK: RECALL_HYBRID_TOP_K,\n });\n\n // 4) Post-filter to sink-resolved paths. A candidate matches a sink\n // iff hit.vault === sink.vault AND hit.notePath starts with\n // sink.resolveToRelativePath (which already carries a trailing\n // slash by the MemorySinkHandle invariant).\n const sinkMatchers = sinks\n .filter((s) => allowedVaultNames.has(s.vault))\n .map((s) => ({ vault: s.vault, prefix: s.resolveToRelativePath }));\n const inSink = candidates.filter((hit) =>\n sinkMatchers.some((m) => hit.vault === m.vault && hit.notePath.startsWith(m.prefix)),\n );\n\n // 5) De-duplicate by (vault, notePath) — a doc can produce multiple\n // chunk hits; we keep the highest-scoring chunk's metadata.\n const uniqueByPath = new Map<string, SearchHit>();\n for (const hit of inSink) {\n const key = `${hit.vault}::${hit.notePath}`;\n const existing = uniqueByPath.get(key);\n if (!existing || hit.score > existing.score) {\n uniqueByPath.set(key, hit);\n }\n }\n if (uniqueByPath.size === 0) return [];\n\n // 6) Load full Documents via the source seam for canonical hash +\n // full property bag (including the provenance keys we need to\n // filter on).\n const docs: Document[] = [];\n for (const hit of uniqueByPath.values()) {\n const docId = formatDocId(\"obsidian-fs\", hit.vault, hit.notePath);\n try {\n const doc = await deps.sourceConnectorFor(hit.vault).readDocument(docId);\n docs.push(doc);\n } catch {\n // A search hit pointing to a now-deleted file is harmless;\n // silently drop it. (Watcher catch-up usually keeps the index\n // in sync, but we don't fail the whole call on one stale row.)\n }\n }\n\n // 7) Apply provenance filters in the documented order.\n const now = Date.now();\n const minRank = args.min_confidence ? confidenceRank(args.min_confidence) : 0;\n const typeSet = args.types && args.types.length > 0 ? new Set(args.types) : null;\n const maxAgeMs = args.max_age_days !== undefined ? args.max_age_days * 86_400_000 : null;\n\n const filtered = docs.filter((doc) => {\n const props = (doc.properties ?? {}) as Record<string, unknown>;\n // 7a) Hide superseded by default.\n if (props.status === \"superseded\") return false;\n // 7b) min_confidence ordinal compare.\n if (minRank > 0) {\n const docConf = typeof props.confidence === \"string\" ? props.confidence : undefined;\n if (confidenceRank(docConf) < minRank) return false;\n }\n // 7c) types exact match.\n if (typeSet) {\n const t = typeof props.type === \"string\" ? props.type : undefined;\n if (t === undefined || !typeSet.has(t)) return false;\n }\n // 7d) max_age_days against observed_at.\n if (maxAgeMs !== null) {\n const iso = observedAtIso(props.observed_at);\n if (iso === null) return false;\n if (now - Date.parse(iso) > maxAgeMs) return false;\n }\n return true;\n });\n\n // 8) Sort: observed_at DESC, mtime DESC tiebreak.\n filtered.sort((a, b) => {\n const ao = observedAtIso((a.properties as Record<string, unknown>)?.observed_at) ?? \"\";\n const bo = observedAtIso((b.properties as Record<string, unknown>)?.observed_at) ?? \"\";\n if (ao !== bo) {\n // ISO-8601 strings sort lexicographically when both well-formed.\n return ao < bo ? 1 : -1;\n }\n return b.mtime - a.mtime;\n });\n\n // 9) Truncate AFTER sort (per D-01).\n const limit = args.limit ?? DEFAULT_LIMIT;\n const top = filtered.slice(0, limit);\n\n // 10) Map each surviving Document → CitationPacket. The display URL\n // is computed via the source adapter's `formatDisplayUrl` seam\n // (ADR-002 §SourceConnector) — recall does not encode adapter-\n // specific URL conventions inline.\n return top.map((doc) => {\n const { authority: vaultName } = decomposeDocId(doc.id);\n const source = deps.sourceConnectorFor(vaultName);\n return toCitationPacket(doc, displayUrlFor(doc.id, source));\n });\n}\n","/**\n * Barrel for the memory MCP-tool controllers.\n *\n * Plan 02-04 ships:\n * - `handleRecordObservation` — MEM-02 controller (record_observation tool).\n * - `handleSupersede` — MEM-04 controller (supersede tool).\n *\n * Plan 02-05 adds:\n * - `handleRecall` — MEM-03 controller (recall tool).\n */\n\nexport { handleRecordObservation } from \"./record-observation.js\";\nexport type { RecordObservationArgs, RecordObservationDeps } from \"./record-observation.js\";\n\nexport { handleSupersede } from \"./supersede.js\";\nexport type { SupersedeArgs, SupersedeDeps } from \"./supersede.js\";\n\nexport { handleRecall } from \"./recall.js\";\nexport type { RecallArgs, RecallDeps, RecallSearchHybridInput } from \"./recall.js\";\n","/**\n * Branded `ChunkId` for the public Phase 5 / D-04 chunk-identifier.\n *\n * Format: `<DocId>#chunk-<fragment>` where `<fragment>` is the 7-hex\n * output of `computeChunkIdFragment` (`src/chunker/chunk-id.ts`).\n *\n * Mirrors the IIFE-closed branding idiom from\n * `src/adapters/registry.ts:67-94` — the only validating parser is\n * exported; the raw brand-cast (`mint`) is closed inside the IIFE so\n * arbitrary strings cannot reach the brand without passing the regex\n * check.\n *\n * Pure module. No fs / gray-matter / chokidar / path imports.\n */\n\nimport type { DocId } from \"../types.js\";\nimport type { ChunkId } from \"../types.js\";\n\n/**\n * `chunk_id_fragment` shape: exactly 7 lowercase hex characters.\n * Matches the slice from `computeChunkHash(text).slice(7, 14)`.\n */\nconst FRAGMENT_REGEX = /^[0-9a-f]{7}$/;\n\n/**\n * Public ChunkId shape: `<scheme>://<authority>/<resource>#chunk-<frag>`.\n *\n * The DocId prefix is validated structurally here (lowercase scheme,\n * non-empty authority + resource); the full DocId-pattern test lives\n * in `src/adapters/registry.ts`. Both must accept the same DocId space.\n */\nconst CHUNK_ID_REGEX = /^([a-z][a-z0-9-]*:\\/\\/[^/]+\\/.+)#chunk-([0-9a-f]{7})$/;\n\nconst { parseChunkId, formatChunkId, decomposeChunkId } = (() => {\n const mint = (s: string): ChunkId => s as ChunkId;\n\n function format(docId: DocId, fragment: string): ChunkId {\n if (!FRAGMENT_REGEX.test(fragment)) {\n throw new Error(\n `Invalid chunk fragment: ${JSON.stringify(fragment)}. ` +\n \"Expected exactly 7 lowercase hex characters (per ADR-005 / D-04).\",\n );\n }\n return mint(`${docId}#chunk-${fragment}`);\n }\n\n function parse(s: string): ChunkId {\n if (!CHUNK_ID_REGEX.test(s)) {\n throw new Error(\n `Invalid ChunkId: ${JSON.stringify(s)}. ` + \"Expected <DocId>#chunk-<7-hex-fragment>.\",\n );\n }\n return mint(s);\n }\n\n function decompose(id: ChunkId): { docId: DocId; fragment: string } {\n const m = CHUNK_ID_REGEX.exec(id);\n if (!m) {\n // Branding guarantees this branch is unreachable in well-typed\n // code, but a defensive check costs nothing.\n throw new Error(`Malformed ChunkId reached decomposeChunkId: ${JSON.stringify(id)}`);\n }\n return { docId: m[1] as DocId, fragment: m[2]! };\n }\n\n return { parseChunkId: parse, formatChunkId: format, decomposeChunkId: decompose };\n})();\n\nexport { parseChunkId, formatChunkId, decomposeChunkId };\nexport type { ChunkId };\n","/**\n * Phase 5 — brief `source_hashes` builder and recompute helper.\n *\n * `source_hashes: Record<ChunkId, BriefSourceHash>` is the staleness\n * contract per ADR-005 §\"Chunk-level source_hashes (ChunkId)\". The\n * brief carries one entry per cited chunk; the daemon walks\n * `brief_sources` (D-06 reverse-index) on a ChangeEvent and compares\n * `recorded_hash` to the current `computeChunkHash(text)` — divergence\n * flips the brief to `status: stale` with `changed_sources` populated.\n *\n * Pure module. The chunker helper (`src/chunker/chunk-id.ts`) is the\n * single source of truth for the hash; we re-export it here so the\n * `src/brief/` barrel is the one-stop import surface for brief\n * consumers. No fs / gray-matter / chokidar / path imports.\n */\n\nimport { computeChunkHash, computeChunkIdFragment } from \"../chunker/chunk-id.js\";\nimport { formatChunkId } from \"./chunk-id.js\";\nimport type { ChunkId } from \"../types.js\";\nimport type { BriefSourceHash, DocId } from \"../types.js\";\n\n// Re-export the canonical chunk-hash + chunk-id-fragment functions so\n// brief consumers can import everything they need from `src/brief/`.\n// The originals live in `src/chunker/chunk-id.ts` — there is exactly\n// one implementation site for the canonicalization algorithm.\nexport { computeChunkHash, computeChunkIdFragment };\n\n/**\n * Per-chunk input shape for `buildSourceHashes`. The brief layer\n * resolves source DocIds to chunks via the existing notes→chunks join\n * (the in-process resolver lives in slice 2, alongside `compile_brief`);\n * the helper here is intentionally decoupled from the DB so it can be\n * unit-tested against pure inputs.\n */\nexport interface ChunkSource {\n /** DocId of the document containing this chunk. */\n docId: DocId;\n /** 7-hex fragment from the `chunks.chunk_id_fragment` column. */\n fragment: string;\n /** Canonical chunk text (already pulled from `chunks.text`). */\n text: string;\n}\n\n/**\n * Build the `source_hashes` map for a brief. For each chunk in\n * `sources`, format the public ChunkId and compute the full\n * `\"sha256:<hex>\"` hash recorded at brief-compile time.\n *\n * Consumers (slice 2 `compile_brief`) resolve `sources` from\n * `source_doc_ids` via the notes+chunks join then pass the result here.\n * Keeping the DB join out of this module preserves the pure-function\n * discipline and lets the eval harness exercise the contract with\n * deterministic fixtures.\n */\nexport function buildSourceHashes(\n sources: readonly ChunkSource[],\n): Record<ChunkId, BriefSourceHash> {\n const out: Record<ChunkId, BriefSourceHash> = {};\n for (const chunk of sources) {\n const chunkId = formatChunkId(chunk.docId, chunk.fragment);\n out[chunkId] = computeChunkHash(chunk.text) as BriefSourceHash;\n }\n return out;\n}\n\n/**\n * Recompute the current hash for one chunk's canonical text. The\n * daemon uses this on each `ChangeEvent` to compare against\n * `brief_sources.recorded_hash` for an O(log N) staleness check.\n */\nexport function recomputeCurrentHash(text: string): BriefSourceHash {\n return computeChunkHash(text) as BriefSourceHash;\n}\n","/**\n * Phase 5 / D-10 — Capability-first LLM ladder for `compile_brief`.\n *\n * Resolves which LLM strategy a given `compile_brief` call should use,\n * in priority order:\n *\n * 1. MCP Sampling — `server.server.getClientCapabilities().sampling`\n * is present (host MCP client supports `sampling/create_message`).\n * 2. Local Ollama — `[brief.ollama] model = \"...\"` is set in\n * `config.toml` (the per-server `BriefConfig` block).\n * 3. Caller-supplied `prepared_text` — vault-memory stitches the\n * caller's verbatim text into the brief body.\n * 4. Structured error — `BriefLlmUnavailableError` carrying the\n * `attempted` array. The controller (`handleCompileBrief`)\n * translates this to `{ok: false, reason: \"no_llm_strategy_available\",\n * attempted, hint}` so the caller can choose its recovery path\n * (configure Ollama, switch to a sampling-capable client, or pass\n * prepared_text).\n *\n * `compileWithLlm` dispatches the resolved strategy and returns\n * `{body, model}`. MCP Sampling result content is a single discriminated\n * union block; we reject anything other than `type === \"text\"`.\n *\n * # Why server-level (not per-vault) Ollama config\n *\n * Slice 1 (Plan 05-01) added the `[brief]` block onto `AppConfig`, not\n * `VaultConfig`. The brief subsystem is a single LLM ladder shared by\n * all vaults the server hosts; per-vault Ollama config would let one\n * vault's brief compile bypass the server's licensed local LLM endpoint\n * without an obvious audit point. The ladder therefore consumes\n * `briefConfig?: BriefConfig` from `AppConfig`, threaded through the\n * controller's `Deps`.\n *\n * Pure module. No fs / gray-matter / chokidar / path imports.\n */\n\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n CreateMessageResult,\n CreateMessageResultWithTools,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { BriefConfig } from \"../types.js\";\nimport type { OllamaClient } from \"../ollama/client.js\";\n\n/**\n * Discriminated union returned by `resolveLlmStrategy`. The shape lets\n * `compileWithLlm` switch-dispatch without re-checking capabilities.\n */\nexport type LlmStrategy =\n | { kind: \"sampling\" }\n | { kind: \"ollama\"; model: string }\n | { kind: \"prepared_text\" }\n | { kind: \"unavailable\"; attempted: string[] };\n\n/**\n * Structured error emitted when no ladder tier resolves. The controller\n * catches this and translates to the `no_llm_strategy_available` MCP\n * tool error envelope.\n */\nexport class BriefLlmUnavailableError extends Error {\n public readonly attempted: string[];\n constructor(attempted: string[]) {\n super(`LLM unavailable; attempted: ${attempted.join(\", \")}`);\n this.name = \"BriefLlmUnavailableError\";\n this.attempted = attempted;\n }\n}\n\n/**\n * Translated when the MCP Sampling client refuses (throws). The\n * controller maps this to `{ok: false, reason: \"sampling_refused\"}`.\n * Kept distinct from `BriefLlmUnavailableError` so callers can branch\n * on it (refusal → retry later; unavailable → reconfigure).\n */\nexport class BriefLlmSamplingRefusedError extends Error {\n public override readonly cause: unknown;\n constructor(cause: unknown) {\n super(\"MCP Sampling refused\");\n this.name = \"BriefLlmSamplingRefusedError\";\n this.cause = cause;\n }\n}\n\n/**\n * Capability-first ladder resolution.\n *\n * `server` is the high-level `McpServer` (we read `.server.getClientCapabilities()`);\n * the test fixtures stand up a minimal stub of the same shape.\n *\n * `briefConfig` is the server-level `[brief]` block from `AppConfig`.\n * Undefined / missing `ollama.model` means tier 2 skips.\n */\nexport function resolveLlmStrategy(\n server: McpServer,\n briefConfig: BriefConfig | undefined,\n preparedText: string | undefined,\n): LlmStrategy {\n const attempted: string[] = [];\n\n // Tier 1: MCP Sampling capability — populated after the MCP initialize\n // handshake. Server bootstrap ordering guarantees compile_brief calls\n // always run post-handshake, so this read is safe.\n const caps = server.server.getClientCapabilities();\n if (caps?.sampling) {\n return { kind: \"sampling\" };\n }\n attempted.push(\"sampling\");\n\n // Tier 2: per-server Ollama config (`[brief.ollama] model = \"...\"`).\n const ollamaModel = briefConfig?.ollama?.model;\n if (typeof ollamaModel === \"string\" && ollamaModel.length > 0) {\n return { kind: \"ollama\", model: ollamaModel };\n }\n attempted.push(\"ollama\");\n\n // Tier 3: caller-supplied prepared_text.\n if (typeof preparedText === \"string\" && preparedText.length > 0) {\n return { kind: \"prepared_text\" };\n }\n attempted.push(\"prepared_text\");\n\n // Tier 4: structured error — `BriefLlmUnavailableError` at dispatch.\n return { kind: \"unavailable\", attempted };\n}\n\n/**\n * Tier dispatch. Returns the raw LLM body plus a `model` identifier\n * for audit-trail attribution. The body still needs to pass the D-11\n * `BriefBodyValidator` before delivery.write.\n *\n * Tier 1 wraps `server.server.createMessage` throws into\n * `BriefLlmSamplingRefusedError`; tier 2 lets `OllamaClient` errors\n * percolate (the controller wraps in try/catch and returns the\n * underlying error semantics unchanged).\n */\nexport async function compileWithLlm(\n strategy: LlmStrategy,\n server: McpServer,\n ollama: OllamaClient,\n prompt: { systemText: string; userText: string },\n maxTokens: number,\n preparedText?: string,\n): Promise<{ body: string; model: string }> {\n switch (strategy.kind) {\n case \"sampling\": {\n let result: CreateMessageResult | CreateMessageResultWithTools;\n try {\n result = await server.server.createMessage({\n messages: [\n {\n role: \"user\",\n content: { type: \"text\", text: prompt.userText },\n },\n ],\n maxTokens,\n systemPrompt: prompt.systemText,\n });\n } catch (err) {\n throw new BriefLlmSamplingRefusedError(err);\n }\n // `CreateMessageResult.content` is a single discriminated block;\n // the brief compile path only handles text. (The tool-enabled\n // overload returns an array, but we never pass `tools`.)\n const content = (result as CreateMessageResult).content;\n if (content === undefined || Array.isArray(content) || content.type !== \"text\") {\n const got =\n content === undefined ? \"undefined\" : Array.isArray(content) ? \"array\" : content.type;\n throw new Error(\n `MCP Sampling returned non-text content (type=${got}); brief compile expects text.`,\n );\n }\n return { body: content.text, model: result.model };\n }\n case \"ollama\": {\n const res = await ollama.chat({\n model: strategy.model,\n messages: [\n { role: \"system\", content: prompt.systemText },\n { role: \"user\", content: prompt.userText },\n ],\n options: { num_predict: maxTokens },\n });\n return { body: res.message.content, model: strategy.model };\n }\n case \"prepared_text\": {\n // Caller's text is stitched verbatim. The controller already\n // verified `preparedText` is a non-empty string in\n // `resolveLlmStrategy`; we re-check defensively here so a\n // misuse (calling compileWithLlm with kind:\"prepared_text\"\n // but no text) surfaces loudly instead of writing an empty body.\n if (typeof preparedText !== \"string\" || preparedText.length === 0) {\n throw new Error(\n \"compileWithLlm(prepared_text) called without a non-empty preparedText string\",\n );\n }\n return { body: preparedText, model: \"prepared_text\" };\n }\n case \"unavailable\": {\n throw new BriefLlmUnavailableError(strategy.attempted);\n }\n }\n}\n","/**\n * Phase 5 / D-11 — Brief body validator.\n *\n * Every brief carries a body that the LLM ladder produced. The\n * Phase 4 D-02 indexer materializes typed `wikilink` edges by parsing\n * `[[Title]]` references during the next index pass. To guarantee the\n * brief participates in the graph layer (so `expand` / `cluster` /\n * `list_backlinks` surface it), every cited source must appear in the\n * body as a wikilink — `[[Title]]` (preferred), `[[Title|alias]]`,\n * `[[Title#heading]]`, or `[[<DocId>]]` (escape hatch).\n *\n * `validateAndPatchBody` is pure:\n * - Parses every `[[...]]` reference using the SAME regex Phase 4's\n * `src/indexer/extract-edges.ts` uses (any drift would break\n * `back-edge materialization`).\n * - Resolves each source DocId to a `Title` via `resolveTitle`.\n * - Collects DocIds that are NOT referenced (neither as title nor\n * as bare DocId).\n * - Appends `\\n\\n## Sources\\n- [[Title]]` per missing entry. The\n * footer is deliberately a markdown section so it round-trips\n * through gray-matter / js-yaml without semantic loss.\n *\n * Body validators that succeed return the body unchanged (byte-stable\n * — no whitespace insertion). Validators that patch return the\n * original body plus the footer; the LLM output is never mutated\n * mid-body.\n *\n * Pure module. No fs / gray-matter / chokidar / path imports.\n */\n\nimport type { DocId } from \"../types.js\";\n\n/**\n * Wikilink regex matching `[[Title]]`, `[[Title|alias]]`,\n * `[[Title#heading]]`, `[[Title#heading|alias]]`. Mirrors the pattern\n * `src/indexer/extract-edges.ts` uses so Phase 4 indexer back-edges\n * stay consistent.\n *\n * Note: the capture group extracts the bare title (everything before\n * `|` or `#`); the validator compares this against `resolveTitle(id)`\n * AND against the raw `id` (DocId escape hatch).\n */\nconst WIKILINK_RE = /\\[\\[([^\\]|#]+)(?:#[^\\]|]+)?(?:\\|[^\\]]+)?\\]\\]/g;\n\n/**\n * Validate the brief body for D-11 compliance; if any source is\n * missing a wikilink, append a `## Sources` footer naming the missing\n * entries.\n *\n * `resolveTitle(id)` returns the canonical title for a DocId — used\n * both for matching and for the patched footer. The controller threads\n * this through `(id) => vault.db.notes.getByPath(resource)?.title ?? id`.\n */\nexport function validateAndPatchBody(\n body: string,\n sourceDocIds: readonly DocId[],\n resolveTitle: (id: DocId) => string,\n): string {\n // Collect every wikilink target from the body (titles only — alias\n // and heading suffix are stripped by the regex group). Track BOTH\n // the raw match AND the trimmed match so callers can include\n // titles with trailing whitespace without surprise.\n const cited = new Set<string>();\n for (const m of body.matchAll(WIKILINK_RE)) {\n const target = m[1]?.trim();\n if (target !== undefined && target.length > 0) cited.add(target);\n }\n\n // For each source, accept any one of:\n // - the resolved title (e.g. \"Atlas-1\")\n // - the bare DocId (escape hatch — the LLM may emit\n // `[[obsidian-fs://vault/notes/atlas-1.md]]` when it doesn't\n // know the human title).\n const missing: DocId[] = [];\n for (const id of sourceDocIds) {\n const title = resolveTitle(id);\n if (cited.has(title) || cited.has(id)) continue;\n missing.push(id);\n }\n\n if (missing.length === 0) return body;\n\n const footerLines = missing.map((id) => `- [[${resolveTitle(id)}]]`);\n const footer = `\\n\\n## Sources\\n${footerLines.join(\"\\n\")}\\n`;\n return body + footer;\n}\n","/**\n * `handleCompileBrief` — the BRF-03 controller.\n *\n * Compiles a brief from caller-supplied source DocIds and writes it\n * through `DeliveryAdapter.write` into `_memory/_briefs/`. The full\n * pipeline (per ADR-005 §\"compile_brief\"):\n *\n * 1. Resolve target vault + brief sink (defaults to `_memory/_briefs`).\n * 2. Validate input: dedupe `source_doc_ids`, enforce ≤50 cap (D-03),\n * gate cross-vault sources (Open Q3 RESOLVED — every source\n * DocId's `authority` MUST equal the target vault).\n * 3. Resolve sources to chunks via the notes+chunks DB join and build\n * `source_hashes` via `buildSourceHashes` (slice 1).\n * 4. Resolve the LLM strategy (D-10 ladder): MCP Sampling → Ollama →\n * `prepared_text` → structured error.\n * 5. Build prompt; dispatch to the resolved tier; capture\n * `BriefLlmSamplingRefusedError` → `{ok:false, reason:\n * \"sampling_refused\"}`.\n * 6. Validate body wikilinks (D-11): append `## Sources` footer for\n * any cited DocId missing a `[[Title]]` reference. Phase 4 D-02\n * indexer materializes back-edges on the next pass.\n * 7. Mint timestamped slug `{target}--YYYYMMDDTHHmm.md`; check for\n * existing brief with the same `target` (status !== \"superseded\")\n * → capture `oldDocId` for D-12 supersede chain.\n * 8. Build the brief Document with the `default-brief-v1` property\n * bag (slice 1's contract).\n * 9. `delivery.write(newDocId, briefDoc, {sink: briefSink.handle})`\n * — the validator at the chokepoint runs schema + sentinel checks.\n * 10. Populate `brief_sources` reverse-index (one row per chunk in\n * every source doc).\n * 11. If `oldDocId` was captured, call `handleSupersede` to mark the\n * prior brief superseded (forward-only D-03 invariant).\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. All file access goes through the\n * `SourceConnector` + `DeliveryAdapter` seams.\n */\n\nimport type { DeliveryAdapter } from \"../adapters/delivery/types.js\";\nimport { decomposeDocId, formatDocId, parseDocId } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport type { OllamaClient } from \"../ollama/client.js\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { BriefConfig, DocId, Document } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\nimport type { MemorySinkRegistry } from \"../memory/registry.js\";\nimport { handleSupersede } from \"../memory/tools/supersede.js\";\nimport { buildSourceHashes, type ChunkSource } from \"./source-hashes.js\";\nimport {\n BriefLlmSamplingRefusedError,\n BriefLlmUnavailableError,\n compileWithLlm,\n resolveLlmStrategy,\n} from \"./llm-ladder.js\";\nimport { validateAndPatchBody } from \"./body-validator.js\";\n\n/** ADR-005 D-03 hard cap; lifted only at planner discretion. */\nconst MAX_SOURCES = 50;\n\n/** Default sink name for briefs; the user may override via `args.sink`. */\nconst DEFAULT_BRIEF_SINK_NAME = \"_memory/_briefs\";\n\n/**\n * Dependencies — supplied by the server bootstrap. Pure interface so\n * tests can wire fakes without touching the file system seam.\n */\nexport interface CompileBriefDeps {\n memorySinkRegistry: MemorySinkRegistry;\n manager: VaultManager;\n deliveryAdapterFor: (vaultName: string) => DeliveryAdapter;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n /**\n * The high-level `McpServer` — the ladder reads\n * `.server.getClientCapabilities()` + `.server.createMessage()`\n * through this handle (Tier 1).\n */\n server: McpServer;\n /** OllamaClient instance (Tier 2 of the D-10 ladder). */\n ollama: OllamaClient;\n /**\n * Server-level `[brief]` block from `AppConfig`. The ladder reads\n * `briefConfig.ollama.model` to decide if Tier 2 is reachable;\n * undefined means tier 2 skips.\n */\n briefConfig: BriefConfig | undefined;\n}\n\nexport interface CompileBriefArgs {\n vault: string;\n /** Stable, vault-relative target slug (e.g. `\"atlas-q3\"`). */\n target: string;\n /** Source DocIds the brief is compiled from; deduped, capped at 50. */\n source_doc_ids: string[];\n /** Free-form purpose; 1..500 chars (validated at Zod gate). */\n purpose: string;\n /** Hint for the LLM ladder; default 2000 tokens. */\n max_tokens?: number;\n /** D-10 tier 3 fallback: verbatim body when no LLM is reachable. */\n prepared_text?: string;\n /** Override the default `_memory/_briefs` sink. */\n sink?: string;\n /** Optional override for the slug timestamp (test-only determinism). */\n _now?: Date;\n}\n\nexport type CompileBriefResult =\n | { ok: true; doc_id: string; supersededPrior?: string; model?: string }\n | {\n ok: false;\n reason: \"no_llm_strategy_available\";\n attempted: string[];\n hint: string;\n }\n | { ok: false; reason: \"too_many_sources\"; limit: number; hint: string }\n | { ok: false; reason: \"cross_vault_sources\"; offending: string[] }\n | { ok: false; reason: \"sampling_refused\"; message?: string }\n | { ok: false; reason: \"write_failed\"; message?: string };\n\n/**\n * Compact ISO slug `YYYYMMDDTHHmm` for the `{target}--<slug>.md` mint\n * (D-12). Stable, sortable, file-system-safe.\n */\nfunction compactIso(date: Date): string {\n // YYYY-MM-DDTHH:mm:ss.sssZ → YYYYMMDDTHHmm\n return date.toISOString().replace(/[-:.]/g, \"\").slice(0, 13);\n}\n\n/**\n * Resolve the brief sink: caller-supplied `args.sink` wins, otherwise\n * default sink-name `_memory/_briefs`. Throws (via registry) if the\n * sink is unknown — surfaced to the MCP caller as an error response.\n */\nfunction resolveBriefSink(deps: CompileBriefDeps, sinkArg: string | undefined) {\n const name = sinkArg ?? DEFAULT_BRIEF_SINK_NAME;\n return deps.memorySinkRegistry.resolveMemorySink(name);\n}\n\n/**\n * Build the `ChunkSource[]` array for `buildSourceHashes` by joining\n * the notes + chunks tables. Source DocIds that resolve to no row are\n * silently dropped — the LLM still cites them by DocId/title; the\n * `brief_sources` reverse-index just has nothing to track. Production\n * call sites compile against indexed docs so this branch only matters\n * for unit-test fakes.\n */\nfunction resolveSourcesToChunks(vault: Vault, docIds: readonly DocId[]): ChunkSource[] {\n const out: ChunkSource[] = [];\n for (const docId of docIds) {\n const { resource } = decomposeDocId(docId);\n const note = vault.db.notes.getByPath(resource);\n if (!note) continue;\n const chunks = vault.db.chunks.getByNote(note.id);\n for (const chunk of chunks) {\n out.push({\n docId,\n fragment: chunk.chunk_id_fragment,\n text: chunk.text,\n });\n }\n }\n return out;\n}\n\n/**\n * Lookup an existing brief for `target` via SourceConnector enumeration.\n * Returns the FIRST non-superseded match by listing order; if multiple\n * non-superseded briefs share the same `target`, the forward-only\n * supersede invariant has been violated upstream and we log a structured\n * warning (via audit). For Slice 2 we proceed with the newest by\n * `compiled_at` and document the WARN in the SUMMARY.\n */\nasync function findBriefByTarget(\n source: SourceConnector,\n briefSinkPrefix: string,\n vaultName: string,\n target: string,\n): Promise<Document | null> {\n // listDocuments yields refs; we readDocument each one and inspect\n // properties. Limit is broad because brief sinks are small; the\n // listing is filtered by path prefix to skip unrelated _memory/ docs.\n const candidates: Document[] = [];\n for await (const ref of source.listDocuments()) {\n const { resource } = decomposeDocId(ref.id);\n if (!resource.startsWith(briefSinkPrefix)) continue;\n let doc: Document;\n try {\n doc = await source.readDocument(ref.id);\n } catch {\n continue;\n }\n const props = doc.properties as Record<string, unknown>;\n if (props.target !== target) continue;\n if (props.status === \"superseded\") continue;\n candidates.push(doc);\n }\n if (candidates.length === 0) return null;\n if (candidates.length === 1) return candidates[0]!;\n // Pick the newest by compiled_at. This branch should not happen\n // under the forward-only invariant.\n candidates.sort((a, b) => {\n const ai = a.properties.compiled_at as string | undefined;\n const bi = b.properties.compiled_at as string | undefined;\n return (bi ?? \"\").localeCompare(ai ?? \"\");\n });\n // Suppress unused-variable lint; vaultName parameter reserved for\n // future audit logging when the duplicate-active-briefs branch fires.\n void vaultName;\n return candidates[0]!;\n}\n\n/**\n * Resolve a DocId to a human title for the body validator + footer.\n * Falls back to the bare DocId when the notes table has no row.\n */\nfunction makeTitleResolver(vault: Vault): (id: DocId) => string {\n return (id: DocId): string => {\n try {\n const { resource } = decomposeDocId(id);\n const row = vault.db.notes.getByPath(resource);\n if (row?.title) return row.title;\n } catch {\n // fall through\n }\n return id;\n };\n}\n\n/**\n * Compile a brief. Returns the success / failure discriminated union;\n * `WriteConflict` from the delivery adapter surfaces as `write_failed`\n * (with the original message preserved) — the underlying conflict is\n * recoverable at the caller layer if needed.\n */\nexport async function handleCompileBrief(\n deps: CompileBriefDeps,\n args: CompileBriefArgs,\n): Promise<CompileBriefResult> {\n const vault = deps.manager.require(args.vault);\n const vaultName = vault.config.name;\n\n // ── 1. Resolve brief sink ─────────────────────────────────────────\n const briefSink = resolveBriefSink(deps, args.sink);\n if (briefSink.vault !== vaultName) {\n throw new Error(\n `Brief sink \"${briefSink.name}\" belongs to vault \"${briefSink.vault}\", not \"${vaultName}\"`,\n );\n }\n\n // ── 2. Validate args: dedupe + cap + cross-vault gate ─────────────\n const dedupedRaw = Array.from(new Set(args.source_doc_ids));\n if (dedupedRaw.length > MAX_SOURCES) {\n return {\n ok: false,\n reason: \"too_many_sources\",\n limit: MAX_SOURCES,\n hint: `Pass at most ${MAX_SOURCES} source_doc_ids. Use cluster() or expand() to narrow the corpus.`,\n };\n }\n\n const parsedSourceDocIds: DocId[] = [];\n const offending: string[] = [];\n for (const raw of dedupedRaw) {\n let parsed: DocId;\n try {\n parsed = parseDocId(raw);\n } catch {\n offending.push(raw);\n continue;\n }\n const { authority } = decomposeDocId(parsed);\n if (authority !== vaultName) {\n offending.push(raw);\n continue;\n }\n parsedSourceDocIds.push(parsed);\n }\n if (offending.length > 0) {\n return { ok: false, reason: \"cross_vault_sources\", offending };\n }\n\n // ── 3. Build source_hashes via slice-1 helper ─────────────────────\n const chunkSources = resolveSourcesToChunks(vault, parsedSourceDocIds);\n const sourceHashes = buildSourceHashes(chunkSources);\n\n // ── 4. Resolve LLM strategy ───────────────────────────────────────\n const strategy = resolveLlmStrategy(deps.server, deps.briefConfig, args.prepared_text);\n if (strategy.kind === \"unavailable\") {\n return {\n ok: false,\n reason: \"no_llm_strategy_available\",\n attempted: strategy.attempted,\n hint: \"Configure [brief.ollama] in config.toml, use a sampling-capable MCP client, or pass prepared_text.\",\n };\n }\n\n // ── 5. Build prompt + dispatch ────────────────────────────────────\n const titleOf = makeTitleResolver(vault);\n const citations = parsedSourceDocIds.map((id) => `- [[${titleOf(id)}]] (${id})`).join(\"\\n\");\n const systemText =\n \"You are compiling a concise, evidence-grounded brief from the source documents below. \" +\n \"Emit `[[Title]]` wikilinks for each cited source so the knowledge graph indexes the brief. \" +\n \"Do not invent attendees, dates, decisions, or numbers — ground every claim in the sources.\";\n const userText =\n `Purpose: ${args.purpose}\\n\\n` +\n `Sources:\\n${citations}\\n\\n` +\n `Compile the brief now. Cite every source as a [[wikilink]] at least once.`;\n\n let rawBody: string;\n let model: string;\n try {\n const compiled = await compileWithLlm(\n strategy,\n deps.server,\n deps.ollama,\n { systemText, userText },\n args.max_tokens ?? 2000,\n args.prepared_text,\n );\n rawBody = compiled.body;\n model = compiled.model;\n } catch (err) {\n if (err instanceof BriefLlmSamplingRefusedError) {\n return {\n ok: false,\n reason: \"sampling_refused\",\n message: err.message,\n };\n }\n if (err instanceof BriefLlmUnavailableError) {\n // resolveLlmStrategy already short-circuited the unavailable\n // case; this branch fires only on programmer error (e.g. a stub\n // strategy threading through). Surface it as the same structured\n // error so the caller has one branch to handle.\n return {\n ok: false,\n reason: \"no_llm_strategy_available\",\n attempted: err.attempted,\n hint: \"Configure [brief.ollama] in config.toml, use a sampling-capable MCP client, or pass prepared_text.\",\n };\n }\n throw err;\n }\n\n // ── 6. Validate body wikilinks (D-11) ─────────────────────────────\n const body = validateAndPatchBody(rawBody, parsedSourceDocIds, titleOf);\n\n // ── 7. Mint new DocId + check for existing brief on target ────────\n const now = args._now ?? new Date();\n const slug = compactIso(now);\n const briefRelative = `${briefSink.resolveToRelativePath}${args.target}--${slug}.md`;\n const newDocId = formatDocId(\"obsidian-fs\", vaultName, briefRelative);\n\n const source = deps.sourceConnectorFor(vaultName);\n const existing = await findBriefByTarget(\n source,\n briefSink.resolveToRelativePath,\n vaultName,\n args.target,\n );\n const oldDocId = existing?.id ?? null;\n\n // ── 8. Build the brief Document ───────────────────────────────────\n const nowIso = now.toISOString();\n const properties: Record<string, unknown> = {\n source: \"agent\",\n confidence: \"inferred\",\n evidence: parsedSourceDocIds.slice(),\n status: \"active\",\n observed_at: nowIso,\n superseded_by: null,\n type: \"brief\",\n target: args.target,\n purpose: args.purpose,\n compiled_from: parsedSourceDocIds.slice(),\n compiled_at: nowIso,\n source_hashes: sourceHashes,\n // Audit-trail attribution — which LLM tier produced the body.\n // The value is whatever the LLM tier returned verbatim (the host\n // MCP client's model identifier, the Ollama model name, or the\n // sentinel string \"prepared_text\"). Per ADR-005 §\"Provenance\" the\n // audit log carries the model name.\n model,\n };\n const title = `${args.target} brief`;\n const briefDoc: Partial<Document> = {\n id: newDocId,\n title,\n properties,\n blocks: [{ kind: \"paragraph\", text: body }],\n };\n\n // ── 9. Write through DeliveryAdapter ──────────────────────────────\n const delivery = deps.deliveryAdapterFor(vaultName);\n const writeRes = await delivery.write(newDocId, briefDoc, {\n sink: briefSink.handle,\n });\n if (!writeRes.ok) {\n return {\n ok: false,\n reason: \"write_failed\",\n message: writeRes.message ?? `Delivery refused brief write: reason=${writeRes.reason}`,\n };\n }\n\n // ── 10. Populate brief_sources reverse-index ──────────────────────\n const sourceRows = chunkSources.map((cs) => ({\n chunkIdFragment: cs.fragment,\n chunkDocId: cs.docId,\n recordedHash: sourceHashes[\n `${cs.docId}#chunk-${cs.fragment}` as keyof typeof sourceHashes\n ] as string,\n }));\n if (sourceRows.length > 0) {\n vault.db.briefSources.insertBatch(newDocId, sourceRows);\n }\n\n // ── 11. D-12 supersede chain on target collision ──────────────────\n if (oldDocId !== null) {\n await handleSupersede(\n {\n memorySinkRegistry: deps.memorySinkRegistry,\n manager: deps.manager,\n deliveryAdapterFor: deps.deliveryAdapterFor,\n sourceConnectorFor: deps.sourceConnectorFor,\n },\n {\n doc_id: oldDocId,\n replacement_doc_id: newDocId,\n reason: \"recompiled\",\n },\n );\n return {\n ok: true,\n doc_id: newDocId,\n supersededPrior: oldDocId,\n model,\n };\n }\n\n return { ok: true, doc_id: newDocId, model };\n}\n","/**\n * `handleGetBrief` — the BRF-04 controller.\n *\n * Looks up a brief by target slug and applies the D-13 decision tree:\n *\n * - **Staleness dominates.** If the brief's `status === \"stale\"` and\n * the caller did not opt in via `allow_stale: true`, return\n * `{brief: null, stale: true, ...}` so the caller knows to\n * recompile.\n * - **Age is independent.** Even on a non-stale brief, if\n * `max_age_days` is set and the brief's `compiled_at` is older\n * than that window AND `allow_stale: false`, return\n * `{brief: null, too_old: true, ...}`.\n * - **Follow the supersede chain.** If the looked-up brief carries\n * `status: \"superseded\"` with a non-null `superseded_by`, follow\n * the chain via `SourceConnector.readDocument` until a terminal\n * brief is reached (or a cycle is detected — defensive 100-hop\n * cap, see Phase 2 D-03 forward-only supersede invariant).\n *\n * The \"not_found\" case is its own branch so callers can differentiate\n * \"no brief exists for this target\" from \"exists but stale/too_old\".\n *\n * Pure controller — no `node:fs`, no `node:path`, no `gray-matter`,\n * no `chokidar`. Everything goes through `SourceConnector.listDocuments`\n * + `readDocument`.\n */\n\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport { decomposeDocId, parseDocId } from \"../adapters/registry.js\";\nimport type { Document } from \"../types.js\";\nimport type { VaultManager } from \"../vault/index.js\";\nimport type { MemorySinkRegistry } from \"../memory/registry.js\";\n\n/** Defensive cycle guard for the supersede chain (forward-only invariant). */\nconst MAX_SUPERSEDE_HOPS = 100;\n\n/** Default sink name for briefs; the user may override via `args.sink`. */\nconst DEFAULT_BRIEF_SINK_NAME = \"_memory/_briefs\";\n\nexport interface GetBriefDeps {\n memorySinkRegistry: MemorySinkRegistry;\n manager: VaultManager;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\nexport interface GetBriefArgs {\n vault: string;\n target: string;\n max_age_days?: number;\n allow_stale?: boolean;\n sink?: string;\n}\n\nexport type GetBriefResult =\n | { brief: Document; stale: false; too_old: false; age_days: number }\n | {\n brief: Document;\n stale: true;\n too_old: boolean;\n age_days: number;\n changed_sources: string[];\n }\n | {\n brief: Document;\n stale: false;\n too_old: true;\n age_days: number;\n }\n | {\n brief: null;\n stale: true;\n too_old?: boolean;\n changed_sources: string[];\n reason: \"stale_blocked\";\n }\n | {\n brief: null;\n stale: false;\n too_old: true;\n age_days: number;\n reason: \"too_old_blocked\";\n }\n | { brief: null; reason: \"not_found\" };\n\n/**\n * Enumerate `_memory/_briefs/` and find the FIRST brief whose\n * `properties.target` matches. Skips superseded briefs at the listing\n * pass — the supersede chain is followed below by the controller.\n */\nasync function findBriefByTarget(\n source: SourceConnector,\n briefSinkPrefix: string,\n target: string,\n): Promise<Document | null> {\n const candidates: Document[] = [];\n for await (const ref of source.listDocuments()) {\n const { resource } = decomposeDocId(ref.id);\n if (!resource.startsWith(briefSinkPrefix)) continue;\n let doc: Document;\n try {\n doc = await source.readDocument(ref.id);\n } catch {\n continue;\n }\n const props = doc.properties as Record<string, unknown>;\n if (props.target !== target) continue;\n if (props.status === \"superseded\") continue;\n candidates.push(doc);\n }\n if (candidates.length === 0) return null;\n if (candidates.length === 1) return candidates[0]!;\n // Forward-only invariant violation — pick newest by compiled_at and\n // proceed; observability lands when audit-log integration follows.\n candidates.sort((a, b) => {\n const ai = a.properties.compiled_at as string | undefined;\n const bi = b.properties.compiled_at as string | undefined;\n return (bi ?? \"\").localeCompare(ai ?? \"\");\n });\n return candidates[0]!;\n}\n\n/**\n * Walk the `superseded_by` chain forward until we hit a terminal brief\n * (status !== \"superseded\" or superseded_by is null) or the cycle\n * guard trips. Returns the terminal Document.\n */\nasync function followSupersedeChain(source: SourceConnector, start: Document): Promise<Document> {\n let current = start;\n let hops = 0;\n while (current.properties.status === \"superseded\") {\n const nextRaw = current.properties.superseded_by;\n if (nextRaw === null || nextRaw === undefined) break;\n if (typeof nextRaw !== \"string\") break;\n if (++hops > MAX_SUPERSEDE_HOPS) {\n throw new Error(\n `get_brief supersede chain exceeded ${MAX_SUPERSEDE_HOPS} hops; ` +\n `target chain rooted at ${start.id}. Indicates a forward-only ` +\n `invariant violation upstream (Phase 2 D-03).`,\n );\n }\n const nextId = parseDocId(nextRaw);\n let next: Document;\n try {\n next = await source.readDocument(nextId);\n } catch {\n // Broken chain — return what we have.\n break;\n }\n current = next;\n }\n return current;\n}\n\nfunction ageDaysFor(brief: Document): number {\n const compiledAt = brief.properties.compiled_at;\n if (typeof compiledAt !== \"string\") return Number.POSITIVE_INFINITY;\n const parsed = Date.parse(compiledAt);\n if (Number.isNaN(parsed)) return Number.POSITIVE_INFINITY;\n return Math.floor((Date.now() - parsed) / 86_400_000);\n}\n\nfunction changedSourcesFor(brief: Document): string[] {\n const raw = brief.properties.changed_sources;\n if (!Array.isArray(raw)) return [];\n return raw.filter((x): x is string => typeof x === \"string\");\n}\n\n/**\n * Look up a brief by target and apply D-13. See file header.\n */\nexport async function handleGetBrief(\n deps: GetBriefDeps,\n args: GetBriefArgs,\n): Promise<GetBriefResult> {\n const vault = deps.manager.require(args.vault);\n const vaultName = vault.config.name;\n\n // Resolve the brief sink so we know which path prefix to enumerate.\n const briefSink = deps.memorySinkRegistry.resolveMemorySink(args.sink ?? DEFAULT_BRIEF_SINK_NAME);\n if (briefSink.vault !== vaultName) {\n throw new Error(\n `Brief sink \"${briefSink.name}\" belongs to vault \"${briefSink.vault}\", not \"${vaultName}\"`,\n );\n }\n\n const source = deps.sourceConnectorFor(vaultName);\n const found = await findBriefByTarget(source, briefSink.resolveToRelativePath, args.target);\n if (found === null) {\n return { brief: null, reason: \"not_found\" };\n }\n\n // Follow the supersede chain to the terminal (defensive — the\n // `findBriefByTarget` pass already filters out superseded briefs,\n // but a brief returned here that carries `superseded_by` non-null\n // means a non-superseded-status row exists with a redirect, which\n // is unusual but possible in mid-flight states).\n const terminal = await followSupersedeChain(source, found);\n\n const ageDays = ageDaysFor(terminal);\n const status = terminal.properties.status;\n const stale = status === \"stale\";\n const tooOld =\n args.max_age_days !== undefined && Number.isFinite(ageDays) && ageDays > args.max_age_days;\n const allowStale = args.allow_stale === true;\n\n if (stale && !allowStale) {\n return {\n brief: null,\n stale: true,\n ...(tooOld ? { too_old: true as const } : {}),\n changed_sources: changedSourcesFor(terminal),\n reason: \"stale_blocked\",\n };\n }\n\n if (tooOld && !allowStale) {\n return {\n brief: null,\n stale: false,\n too_old: true,\n age_days: ageDays,\n reason: \"too_old_blocked\",\n };\n }\n\n if (stale) {\n return {\n brief: terminal,\n stale: true,\n too_old: tooOld,\n age_days: ageDays,\n changed_sources: changedSourcesFor(terminal),\n };\n }\n\n if (tooOld) {\n return {\n brief: terminal,\n stale: false,\n too_old: true,\n age_days: ageDays,\n };\n }\n\n return {\n brief: terminal,\n stale: false,\n too_old: false,\n age_days: ageDays,\n };\n}\n","// vault-memory:claude-ok — process state (~/.vault-memory/locks/) not vault content.\n// See ADR-005 §\"Lockfile carve-out\" for the rationale.\n//\n// This file is the ONLY exemption to scripts/lint-adapters.sh in Phase 5.\n// Adapter-seam discipline (no fs/path.join outside src/adapters/*/) does NOT\n// apply: lockfiles are process state managed by ~/.vault-memory/, not user\n// vault content. Per D-08 + ADR-005.\n\n/**\n * Phase 5 / D-08 — `~/.vault-memory/locks/<vault>.lock` single-owner\n * primitive for the staleness daemon.\n *\n * Atomic exclusive create via `fs.open(path, 'wx')` (POSIX `O_WRONLY |\n * O_CREAT | O_EXCL`). On EEXIST, read the recorded PID; if dead\n * (POSIX `kill(pid, 0)` throws ESRCH), steal the lock — otherwise\n * return contended.\n *\n * Multi-MCP-client friendly per CONTEXT D-08: second `vault-memory\n * serve` against the same vault boots normally; only the daemon\n * subscription is gated.\n *\n * No `node:fs` imports outside this file in `src/brief/`.\n */\n\nimport { open, readFile, unlink, mkdir } from \"node:fs/promises\"; // vault-memory:claude-ok\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\"; // vault-memory:claude-ok\n\nexport interface LockAcquired {\n acquired: true;\n pid: number;\n path: string;\n stolenFromPid?: number;\n}\n\nexport interface LockContended {\n acquired: false;\n ownerPid: number;\n path: string;\n}\n\nexport type LockResult = LockAcquired | LockContended;\n\n/**\n * Override the lock directory for tests so the real\n * `~/.vault-memory/locks/` is never touched during the test suite.\n * Test-only — production call sites omit the argument.\n */\nfunction lockDir(rootOverride?: string): string {\n if (rootOverride !== undefined) return join(rootOverride, \"locks\");\n return join(homedir(), \".vault-memory\", \"locks\");\n}\n\nfunction lockPath(vaultName: string, rootOverride?: string): string {\n return join(lockDir(rootOverride), `${vaultName}.lock`);\n}\n\n/** POSIX `kill(pid, 0)`: returns true if pid is alive, false on ESRCH. */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // ESRCH means no such process. EPERM means alive but inaccessible.\n if ((err as NodeJS.ErrnoException).code === \"ESRCH\") return false;\n // Defensive: any other error treated as alive (we won't steal).\n return true;\n }\n}\n\nasync function readOwnerPid(path: string): Promise<number | null> {\n try {\n const buf = await readFile(path, \"utf8\");\n const pid = parseInt(buf.trim(), 10);\n return Number.isFinite(pid) && pid > 0 ? pid : null;\n } catch {\n return null;\n }\n}\n\nexport interface AcquireLockOptions {\n /**\n * Test-only override for the `~/.vault-memory/` root. Production\n * call sites omit. When set, the lock lives at\n * `<root>/locks/<vault>.lock`.\n */\n rootOverride?: string;\n}\n\n/**\n * Try to acquire the lock for a vault.\n * Atomic create via `fs.open(path, 'wx')` (`O_WRONLY | O_CREAT | O_EXCL`).\n * On EEXIST: read current owner PID; if dead (ESRCH) or malformed,\n * steal the lock; else return contended.\n */\nexport async function tryAcquireLock(\n vaultName: string,\n options: AcquireLockOptions = {},\n): Promise<LockResult> {\n const dir = lockDir(options.rootOverride);\n await mkdir(dir, { recursive: true });\n const path = lockPath(vaultName, options.rootOverride);\n\n // Defensive: bound recursion so a hostile / racing peer can't loop\n // us. Two retries is plenty (steal once, then acquire on the next).\n const MAX_ATTEMPTS = 3;\n\n const attempt = async (n: number, stolenFromPid?: number): Promise<LockResult> => {\n if (n > MAX_ATTEMPTS) {\n // Treat as contended with an unknown owner; caller logs WARN.\n return { acquired: false, ownerPid: stolenFromPid ?? -1, path };\n }\n try {\n const handle = await open(path, \"wx\");\n try {\n await handle.writeFile(`${process.pid}\\n`);\n } finally {\n await handle.close();\n }\n const result: LockAcquired = { acquired: true, pid: process.pid, path };\n if (stolenFromPid !== undefined) result.stolenFromPid = stolenFromPid;\n return result;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n const ownerPid = await readOwnerPid(path);\n if (ownerPid === null || !isProcessAlive(ownerPid)) {\n // Stale lock (or malformed contents): unlink and retry.\n await unlink(path).catch(() => undefined);\n return attempt(n + 1, ownerPid ?? -1);\n }\n return { acquired: false, ownerPid, path };\n }\n };\n\n return attempt(1);\n}\n\n/** Release the lock. Safe to call even if we don't hold it. */\nexport async function releaseLock(\n vaultName: string,\n options: AcquireLockOptions = {},\n): Promise<void> {\n await unlink(lockPath(vaultName, options.rootOverride)).catch(() => undefined);\n}\n","/**\n * Phase 5 / BRF-05/06/07/08 — `BriefStalenessDaemon`.\n *\n * In-process daemon that subscribes to the same `ChangeFeed` as the\n * `VaultWatcher` and flips affected briefs to `status: \"stale\"` when\n * chunk-hash divergence is observed.\n *\n * # Lifecycle (mirrors `VaultWatcher.start/stop`)\n *\n * 1. `start(vault, feed, deps)`:\n * a. acquire `~/.vault-memory/locks/<vault>.lock`; on contention\n * log structured WARN to stderr + audit (`daemon_already_owned`)\n * and return early — second-server boots fine without a daemon.\n * b. read `daemon_state.last_seen_doc_mtime` cursor (diagnostic).\n * c. run a startup full scan over `brief_sources.listBriefDocIds()`\n * and mark divergent briefs stale (D-09 correctness floor).\n * d. subscribe to the feed for create/update/delete/rename events.\n *\n * 2. handler — on each ChangeEvent:\n * - create/update → `evaluateChangedDocId(id)` (recompute hashes,\n * flip divergent briefs stale via `delivery.update`).\n * - delete → record in pendingDeletes (5s grace-window); when\n * the grace-window expires without a matching create, mark\n * briefs stale with reason `\"source_deleted\"`.\n * - rename — adapter-native rename → update\n * `brief_sources.chunk_doc_id` in place (BRF-08 preserve\n * brief→source links).\n *\n * 3. `shutdown()` — dispose subscription FIRST, then releaseLock LAST.\n * A crashed shutdown that fails to release the lock leaves it for\n * `kill(pid, 0)` stale-detection to recover.\n *\n * # Anti-Pattern 2 — never direct DB writes\n *\n * Brief staleness writes route through `delivery.update(briefId, patch,\n * {expectedHash, sink})` so the MEM-05 validator runs at the\n * `DeliveryAdapter` chokepoint (`default-brief-v1` permits\n * `status: \"stale\"` per slice 1 contract). Direct\n * `vault.db.notes.upsert(...)` would bypass the validator AND the\n * existing watcher suppression-set hook → Pitfall 3.\n *\n * # Adapter-seam discipline\n *\n * Zero `fs` / `path.join` / `gray-matter` / `chokidar` imports here.\n * The daemon delegates to `lock.ts` (the only lockfile carve-out) for\n * `~/.vault-memory/locks/` access; everything else routes through the\n * `DeliveryAdapter` / `SourceConnector` / `ChangeFeed` seams.\n */\n\nimport type { ChangeEvent, ChangeFeed, Disposable } from \"../adapters/change-feed/types.js\";\nimport type { DeliveryAdapter } from \"../adapters/delivery/types.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport { decomposeDocId } from \"../adapters/registry.js\";\nimport type { MemorySinkRegistry } from \"../memory/index.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { DocId, Document } from \"../types.js\";\nimport { recomputeCurrentHash } from \"./source-hashes.js\";\nimport { releaseLock, tryAcquireLock } from \"./lock.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\n/** Default sink name for briefs. Mirrors compile.ts / get.ts. */\nconst DEFAULT_BRIEF_SINK_NAME = \"_memory/_briefs\";\n\n/**\n * 5-second grace-window for rename survival (BRF-08). chokidar surfaces\n * a true OS-level rename as `unlink + add`; this window correlates the\n * pair by matching chunk hash sets.\n */\nconst RENAME_GRACE_MS = 5_000;\n\n/**\n * Defensive hop cap for shutdown-period grace-window expiry — should\n * never fire in normal operation.\n */\nconst MAX_EXPIRE_PER_TICK = 1024;\n\nexport interface DaemonDeps {\n memorySinkRegistry: MemorySinkRegistry;\n deliveryAdapterFor: (vaultName: string) => DeliveryAdapter;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n /** Optional override for the lock root (tests inject mkdtemp dir). */\n lockRootOverride?: string;\n /** Optional override for the brief sink name (defaults to _memory/_briefs). */\n briefSinkName?: string;\n /** Optional logger; defaults to stderr writer. */\n log?: (msg: string) => void;\n /** Optional clock override for tests (default `Date.now`). */\n now?: () => number;\n}\n\ninterface PendingDelete {\n id: DocId;\n /** Set of full chunk hashes (`\"sha256:...\"`) captured at delete time. */\n chunkHashes: Set<string>;\n timestamp: number;\n}\n\nexport interface DaemonStartResult {\n acquired: boolean;\n ownerPid?: number;\n}\n\nexport class BriefStalenessDaemon {\n private disposable: Disposable | null = null;\n private vault: Vault | null = null;\n private deps: DaemonDeps | null = null;\n private acquired = false;\n private readonly pendingDeletes = new Map<DocId, PendingDelete>();\n private now: () => number = Date.now;\n private log: (msg: string) => void = (m) => process.stderr.write(`[brief-daemon] ${m}\\n`);\n\n /**\n * Acquire the per-vault lock, run the startup scan, subscribe to\n * the feed. Multi-MCP-client friendly: returns\n * `{acquired: false, ownerPid}` on lock contention WITHOUT\n * subscribing or throwing — the second server boots normally.\n */\n async start(vault: Vault, feed: ChangeFeed, deps: DaemonDeps): Promise<DaemonStartResult> {\n this.vault = vault;\n this.deps = deps;\n if (deps.now) this.now = deps.now;\n if (deps.log) this.log = deps.log;\n\n const lockOpts =\n deps.lockRootOverride !== undefined ? { rootOverride: deps.lockRootOverride } : {};\n const lock = await tryAcquireLock(vault.config.name, lockOpts);\n if (!lock.acquired) {\n // D-08: structured WARN + return early. The lock-contention path\n // is a NORMAL multi-MCP-client outcome, not an error. We log to\n // stderr in a structured (single-line JSON) shape so external\n // collectors can parse it. Audit-log integration uses the\n // `audit.recordWrite` shape — but that table is `write_audit`\n // (per-note write history); a daemon-ownership event does not\n // bind to a note row, so we emit stderr only.\n const payload = JSON.stringify({\n kind: \"daemon_already_owned\",\n vault: vault.config.name,\n ownerPid: lock.ownerPid,\n path: lock.path,\n });\n this.log(`WARN ${payload}`);\n return { acquired: false, ownerPid: lock.ownerPid };\n }\n this.acquired = true;\n // Diagnostic: capture starting cursor (slice 1 D-09 cursor table).\n // The startup scan is the correctness floor regardless of cursor\n // value, but we log the value so operators can compare against\n // the post-scan cursor to verify the daemon is current.\n const startCursor = vault.db.daemonState.getCursor(vault.config.name);\n this.log(`start vault=${vault.config.name} startCursor=${startCursor ?? \"null\"}`);\n\n // ── Startup full scan (D-09 correctness floor) ─────────────────\n await this.runStartupScan();\n\n // ── Subscribe to ChangeFeed (D-07) ─────────────────────────────\n this.disposable = feed.subscribe(async (event: ChangeEvent) => {\n try {\n await this.handleEvent(event);\n vault.db.daemonState.setCursor(vault.config.name, this.now());\n } catch (err) {\n const message = errorMessage(err);\n const payload = JSON.stringify({\n kind: \"brief_staleness_error\",\n vault: vault.config.name,\n event_kind: event.kind,\n event_id: \"id\" in event ? event.id : null,\n message,\n });\n this.log(`ERROR ${payload}`);\n }\n });\n\n // Set initial cursor.\n vault.db.daemonState.setCursor(vault.config.name, this.now());\n return { acquired: true };\n }\n\n /**\n * Force any pending grace-window deletes to expire and propagate.\n * Test hook + shutdown-flush helper.\n */\n async drainPending(): Promise<void> {\n await this.expireGraceWindow(true);\n }\n\n async shutdown(): Promise<void> {\n // Dispose subscription FIRST so no more events arrive mid-shutdown.\n if (this.disposable) {\n this.disposable[Symbol.dispose]();\n this.disposable = null;\n }\n // Release lock LAST. A crashed shutdown that fails here leaves the\n // lock for `kill(pid, 0)` stale-detection (lock.ts) to recover.\n if (this.acquired && this.vault && this.deps) {\n const lockOpts =\n this.deps.lockRootOverride !== undefined\n ? { rootOverride: this.deps.lockRootOverride }\n : {};\n await releaseLock(this.vault.config.name, lockOpts);\n this.acquired = false;\n }\n }\n\n /** True iff the daemon currently owns the lock (test hook). */\n get isOwner(): boolean {\n return this.acquired;\n }\n\n // ────────────────────────────────────────────────────────────────────\n // Internal — handlers\n // ────────────────────────────────────────────────────────────────────\n\n private async runStartupScan(): Promise<void> {\n const vault = this.requireVault();\n const briefIds = vault.db.briefSources.listBriefDocIds();\n for (const briefId of briefIds) {\n try {\n await this.evaluateBrief(briefId as DocId);\n } catch (err) {\n const message = errorMessage(err);\n const payload = JSON.stringify({\n kind: \"brief_staleness_error\",\n vault: vault.config.name,\n phase: \"startup_scan\",\n brief_id: briefId,\n message,\n });\n this.log(`ERROR ${payload}`);\n }\n }\n }\n\n private async handleEvent(event: ChangeEvent): Promise<void> {\n // Always tick the grace-window expiries at the start of each event\n // so pending deletes propagate even when the next event is itself\n // a non-matching create on a different doc.\n await this.expireGraceWindow(false);\n\n switch (event.kind) {\n case \"create\":\n await this.handleCreate(event.id);\n break;\n case \"update\":\n await this.evaluateChangedDocId(event.id);\n break;\n case \"delete\":\n await this.handleDelete(event.id);\n break;\n case \"rename\":\n await this.handleRenameDirect(event.old_id, event.new_id);\n break;\n }\n }\n\n /**\n * For each brief that cites `docId`, re-evaluate its source_hashes\n * and flip the brief stale if any chunk diverges (or sources were\n * removed entirely).\n */\n private async evaluateChangedDocId(docId: DocId): Promise<void> {\n const vault = this.requireVault();\n const affected = vault.db.briefSources.briefsForChunkDoc(docId);\n const briefIds = new Set(affected.map((a) => a.briefDocId));\n for (const briefId of briefIds) {\n await this.evaluateBrief(briefId as DocId);\n }\n }\n\n /**\n * Read the brief Document, walk its `brief_sources` rows, and\n * compare each `recorded_hash` to the current chunk hash. On\n * divergence, call `delivery.update` to flip status → \"stale\".\n *\n * Errors per-brief are caught + logged; the loop never crashes.\n */\n private async evaluateBrief(briefId: DocId): Promise<void> {\n const vault = this.requireVault();\n const deps = this.requireDeps();\n\n const sources = vault.db.briefSources.sourcesForBrief(briefId);\n if (sources.length === 0) return; // Brief was never recorded.\n\n // Recompute the current hash for each cited chunk.\n const currentHashes = new Map<string, string | null>();\n for (const row of sources) {\n const key = `${row.chunkDocId}#${row.chunkIdFragment}`;\n if (currentHashes.has(key)) continue;\n try {\n const { resource } = decomposeDocId(row.chunkDocId as DocId);\n const note = vault.db.notes.getByPath(resource);\n if (!note) {\n currentHashes.set(key, null); // Source doc disappeared.\n continue;\n }\n const chunks = vault.db.chunks.getByNote(note.id);\n const found = chunks.find((c) => c.chunk_id_fragment === row.chunkIdFragment);\n if (!found) {\n currentHashes.set(key, null); // Chunk was renamed / deleted.\n continue;\n }\n currentHashes.set(key, recomputeCurrentHash(found.text));\n } catch {\n currentHashes.set(key, null);\n }\n }\n\n // Build the changed_sources list — unique DocIds whose any chunk\n // diverged or disappeared.\n const changedSourceIds = new Set<DocId>();\n for (const row of sources) {\n const key = `${row.chunkDocId}#${row.chunkIdFragment}`;\n const current = currentHashes.get(key);\n if (current === null || current !== row.recordedHash) {\n changedSourceIds.add(row.chunkDocId as DocId);\n }\n }\n\n if (changedSourceIds.size === 0) return;\n\n // Read the brief Document for its current hash + properties.\n const source = deps.sourceConnectorFor(vault.config.name);\n let briefDoc: Document;\n try {\n briefDoc = await source.readDocument(briefId);\n } catch (err) {\n const message = errorMessage(err);\n const payload = JSON.stringify({\n kind: \"brief_staleness_error\",\n vault: vault.config.name,\n brief_id: briefId,\n phase: \"read_brief\",\n message,\n });\n this.log(`ERROR ${payload}`);\n return;\n }\n\n // If the brief is already stale or superseded, skip the write —\n // re-flipping a stale brief would churn the suppression set.\n const currentStatus = briefDoc.properties.status;\n if (currentStatus === \"stale\" || currentStatus === \"superseded\") return;\n\n const briefSink = this.resolveBriefSink(vault.config.name);\n const delivery = deps.deliveryAdapterFor(vault.config.name);\n\n // Preserve existing properties; flip status + record changed_sources.\n const patchProperties: Record<string, unknown> = {\n ...briefDoc.properties,\n status: \"stale\",\n changed_sources: Array.from(changedSourceIds),\n };\n const updateRes = await delivery.update(\n briefId,\n { properties: patchProperties },\n {\n expectedHash: briefDoc.hash,\n sink: briefSink.handle,\n },\n );\n if (!updateRes.ok) {\n const payload = JSON.stringify({\n kind: \"brief_staleness_error\",\n vault: vault.config.name,\n brief_id: briefId,\n phase: \"update\",\n reason: updateRes.reason,\n message: updateRes.message,\n });\n this.log(`ERROR ${payload}`);\n }\n }\n\n /**\n * Delete handler — capture the deleted doc's chunk hashes into the\n * grace-window so a matching `create` can survive the link via\n * rename heuristic (BRF-08).\n */\n private async handleDelete(docId: DocId): Promise<void> {\n const vault = this.requireVault();\n // Capture the set of chunk hashes for this doc BEFORE the deletion\n // propagates through the indexer. We read from the chunks table\n // which is still populated at this point — the watcher's removeNote\n // happens on its own debounced flush, not synchronously with our\n // event handler.\n const chunkHashes = new Set<string>();\n try {\n const { resource } = decomposeDocId(docId);\n const note = vault.db.notes.getByPath(resource);\n if (note) {\n for (const chunk of vault.db.chunks.getByNote(note.id)) {\n chunkHashes.add(recomputeCurrentHash(chunk.text));\n }\n }\n } catch {\n // Doc may already be gone; we keep an empty set so the\n // grace-window will eventually expire and propagate as a \"real\"\n // delete (mark briefs stale).\n }\n this.pendingDeletes.set(docId, {\n id: docId,\n chunkHashes,\n timestamp: this.now(),\n });\n }\n\n /**\n * Create handler — look for a matching pendingDelete by chunk-hash\n * set; if found, rewrite `brief_sources.chunk_doc_id` from old → new\n * in place (BRF-08).\n */\n private async handleCreate(docId: DocId): Promise<void> {\n const vault = this.requireVault();\n // Compute the new doc's chunk hashes.\n const newHashes = new Set<string>();\n try {\n const { resource } = decomposeDocId(docId);\n const note = vault.db.notes.getByPath(resource);\n if (note) {\n for (const chunk of vault.db.chunks.getByNote(note.id)) {\n newHashes.add(recomputeCurrentHash(chunk.text));\n }\n }\n } catch {\n // If we can't read the new doc, skip the rename heuristic —\n // it's purely an optimization on top of the staleness fallback.\n return;\n }\n if (newHashes.size === 0) return;\n\n // Find a pending delete with the same chunk-hash set.\n for (const [oldId, pending] of this.pendingDeletes) {\n if (chunkSetMatch(pending.chunkHashes, newHashes)) {\n this.pendingDeletes.delete(oldId);\n // UPDATE brief_sources.chunk_doc_id = newId WHERE chunk_doc_id = oldId.\n // We use a low-level prepared statement against the same DB\n // handle the BriefSourcesQueries class uses, surfaced as a\n // dedicated method below to keep the SQL string in one place.\n rewriteBriefSourceDocId(vault, oldId, docId);\n return;\n }\n }\n }\n\n /**\n * Native rename handler — for adapters that surface `rename` events\n * directly. Today's obsidian-fs ChangeFeed emits delete+create\n * (`emitsRename: false`); this branch fires only when a future\n * adapter (notion-api, github-api) emits a real rename.\n */\n private async handleRenameDirect(oldId: DocId, newId: DocId): Promise<void> {\n const vault = this.requireVault();\n rewriteBriefSourceDocId(vault, oldId, newId);\n }\n\n /**\n * Walk the pendingDeletes map; for each entry older than the grace\n * window, treat as a real delete and mark its dependent briefs stale.\n */\n private async expireGraceWindow(force: boolean): Promise<void> {\n const cutoff = force ? Number.POSITIVE_INFINITY : RENAME_GRACE_MS;\n const nowMs = this.now();\n let processed = 0;\n for (const [id, pending] of this.pendingDeletes) {\n if (processed++ > MAX_EXPIRE_PER_TICK) break;\n if (force || nowMs - pending.timestamp >= cutoff) {\n this.pendingDeletes.delete(id);\n // Mark briefs stale with reason: \"source_deleted\".\n try {\n await this.evaluateChangedDocId(id);\n } catch (err) {\n const message = errorMessage(err);\n const vault = this.vault;\n const payload = JSON.stringify({\n kind: \"brief_staleness_error\",\n vault: vault?.config.name ?? \"unknown\",\n brief_id: id,\n phase: \"grace_expire\",\n message,\n });\n this.log(`ERROR ${payload}`);\n }\n }\n }\n }\n\n // ────────────────────────────────────────────────────────────────────\n\n private resolveBriefSink(vaultName: string) {\n const deps = this.requireDeps();\n const name = deps.briefSinkName ?? DEFAULT_BRIEF_SINK_NAME;\n const sink = deps.memorySinkRegistry.resolveMemorySink(name);\n if (sink.vault !== vaultName) {\n throw new Error(`Brief sink \"${name}\" belongs to vault \"${sink.vault}\", not \"${vaultName}\"`);\n }\n return sink;\n }\n\n private requireVault(): Vault {\n if (!this.vault) throw new Error(\"daemon used before start()\");\n return this.vault;\n }\n\n private requireDeps(): DaemonDeps {\n if (!this.deps) throw new Error(\"daemon used before start()\");\n return this.deps;\n }\n}\n\n/** Set equality for chunk-hash multisets. Order-independent. */\nfunction chunkSetMatch(a: Set<string>, b: Set<string>): boolean {\n if (a.size !== b.size) return false;\n for (const x of a) if (!b.has(x)) return false;\n return true;\n}\n\n/**\n * Rewrite every `brief_sources.chunk_doc_id` from `oldId` to `newId`.\n * Idempotent and INSERT-OR-IGNORE friendly. The dedicated method lives\n * here (not in `BriefSourcesQueries`) because the rename heuristic is\n * a daemon concern; the query class stays focused on read-side lookups.\n */\nfunction rewriteBriefSourceDocId(vault: Vault, oldId: DocId, newId: DocId): void {\n // We reach into the shared db handle to issue the UPDATE. The query\n // class doesn't ship a `updateChunkDocId` method (yet); doing it\n // here keeps the migration surface minimal. If a future slice\n // promotes this to a first-class API on BriefSourcesQueries, the\n // signature is already correct.\n // Note: the UNIQUE(brief_doc_id, chunk_id_fragment) constraint is\n // unaffected because we only change `chunk_doc_id` — not the unique\n // key columns.\n vault.db.handle\n .prepare(\n `UPDATE brief_sources\n SET chunk_doc_id = ?\n WHERE chunk_doc_id = ?`,\n )\n .run(newId, oldId);\n}\n","/**\n * `vault-memory://briefs` — MCP Resource enumerating compiled briefs\n * (Plan 05-04, BRF-09). Mirrors the structural analog\n * `src/memory/resources/list-sinks.ts`.\n *\n * Resource, not Tool: brief discovery is a read-only side-effect-free\n * enumeration surface. Agents that want to find briefs by target read\n * this URI instead of invoking a tool. Per CONTEXT D-Q4 this is\n * polled-only — no `notifyResourceUpdated` integration in v2.x.\n *\n * The handler is a pure function over (`MemorySinkRegistry`,\n * `VaultManager`, per-vault `SourceConnector`). It MUST NOT touch\n * `node:fs`, `node:path`, `gray-matter`, or `chokidar` — all reads\n * route through `SourceConnector.listDocuments` + `readDocument`.\n * `scripts/lint-adapters.sh` enforces this.\n *\n * Status surfacing:\n * The resource projects `properties.status` verbatim so agents see\n * `active`, `stale`, and `superseded` entries. Callers filter\n * client-side; the registry-style listing intentionally lets the\n * chain be inspectable (this matches the \"let agents see the chain\"\n * stance in the Phase 5 CONTEXT discretion).\n *\n * Source-count semantics:\n * `source_count` is the row count from `brief_sources` — the\n * reverse-index of record per ADR-005. It is independent of\n * `properties.compiled_from`; if the brief was compiled without\n * chunk-level sources, `source_count === 0` even though the brief\n * exists.\n */\n\nimport type { MemorySinkRegistry } from \"../memory/registry.js\";\nimport type { VaultManager } from \"../vault/index.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\n\n/** Default sink name for briefs; the caller may override via `opts.sink`. */\nconst DEFAULT_BRIEF_SINK_NAME = \"_memory/_briefs\";\n\nexport interface ListBriefEntry {\n /** Canonical brief DocId (e.g. `obsidian-fs://<vault>/_memory/_briefs/<slug>--<purpose>--<ts>.md`). */\n doc_id: string;\n /** Brief target slug (e.g. `\"atlas-q3\"`). */\n target: string;\n /** Free-form purpose recorded at compile time. */\n purpose: string;\n /** ISO-8601 compile timestamp (UTC, milliseconds precision). */\n compiled_at: string;\n /** Lifecycle status: `\"active\"`, `\"stale\"`, or `\"superseded\"`. */\n status: string;\n /** Number of source-chunk rows in `brief_sources` for this brief. */\n source_count: number;\n /** Days since `compiled_at` (`floor((now - compiled_at) / 86400000)`). */\n age_days: number;\n /** Owning vault name. */\n vault: string;\n}\n\nexport interface ListBriefsResource {\n /** Total number of briefs across all enumerated vaults (post-filter). */\n total: number;\n briefs: ListBriefEntry[];\n}\n\nexport interface ListBriefsOpts {\n /** Restrict enumeration to a single vault. */\n vault?: string;\n /** Substring filter applied to `properties.target` (case-sensitive). */\n target?: string;\n /** Override the default `_memory/_briefs` sink. */\n sink?: string;\n /** Test override; defaults to `Date.now()`. */\n _now?: number;\n}\n\nexport interface ListBriefsDeps {\n registry: MemorySinkRegistry;\n manager: VaultManager;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\n/**\n * Build the resource payload by enumerating each vault's brief sink\n * through `SourceConnector.listDocuments`, reading each candidate\n * document, and projecting briefs that satisfy `properties.type ===\n * \"brief\"`. Substring-filter on `properties.target` when\n * `opts.target` is set.\n */\nexport async function readListBriefs(\n deps: ListBriefsDeps,\n opts: ListBriefsOpts = {},\n): Promise<ListBriefsResource> {\n const sinkName = opts.sink ?? DEFAULT_BRIEF_SINK_NAME;\n const now = opts._now ?? Date.now();\n\n // Resolve which vaults to enumerate. When `opts.vault` is set we go\n // single-vault via `manager.require()` (which throws on unknown vault\n // — same contract as `handleGetBrief`). Otherwise we fan out over\n // every vault the manager knows about.\n const vaults =\n opts.vault !== undefined ? [deps.manager.require(opts.vault)] : deps.manager.list();\n\n const out: ListBriefEntry[] = [];\n for (const vault of vaults) {\n const vaultName = vault.config.name;\n // The brief sink might not be registered in every vault (the\n // sink-registry is per-vault). Skip vaults that have no brief\n // sink — they have no briefs to list.\n let resolveTo: string;\n try {\n const briefSink = deps.registry.resolveMemorySink(sinkName);\n if (briefSink.vault !== vaultName) continue;\n resolveTo = briefSink.resolveToRelativePath;\n } catch {\n continue;\n }\n\n const connector = deps.sourceConnectorFor(vaultName);\n for await (const ref of connector.listDocuments()) {\n // listDocuments returns DocumentRef; the path-prefix filter\n // applies to the resource portion of the DocId. We use a\n // substring check on the canonical id (`<scheme>://<auth>/<res>`)\n // since the brief sink's `resolveToRelativePath` is part of\n // the DocId resource segment. Cheap pre-filter before the\n // expensive `readDocument()` call.\n if (!String(ref.id).includes(`/${resolveTo}`)) continue;\n\n let doc;\n try {\n doc = await connector.readDocument(ref.id);\n } catch {\n // Tolerate transient read errors so the discovery surface\n // never crashes the resource read. The brief simply won't\n // show up; subsequent reads can retry.\n continue;\n }\n const props = doc.properties as Record<string, unknown>;\n if (props.type !== \"brief\") continue;\n const target = typeof props.target === \"string\" ? props.target : \"\";\n if (opts.target !== undefined && !target.includes(opts.target)) continue;\n\n const compiledAt = typeof props.compiled_at === \"string\" ? props.compiled_at : \"\";\n const purpose = typeof props.purpose === \"string\" ? props.purpose : \"\";\n const status = typeof props.status === \"string\" ? props.status : \"active\";\n const sourceCount = vault.db.briefSources.sourcesForBrief(doc.id).length;\n const compiledAtMs = compiledAt ? Date.parse(compiledAt) : NaN;\n const ageDays = Number.isNaN(compiledAtMs)\n ? Number.POSITIVE_INFINITY\n : Math.floor((now - compiledAtMs) / 86_400_000);\n\n out.push({\n doc_id: String(doc.id),\n target,\n purpose,\n compiled_at: compiledAt,\n status,\n source_count: sourceCount,\n age_days: ageDays,\n vault: vaultName,\n });\n }\n }\n\n return { total: out.length, briefs: out };\n}\n","/**\n * Phase 5 — `src/brief/` barrel.\n *\n * Wave 0 (Plan 05-01) — slice-1 exports only:\n * - canonical chunk-hash / fragment helpers (re-exported from the\n * chunker so brief consumers have one import surface);\n * - branded `ChunkId` + `parseChunkId` / `formatChunkId` /\n * `decomposeChunkId`;\n * - `buildSourceHashes` / `recomputeCurrentHash`.\n *\n * Later slices (05-02, 05-03, 05-04) extend this barrel with:\n * - `handleCompileBrief`, `handleGetBrief` (slice 2);\n * - `BriefBodyValidator`, `BriefStalenessDaemon`, lockfile (slice 3);\n * - `list_briefs` Resource (slice 4).\n *\n * No fs / gray-matter / chokidar / path imports in any slice-1 file\n * (`scripts/lint-adapters.sh` enforces).\n */\n\nexport { computeChunkHash, computeChunkIdFragment } from \"./source-hashes.js\";\nexport { buildSourceHashes, recomputeCurrentHash, type ChunkSource } from \"./source-hashes.js\";\nexport { parseChunkId, formatChunkId, decomposeChunkId, type ChunkId } from \"./chunk-id.js\";\n\n// ── Slice 2 (Plan 05-02) — LLM ladder + body validator ──────────────\nexport {\n resolveLlmStrategy,\n compileWithLlm,\n BriefLlmUnavailableError,\n BriefLlmSamplingRefusedError,\n type LlmStrategy,\n} from \"./llm-ladder.js\";\nexport { validateAndPatchBody } from \"./body-validator.js\";\nexport {\n handleCompileBrief,\n type CompileBriefArgs,\n type CompileBriefDeps,\n type CompileBriefResult,\n} from \"./compile.js\";\nexport {\n handleGetBrief,\n type GetBriefArgs,\n type GetBriefDeps,\n type GetBriefResult,\n} from \"./get.js\";\n\n// ── Slice 3 (Plan 05-03) — staleness daemon + lockfile primitive ────\nexport {\n tryAcquireLock,\n releaseLock,\n isProcessAlive,\n type LockResult,\n type LockAcquired,\n type LockContended,\n} from \"./lock.js\";\nexport { BriefStalenessDaemon, type DaemonDeps, type DaemonStartResult } from \"./daemon.js\";\n\n// ── Slice 4 (Plan 05-04) — list_briefs MCP Resource (BRF-09) ────────\nexport { readListBriefs } from \"./resources.js\";\nexport type {\n ListBriefsResource,\n ListBriefEntry,\n ListBriefsOpts,\n ListBriefsDeps,\n} from \"./resources.js\";\n","/**\n * `searchSections` — the ASM-03 controller.\n *\n * Section-level retrieval that COMPOSES (does not reimplement) the v1\n * chunk-level hybrid pipeline (`hybridSearch`) with a chunk → section\n * promotion step.\n *\n * Composition algorithm (per 03-RESEARCH.md §3 option 3):\n *\n * 1. Run `hybridSearch` with an inflated `topK = limit * 5`. The\n * multiplier is a cushion: a section may span 1..N chunks, so we\n * need enough chunk candidates that the top `limit` sections all\n * land in the post-promotion set.\n * 2. Promote each chunk hit to its enclosing section via\n * `findContainingChunk`. A chunk that does NOT map to any section\n * (legacy pre-migration-010 row, or a chunk whose section has\n * NULL `chunk_id_first`/`chunk_id_last`) is silently dropped.\n * 3. De-duplicate by `(note_id, heading_path, anchor)` — the section\n * identity per ADR-032. When multiple chunk hits\n * land in the same section, the section's score is the MAX of\n * the constituent chunk scores — the natural reading of\n * \"how relevant is this section\". RRF rank-position scores would\n * systematically punish short sections under summation; max is\n * both fairer and easier to reason about.\n * 4. Sort by score DESC; tie-break by `chunk_id_first` ASC so\n * earlier sections in document order win deterministic ties.\n * 5. Slice to `limit`. Hydrate the surviving sections into\n * `SectionHit` packets via the `Document` returned by the\n * injected `SourceConnector` so callers always get the canonical\n * 8-field citation floor PLUS the section-specific extras\n * (anchor, score, chunk_ids, snippet).\n *\n * Adapter-seam discipline (ADR-002 §Invariants, enforced by\n * `scripts/lint-adapters.sh`): this module imports NOTHING from\n * `node:fs`, `node:path`, `gray-matter`, or `chokidar`. All FS / vault-\n * content access goes through injected dependencies (`searchHybrid`,\n * `sectionForHit`, `readDocument`, `displayUrlFor`).\n *\n * Inflight-dependency note: slice 03-05 extends `hybridSearch` with\n * optional `recency_weight`, `authority_weight`, `include_superseded`\n * params (additive). This controller accepts those args from callers\n * but, until 03-05 merges, the production wiring forwards only the\n * subset that `hybridSearch` currently understands. The Zod schema in\n * `tool-registry.ts` accepts the full set so the wire surface is\n * forward-compatible; see `.planning/phases/03-bundles-authority-staleness/03-03-DEVIATIONS.md`.\n */\n\nimport type { DocId, Document, SearchHit, SourceHandle } from \"../types.js\";\nimport type { CitationPacket } from \"../memory/citation-packet.js\";\nimport { toCitationPacket } from \"../memory/citation-packet.js\";\n\n/**\n * Input shape for the `search_sections` MCP tool. Validated upstream\n * by Zod in `tool-registry.ts`; this is the post-validation shape.\n */\nexport interface SearchSectionsArgs {\n query: string;\n limit: number;\n vaults?: string[];\n /** Forwarded to `hybridSearch` once slice 03-05 lands. Placeholder\n * until then — see file-header inflight note. */\n recency_weight?: number;\n authority_weight?: number;\n include_superseded?: boolean;\n}\n\n/**\n * Minimal projection of a `SectionRow` carrying only what the promotion\n * step needs. Keeps the dependency surface narrow so test stubs do not\n * have to fabricate the full DB row.\n */\nexport interface SectionResolution {\n /** Numeric DB note id; carried only for the dedup key. */\n noteId: number;\n /** Section's content-hash anchor (ADR-003 H-7). */\n anchor: string;\n /** Section heading path (root → leaf). Empty for preamble (level 0). */\n headingPath: string[];\n /** For deterministic tiebreak: section's earliest chunk_id_first. */\n chunkIdFirst: number;\n}\n\n/**\n * Input passed to the injected `searchHybrid` dep. Mirrors the subset\n * of `HybridSearchOptions` this controller drives. Server bootstrap\n * supplies the production closure; tests inject a stub.\n *\n * Slice 03-05 will additively widen this with the rescore params; the\n * fields are already accepted (and IGNORED) here so a one-line wiring\n * change suffices once 03-05 lands.\n */\nexport interface SearchSectionsHybridInput {\n query: string;\n topK: number;\n vaults?: string[];\n}\n\nexport interface SearchSectionsDeps {\n /** Inner chunk-level hybrid search. */\n searchHybrid: (input: SearchSectionsHybridInput) => Promise<SearchHit[]>;\n /**\n * Resolve the section enclosing a chunk hit. Returns `null` when no\n * containing section exists (orphan chunk — silently dropped).\n *\n * The hit identifies a chunk via `(vault, notePath, chunkIdx)`. The\n * adapter wiring is responsible for the chunkIdx → chunk_id lookup\n * and the `SectionsQueries.findContainingChunk` call.\n */\n sectionForHit: (\n vaultName: string,\n notePath: string,\n chunkIdx: number,\n ) => SectionResolution | null;\n /**\n * Read the canonical `Document` for a (vault, notePath) so the\n * SectionHit can carry the full 8-field citation packet floor.\n * Throws when the doc is missing — callers may silently drop on\n * throw (stale index row), but this controller surfaces the error.\n */\n readDocument: (vaultName: string, notePath: string) => Promise<Document>;\n /**\n * Adapter-mediated display URL for a `DocId`. Same seam used by\n * recall (`citation-packet.displayUrlFor`); the wiring passes a\n * closure that resolves via `SourceConnector.formatDisplayUrl`.\n */\n displayUrlFor: (docId: DocId, vaultName: string) => string;\n}\n\n/**\n * Section-level retrieval response item. Extends the 8-field citation\n * packet floor (D-01) with the section-specific extras called out in\n * the plan's \"Section hit shape\" table.\n */\nexport interface SectionHit extends CitationPacket {\n /** Section's canonical content-hash anchor (ADR-003 H-7). */\n anchor: string;\n /** MAX of the constituent chunk scores. */\n score: number;\n /** Snippet from the highest-scoring contributing chunk. */\n snippet?: string;\n /** Every chunk_idx that contributed to this section in this query. */\n chunk_ids: number[];\n}\n\n/**\n * Multiplier applied to `limit` when sizing the inner `hybridSearch`\n * candidate pool. Rationale: a section may span 1..N chunks, so we\n * need enough chunk candidates that the top `limit` sections (post-\n * promotion + dedup) are all represented. 5× is the same cushion the\n * v1 reranker uses for its fan-out (`hybrid.ts:rerankFanOut`).\n */\nconst TOP_K_INFLATION_FACTOR = 5;\n\n/**\n * Internal accumulator shape — tracks the constituent chunks of a\n * section as we walk the chunk hits.\n */\ninterface SectionAccumulator {\n resolution: SectionResolution;\n /** The hit whose score is currently the section's max. */\n bestHit: SearchHit;\n bestScore: number;\n /** Every contributing `chunkIdx` (used for `SectionHit.chunk_ids`). */\n chunkIdxs: number[];\n /** Owning vault name — needed for the per-hit `Document` read. */\n vaultName: string;\n /** Owning note path — needed for the per-hit `Document` read. */\n notePath: string;\n}\n\n/**\n * Run section-level retrieval. See the file header for the full\n * composition algorithm.\n */\nexport async function searchSections(\n deps: SearchSectionsDeps,\n args: SearchSectionsArgs,\n): Promise<SectionHit[]> {\n // 1) Inflate topK and call the inner hybrid pipeline. A single call\n // keeps the v1 RRF (+ optional rerank) byte-identical.\n const chunkHits = await deps.searchHybrid({\n query: args.query,\n topK: args.limit * TOP_K_INFLATION_FACTOR,\n vaults: args.vaults,\n });\n\n if (chunkHits.length === 0) return [];\n\n // 2) Promote each chunk hit to its enclosing section, accumulating\n // by `(note_id, anchor)`. Drop orphan chunks silently.\n const sectionMap = new Map<string, SectionAccumulator>();\n for (const hit of chunkHits) {\n const resolution = deps.sectionForHit(hit.vault, hit.notePath, hit.chunkIdx);\n if (!resolution) continue;\n // Plan acceptance: heading_path always non-empty. Preamble\n // (level 0, empty heading_path) is dropped — preamble has no\n // human-readable anchor and would surface as a citation with no\n // heading, which is precisely what the acceptance excludes.\n if (resolution.headingPath.length === 0) continue;\n\n // Dedup key matches the section identity (note_id, heading_path, anchor)\n // per ADR-032: two byte-identical sections in DIFFERENT contexts are\n // distinct citations and must not merge here. heading_path is joined with\n // a separator that cannot appear in a heading slug segment.\n const key = `${resolution.noteId}#${resolution.headingPath.join(\"\u0000\")}#${resolution.anchor}`;\n const existing = sectionMap.get(key);\n if (!existing) {\n sectionMap.set(key, {\n resolution,\n bestHit: hit,\n bestScore: hit.score,\n chunkIdxs: [hit.chunkIdx],\n vaultName: hit.vault,\n notePath: hit.notePath,\n });\n continue;\n }\n // Section already seen — add chunkIdx, raise max score if needed.\n existing.chunkIdxs.push(hit.chunkIdx);\n if (hit.score > existing.bestScore) {\n existing.bestScore = hit.score;\n existing.bestHit = hit;\n }\n }\n\n if (sectionMap.size === 0) return [];\n\n // 3) Sort by score DESC, tie-break by `chunk_id_first` ASC. Earlier\n // sections in document order win deterministic ties.\n const sorted = [...sectionMap.values()].sort((a, b) => {\n if (b.bestScore !== a.bestScore) return b.bestScore - a.bestScore;\n return a.resolution.chunkIdFirst - b.resolution.chunkIdFirst;\n });\n\n // 4) Slice to limit BEFORE hydration — avoids paying for `Document`\n // reads on losing sections.\n const winners = sorted.slice(0, args.limit);\n\n // 5) Hydrate each surviving section into a `SectionHit`. The\n // citation packet is built from the full `Document` (via the\n // injected `readDocument` dep) so we get the canonical hash +\n // full property bag. The section-specific fields override\n // `heading_path` (with the section's path) and add anchor /\n // score / chunk_ids / snippet.\n const hits: SectionHit[] = [];\n for (const acc of winners) {\n let doc: Document;\n try {\n doc = await deps.readDocument(acc.vaultName, acc.notePath);\n } catch {\n // Stale index pointer to a deleted doc — silently drop, as\n // recall does. The post-slice + drop means the result count\n // may dip below `limit` in this edge case; we accept that\n // rather than re-running with a larger inflation factor.\n continue;\n }\n const packet = toCitationPacket(\n {\n id: doc.id,\n source: doc.source,\n title: doc.title,\n mtime: doc.mtime,\n hash: doc.hash,\n properties: doc.properties,\n heading_path: acc.resolution.headingPath,\n },\n deps.displayUrlFor(doc.id, acc.vaultName),\n );\n const hit: SectionHit = {\n ...packet,\n anchor: acc.resolution.anchor,\n score: acc.bestScore,\n chunk_ids: [...acc.chunkIdxs],\n };\n if (acc.bestHit.chunkText.length > 0) {\n hit.snippet = acc.bestHit.chunkText;\n }\n hits.push(hit);\n }\n\n return hits;\n}\n\n// Re-exports for ergonomic imports.\nexport type { CitationPacket, DocId, Document, SearchHit, SourceHandle };\n","/**\n * Phase 3 (ASM-02) — `get_outline` controller.\n *\n * Resolves a DocId into a nested outline tree of `OutlineNode`s built\n * from the `sections` table (landed in 03-01, migration 010) plus the\n * document-level citation packet (title/mtime/hash/display_url) read\n * through the `SourceConnector` seam.\n *\n * Pipeline:\n *\n * 1. Decompose the DocId into `{vaultName, path}`. The DocId scheme\n * portion (e.g. `obsidian-fs`) is preserved on the response via\n * `source_handle`. Optional `vaults` filter — when set, the\n * decomposed vault MUST appear in it (otherwise `doc_not_found`).\n * 2. Resolve the `Vault` from the manager (throws `doc_not_found`\n * shaped error on unknown vault).\n * 3. Load the note row by path (`notes.getByPath`). Missing row →\n * `doc_not_found` (the indexer hasn't seen this doc yet, OR it\n * was deleted between the catch-up scan and this call).\n * 4. Read the canonical `Document` via the SourceConnector. The\n * adapter's `formatDisplayUrl(id)` mints the deep-link URL.\n * 5. Query `sections.getByNote(noteId)` — returns rows in\n * parent-id-NULL-first, then parent-id ASC, then ord ASC order\n * (one DFS-friendly pass).\n * 6. Build the tree by parent-pointer reconstruction.\n * 7. Resolve each section's `chunk_ids` from a single\n * `chunks.getByNote(noteId)` lookup, filtered per-section by the\n * stored `[chunk_id_first, chunk_id_last]` range. Sections with\n * NULL ranges produce `chunk_ids: []`.\n *\n * Adapter-seam discipline (per `scripts/lint-adapters.sh`): NO `fs`,\n * `gray-matter`, `chokidar`, or `path.*` imports. Document reads route\n * through the injected `SourceConnector`. SQLite access (the `sections`,\n * `notes`, `chunks` query namespaces) is permitted — that is the L0\n * substrate, not the adapter tier.\n *\n * Error contract (per plan §\"Empty / unknown doc_id\"): an unknown\n * `doc_id` is an exceptional case — callers asked about a specific doc\n * by ID. Throw a tagged error; the server dispatch wraps it into\n * `{ isError: true, content: [...JSON.stringify({error: \"doc_not_found\", doc_id})...] }`.\n */\n\nimport { decomposeDocId, parseDocId, parseSourceHandle } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport { toCitationPacket, displayUrlFor } from \"../memory/citation-packet.js\";\nimport type { ChunkRow, SectionRow } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\nimport type { GetOutlineArgs, OutlineNode, OutlineResult } from \"./types.js\";\n\n/**\n * Dedicated error class for the \"unknown doc_id\" case. The server\n * handler catches this and emits the structured `{error: \"doc_not_found\",\n * doc_id}` payload required by the plan's error contract — distinct\n * from generic exception messages (validation errors, etc).\n */\nexport class DocNotFoundError extends Error {\n override readonly name = \"DocNotFoundError\";\n readonly doc_id: string;\n constructor(docId: string) {\n super(`Document not found: ${docId}`);\n this.doc_id = docId;\n }\n}\n\n/**\n * Injected dependencies for `getOutline`. Mirrors `RecallDeps` in\n * `src/memory/tools/recall.ts` — the server bootstrap supplies the\n * production wiring, tests inject in-memory stubs.\n */\nexport interface GetOutlineDeps {\n manager: VaultManager;\n /** Resolve the `SourceConnector` for a vault name. */\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\n/**\n * Public entry point. See file header for the full pipeline.\n */\nexport async function getOutline(\n deps: GetOutlineDeps,\n args: GetOutlineArgs,\n): Promise<OutlineResult> {\n // 1) Validate-decompose the DocId. `parseDocId` throws on malformed\n // input — surface as `doc_not_found` (callers gave us a bad id).\n let parsed: { scheme: string; authority: string; resource: string };\n try {\n const docId = parseDocId(args.doc_id);\n parsed = decomposeDocId(docId);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n const { scheme, authority: vaultName, resource: path } = parsed;\n\n // Optional vault-filter narrowing. The DocId already names a vault;\n // the filter exists for callers that want to assert they're talking\n // to a known set (e.g. a multi-vault agent guarding a tenant boundary).\n if (args.vaults && args.vaults.length > 0 && !args.vaults.includes(vaultName)) {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 2) Resolve the Vault. `manager.require` throws on unknown — map to\n // DocNotFoundError so the wire response is consistent.\n let vault: Vault;\n try {\n vault = deps.manager.require(vaultName);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 3) Look up the note row by path. Missing row → doc_not_found.\n const noteRow = vault.db.notes.getByPath(path);\n if (!noteRow) {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 4) Read the canonical Document via the source seam. We use this\n // for the doc-level citation-packet fields (title/mtime/hash/\n // display_url) — staying off the DB-cached row keeps us aligned\n // with `read_note` (which also reads fresh through the seam per\n // Plan 01-03 Task 06).\n const source = deps.sourceConnectorFor(vaultName);\n const docId = parseDocId(args.doc_id);\n let docFields: { title: string; mtime: number; hash: string };\n let displayUrl: string;\n try {\n const doc = await source.readDocument(docId);\n docFields = { title: doc.title, mtime: doc.mtime, hash: doc.hash };\n // Use the canonical packet helpers so display-URL resolution\n // matches recall + Phase 3 conformance assertions byte-for-byte.\n const packet = toCitationPacket(doc, displayUrlFor(doc.id, source));\n displayUrl = packet.display_url;\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 5) Read all sections for the note. `getByNote` returns rows in\n // parent-NULL-first, then by parent_id ASC, then ord ASC — exactly\n // the order needed to populate `byId` before any child references\n // its parent in step 6.\n const sectionRows = vault.db.sections.getByNote(noteRow.id);\n\n // 7-prep) Load all chunks for the note once. Sections will filter\n // this list by their stored [chunk_id_first, chunk_id_last] range.\n // For a note with N chunks and S sections, this is one O(N) read\n // + S × O(N) filters — totally fine for v2 doc sizes (N≤low\n // thousands; S≤low hundreds). Could be optimized later with a\n // range-keyed SQL helper, but kept simple here.\n const allChunks: ChunkRow[] = vault.db.chunks.getByNote(noteRow.id);\n\n // 6) Build the tree. Parent-pointer reconstruction in one pass.\n const root = buildOutlineTree(sectionRows, allChunks);\n\n // Compose the response. `source_handle` is derived from the DocId's\n // scheme + vault — minted via `parseSourceHandle` so the brand is\n // valid at the type level.\n const sourceHandle = parseSourceHandle(`${scheme}://${vaultName}`);\n\n return {\n doc_id: docId,\n source_handle: sourceHandle,\n title: docFields.title,\n root,\n mtime: docFields.mtime,\n hash: docFields.hash,\n display_url: displayUrl,\n };\n}\n\n/**\n * Build the outline tree from a flat `SectionRow[]` (in\n * `getByNote` order — NULL parents first) plus the note's chunks\n * (for `chunk_ids` resolution).\n *\n * Exported only for unit tests; production callers use `getOutline`.\n */\nexport function buildOutlineTree(rows: SectionRow[], allChunks: ChunkRow[]): OutlineNode[] {\n const byId = new Map<number, OutlineNode>();\n const roots: OutlineNode[] = [];\n for (const r of rows) {\n const node: OutlineNode = {\n anchor: r.anchor,\n heading_path: parseHeadingPath(r.heading_path),\n heading_text: r.heading_text,\n level: r.level,\n chunk_ids: collectChunkIdsInRange(allChunks, r.chunk_id_first, r.chunk_id_last),\n children: [],\n };\n byId.set(r.id, node);\n if (r.parent_id == null) {\n roots.push(node);\n } else {\n const parent = byId.get(r.parent_id);\n // Defensive: a row whose `parent_id` has not yet been seen would\n // indicate a getByNote ordering regression. The 03-01 contract\n // guarantees NULL-first ordering, so this branch is unreachable\n // in production. Tests that violate the contract will surface\n // the bug as a visibly-orphan node rather than a silent drop.\n if (parent) {\n parent.children.push(node);\n } else {\n roots.push(node);\n }\n }\n }\n return roots;\n}\n\n/**\n * Parse the stringified-JSON `heading_path` column with one defensive\n * fallback: a `null` / malformed payload yields `[]` (no crash). The\n * 03-01 indexer always writes a valid JSON array; this defense is\n * cheap and prevents one bad row from poisoning the whole tree.\n */\nfunction parseHeadingPath(raw: string): string[] {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (Array.isArray(parsed) && parsed.every((s) => typeof s === \"string\")) {\n return parsed as string[];\n }\n return [];\n } catch {\n return [];\n }\n}\n\n/**\n * Resolve a section's chunk-id range into the actual chunk IDs (as\n * strings — opaque tokens for downstream consumers). Returns `[]`\n * when either bound is `null` (a heading with no body content).\n *\n * Chunks are filtered from the pre-loaded `allChunks` list rather\n * than re-queried per-section, which keeps the overall outline build\n * at O(N + S × N) — fine for v2 doc sizes.\n */\nfunction collectChunkIdsInRange(\n allChunks: ChunkRow[],\n first: number | null,\n last: number | null,\n): string[] {\n if (first === null || last === null) return [];\n const ids: string[] = [];\n for (const c of allChunks) {\n if (c.id >= first && c.id <= last) {\n ids.push(String(c.id));\n }\n }\n return ids;\n}\n","/**\n * DebouncedQueue — coalesces rapid filesystem events on the same path\n * into a single onFlush call.\n *\n * Semantics:\n * - Multiple \"change\" events on the same path within debounceMs collapse\n * to one flush.\n * - \"delete\" overrides a pending \"change\" (delete is final).\n * - A later \"change\" on a pending \"delete\" replaces it (file came back).\n * - maxLatencyMs caps how long an entry may sit pending; once exceeded,\n * the next enqueue (or scheduled timer) flushes it immediately.\n */\n\nexport interface QueueEvent {\n /** Vault-relative path, forward slashes. */\n path: string;\n /** \"change\" or \"delete\". add/change are merged to \"change\". */\n kind: \"change\" | \"delete\";\n}\n\nexport interface DebouncedQueueOptions {\n /** Debounce window in ms. Default 500. */\n debounceMs?: number;\n /** Maximum age (ms) a pending event may sit before forced flush. Default 5000. */\n maxLatencyMs?: number;\n /** Called when an event is ready to be processed. Errors are caught + logged. */\n onFlush: (event: QueueEvent) => Promise<void> | void;\n /** Optional error sink — invoked with (event, error) when onFlush throws. */\n onError?: (event: QueueEvent, err: unknown) => void;\n}\n\ninterface PendingEntry {\n kind: \"change\" | \"delete\";\n /** Insertion order — first time this path was enqueued in the current pending cycle. */\n firstSeen: number;\n /** Timer for the debounce window. */\n timer: ReturnType<typeof setTimeout>;\n}\n\nexport class DebouncedQueue {\n private readonly debounceMs: number;\n private readonly maxLatencyMs: number;\n private readonly onFlush: (event: QueueEvent) => Promise<void> | void;\n private readonly onError: (event: QueueEvent, err: unknown) => void;\n private readonly pending = new Map<string, PendingEntry>();\n /** Tracks in-flight flush promises so flushAll can await them. */\n private readonly inFlight = new Set<Promise<void>>();\n private stopped = false;\n\n constructor(options: DebouncedQueueOptions) {\n this.debounceMs = options.debounceMs ?? 500;\n this.maxLatencyMs = options.maxLatencyMs ?? 5000;\n this.onFlush = options.onFlush;\n this.onError =\n options.onError ??\n ((event, err) => {\n // eslint-disable-next-line no-console\n console.error(`[DebouncedQueue] onFlush failed for ${event.path} (${event.kind}):`, err);\n });\n }\n\n /**\n * Enqueue an event. After shutdown() this is a no-op.\n */\n enqueue(event: QueueEvent): void {\n if (this.stopped) return;\n\n const now = Date.now();\n const existing = this.pending.get(event.path);\n\n // maxLatencyMs guard: if we already have a pending entry that has been\n // sitting longer than the cap, flush it now (with its current kind)\n // before recording the new event.\n if (existing && now - existing.firstSeen >= this.maxLatencyMs) {\n clearTimeout(existing.timer);\n this.pending.delete(event.path);\n this.dispatch({ path: event.path, kind: existing.kind });\n // Fall through and record the new event fresh.\n }\n\n const prior = this.pending.get(event.path);\n const firstSeen = prior?.firstSeen ?? now;\n if (prior) clearTimeout(prior.timer);\n\n // Coalesce kind. Later events overwrite. (Both delete-after-change and\n // change-after-delete simply take the latest event's kind, matching the\n // documented behavior.)\n const kind: \"change\" | \"delete\" = event.kind;\n\n // Schedule debounce. If the entry is close to maxLatency, fire sooner.\n const age = now - firstSeen;\n const remaining = this.maxLatencyMs - age;\n const delay = Math.max(0, Math.min(this.debounceMs, remaining));\n\n const timer = setTimeout(() => {\n const entry = this.pending.get(event.path);\n if (!entry) return;\n this.pending.delete(event.path);\n this.dispatch({ path: event.path, kind: entry.kind });\n }, delay);\n\n this.pending.set(event.path, { kind, firstSeen, timer });\n }\n\n /** Force-flush all pending events. Resolves once all onFlush calls settle. */\n async flushAll(): Promise<void> {\n // Snapshot in insertion order (Map preserves it).\n const entries = [...this.pending.entries()];\n for (const [path, entry] of entries) {\n clearTimeout(entry.timer);\n this.pending.delete(path);\n this.dispatch({ path, kind: entry.kind });\n }\n // Await any in-flight promises (including just-dispatched ones).\n while (this.inFlight.size > 0) {\n await Promise.all([...this.inFlight]);\n }\n }\n\n /** Cancel timers, drop pending events. Idempotent. After this enqueue is a no-op. */\n shutdown(): void {\n if (this.stopped) return;\n this.stopped = true;\n for (const entry of this.pending.values()) {\n clearTimeout(entry.timer);\n }\n this.pending.clear();\n }\n\n /** Pending event count (excludes in-flight). */\n size(): number {\n return this.pending.size;\n }\n\n private dispatch(event: QueueEvent): void {\n let result: Promise<void> | void;\n try {\n result = this.onFlush(event);\n } catch (err) {\n this.safeOnError(event, err);\n return;\n }\n if (result && typeof (result as Promise<void>).then === \"function\") {\n const p = (result as Promise<void>)\n .catch((err: unknown) => this.safeOnError(event, err))\n .finally(() => {\n this.inFlight.delete(p);\n });\n this.inFlight.add(p);\n }\n }\n\n private safeOnError(event: QueueEvent, err: unknown): void {\n try {\n this.onError(event, err);\n } catch {\n // swallow — onError is best-effort\n }\n }\n}\n","/**\n * Chokidar watcher options for the obsidian-fs adapters.\n *\n * Shared by:\n * - `VaultWatcher` (v1 live-indexing path; see ./watcher.ts)\n * - `ObsidianFsChangeFeed` (v2 ChangeFeed seam; see ./index.ts)\n *\n * The four critical fields originated BYTE-FOR-BYTE from v1\n * (`src/watcher/watcher.ts:79-96` pre-plan-01-05) per RESEARCH Pitfall 6.\n * Modifying these values may break the suppression-set integration (the\n * watcher could race the atomic-rename suppression window) — DO NOT\n * change without first re-running the suppression conformance test in\n * `src/adapters/change-feed/conformance.test.ts`.\n *\n * - awaitWriteFinish: { stabilityThreshold: 400, pollInterval: 50 }\n * - ignored: [/(^|[\\\\/])\\../, \"**\\/*.tmp.*\"] (+ caller excludes)\n * - followSymlinks: false\n * - ignoreInitial: true (initial state arrives via indexVault catch-up)\n *\n * Note (quick-task 260515-hkc): stabilityThreshold was bumped 200→400ms\n * to give a 300–400ms safety margin over the 700–800ms test sleeps in\n * change-feed.test.ts:91 and watcher.test.ts:95, which intermittently\n * raced under full-suite load. It remains safely below the two\n * 400ms-sleep test cases (closed-feed + drain()) which pass for\n * unrelated reasons. The suppression integration test (Pitfall 6) was\n * re-run and remains green — extending the stability window only\n * widens the favorable race for own-write suppression.\n */\n\nimport { posix } from \"node:path\";\nimport type { ChokidarOptions } from \"chokidar\";\n\n/**\n * Build chokidar options for a vault root.\n *\n * The caller-provided `excludes` are joined with `vaultPath` (absolute\n * glob patterns) and pre-pended to the v1 baseline filters (`hidden\n * files at any level` regex + `**\\/*.tmp.*` atomic-write artifacts).\n */\nexport function buildChokidarOptions(\n vaultPath: string,\n excludes: ReadonlyArray<string>,\n): ChokidarOptions {\n return {\n persistent: true,\n ignoreInitial: true, // we expect initial state via indexVault\n ignored: [\n // chokidar handles glob-like patterns. Provide both raw and absolute.\n ...excludes.map((g) => posix.join(vaultPath, g)),\n /(^|[\\\\/])\\../, // hidden files at any level\n \"**/*.tmp.*\", // our atomic-write artifacts\n ],\n // Only watch markdown files — saves event volume.\n // chokidar's `ignored` runs against absolute paths, so we filter via\n // an after-the-fact event check (cheaper than a glob).\n awaitWriteFinish: {\n stabilityThreshold: 400,\n pollInterval: 50,\n },\n followSymlinks: false,\n };\n}\n","/**\n * VaultWatcher — chokidar-driven incremental re-indexing.\n *\n * Lifecycle: start() opens a chokidar watcher on the vault path, routes\n * change/add/unlink events through a DebouncedQueue, and on flush invokes\n * indexNote / removeNote.\n *\n * Suppression: writes from the MCP server itself (writeNote, deleteNote,\n * updateFrontmatter) mark the path on a shared SuppressionSet just before\n * touching the filesystem. The watcher checks + consumes the entry; if\n * present, the event is dropped. This prevents endless write→watch→reindex\n * loops.\n */\n\nimport chokidar from \"chokidar\";\nimport type { FSWatcher } from \"chokidar\";\nimport { sep as nativeSep } from \"node:path\";\nimport type { Vault } from \"../../../vault/index.js\";\nimport type { OllamaClient } from \"../../../ollama/index.js\";\nimport { indexNote, removeNote } from \"../../../indexer/index.js\";\nimport { DebouncedQueue, type QueueEvent } from \"./queue.js\";\nimport type { SuppressionSet } from \"./suppression.js\";\nimport { buildChokidarOptions } from \"./chokidar-config.js\";\nimport { errorMessage } from \"../../../errors/format.js\";\n\nexport interface VaultWatcherOptions {\n vault: Vault;\n embeddingModel: string;\n /** Phase 7c: optional shadow model name; passed through to indexNote so\n * the secondary index stays current on live file edits. Silently\n * ignored if the model is not yet registered in the DB. */\n secondaryEmbeddingModel?: string;\n ollama: OllamaClient;\n suppression: SuppressionSet;\n /** Debounce window (ms) for coalescing rapid file changes. Default 500. */\n debounceMs?: number;\n /** Log sink — defaults to stderr. */\n log?: (msg: string) => void;\n}\n\nexport class VaultWatcher {\n private fsWatcher: FSWatcher | null = null;\n private queue: DebouncedQueue;\n private readonly opts: Required<\n Omit<VaultWatcherOptions, \"log\" | \"debounceMs\" | \"secondaryEmbeddingModel\">\n > & {\n log: (msg: string) => void;\n debounceMs: number;\n secondaryEmbeddingModel: string | undefined;\n };\n private started = false;\n /** ADR-008: debounce timer for ContextFit KB re-ingest (coalesces bursts). */\n private cfReingestTimer: ReturnType<typeof setTimeout> | null = null;\n private cfReingestInFlight = false;\n\n constructor(options: VaultWatcherOptions) {\n this.opts = {\n vault: options.vault,\n embeddingModel: options.embeddingModel,\n secondaryEmbeddingModel: options.secondaryEmbeddingModel,\n ollama: options.ollama,\n suppression: options.suppression,\n debounceMs: options.debounceMs ?? 500,\n log: options.log ?? ((m) => process.stderr.write(`[watcher] ${m}\\n`)),\n };\n\n this.queue = new DebouncedQueue({\n debounceMs: this.opts.debounceMs,\n maxLatencyMs: 5000,\n onFlush: (event) => this.handleFlush(event),\n onError: (event, err) => {\n const message = errorMessage(err);\n this.opts.log(`error processing ${event.path}: ${message}`);\n },\n });\n }\n\n async start(): Promise<void> {\n if (this.started) return;\n const vaultPath = this.opts.vault.config.path;\n const excludes = this.opts.vault.config.exclude_globs ?? [];\n\n this.fsWatcher = chokidar.watch(vaultPath, buildChokidarOptions(vaultPath, excludes));\n\n this.fsWatcher.on(\"add\", (path) => this.onFsEvent(path, \"change\"));\n this.fsWatcher.on(\"change\", (path) => this.onFsEvent(path, \"change\"));\n this.fsWatcher.on(\"unlink\", (path) => this.onFsEvent(path, \"delete\"));\n this.fsWatcher.on(\"error\", (err) => {\n const message = errorMessage(err);\n this.opts.log(`fs watcher error: ${message}`);\n });\n\n await new Promise<void>((resolve) => {\n this.fsWatcher!.once(\"ready\", () => resolve());\n });\n\n this.started = true;\n this.opts.log(`watching ${vaultPath}`);\n }\n\n /** Force-process any pending events. Used during shutdown. */\n async drain(): Promise<void> {\n await this.queue.flushAll();\n }\n\n async stop(): Promise<void> {\n if (!this.started) return;\n this.started = false;\n this.queue.shutdown();\n if (this.cfReingestTimer) {\n clearTimeout(this.cfReingestTimer);\n this.cfReingestTimer = null;\n }\n if (this.fsWatcher) {\n await this.fsWatcher.close();\n this.fsWatcher = null;\n }\n }\n\n // ─── internal ──────────────────────────────────────────────────────────\n\n /**\n * ADR-008: schedule a debounced full ContextFit KB re-ingest. Per-note\n * changes update the SQLite layer immediately (via indexNote); the ContextFit\n * search KB is rebuilt in one coalesced pass ~1.5s after the last change so a\n * burst of edits triggers a single re-ingest. CPU-only and fast.\n */\n private scheduleContextFitReingest(): void {\n if (this.cfReingestTimer) clearTimeout(this.cfReingestTimer);\n this.cfReingestTimer = setTimeout(() => {\n this.cfReingestTimer = null;\n void this.runContextFitReingest();\n }, 1500);\n }\n\n private async runContextFitReingest(): Promise<void> {\n if (this.cfReingestInFlight) {\n // A re-ingest is already running; schedule another pass after it so the\n // latest changes are captured.\n this.scheduleContextFitReingest();\n return;\n }\n this.cfReingestInFlight = true;\n try {\n const { indexVaultWithContextFit } = await import(\"../../retrieval/contextfit/index.js\");\n const r = await indexVaultWithContextFit(this.opts.vault.config, {});\n if (r.status === \"completed\") {\n this.opts.log(`ContextFit KB refreshed (${r.durationMs}ms)`);\n } else if (r.status === \"skipped\") {\n // Issue #17: another process is mid-ingest; it will do a trailing pass.\n this.opts.log(`ContextFit KB refresh skipped (another ingest in progress; flagged)`);\n } else {\n this.opts.log(`ContextFit KB refresh failed: ${r.error}`);\n }\n } catch (err) {\n this.opts.log(\n `ContextFit KB refresh error: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n this.cfReingestInFlight = false;\n }\n }\n\n private onFsEvent(absolutePath: string, kind: \"change\" | \"delete\"): void {\n // Filter to .md only — Obsidian writes other artifacts (.obsidian/*) that\n // we either don't care about or already excluded.\n if (!absolutePath.endsWith(\".md\")) return;\n\n const relativePath = this.toRelative(absolutePath);\n\n // Suppression: was this just written by the MCP server itself?\n if (this.opts.suppression.consume(relativePath)) {\n this.opts.log(`suppressed ${kind} ${relativePath} (own write)`);\n return;\n }\n\n this.queue.enqueue({ path: absolutePath, kind });\n }\n\n private toRelative(absolutePath: string): string {\n const root = this.opts.vault.config.path;\n let rel = absolutePath;\n if (rel.startsWith(root)) rel = rel.slice(root.length);\n if (rel.startsWith(nativeSep) || rel.startsWith(\"/\")) rel = rel.slice(1);\n return rel.split(nativeSep).join(\"/\");\n }\n\n private async handleFlush(event: QueueEvent): Promise<void> {\n const relativePath = this.toRelative(event.path);\n\n const isContextFit = this.opts.vault.config.backend === \"contextfit\";\n\n if (event.kind === \"delete\") {\n const result = removeNote(this.opts.vault, event.path);\n if (result.removed) {\n this.opts.log(`removed ${relativePath}`);\n // ADR-008: a deleted note must drop out of the ContextFit KB too.\n if (isContextFit) this.scheduleContextFitReingest();\n } else {\n this.opts.log(`delete event for unknown ${relativePath} (skip)`);\n }\n return;\n }\n\n const result = await indexNote({\n vault: this.opts.vault,\n absolutePath: event.path,\n embeddingModel: this.opts.embeddingModel,\n secondaryEmbeddingModel: this.opts.secondaryEmbeddingModel,\n // ADR-008: ContextFit vaults build the SQLite layer without embeddings;\n // their search KB is refreshed by the debounced re-ingest below.\n ...(isContextFit ? { embeddings: \"none\" as const } : { ollama: this.opts.ollama }),\n });\n\n switch (result.status) {\n case \"indexed\":\n this.opts.log(\n `indexed ${relativePath} (${result.isNew ? \"new\" : \"updated\"}, ${result.chunksCreated} chunks)`,\n );\n // ADR-008: refresh the ContextFit search KB (debounced full re-ingest).\n if (isContextFit) this.scheduleContextFitReingest();\n break;\n case \"unchanged\":\n // Common when chokidar fires for a re-save with no content delta —\n // log at debug level (skip entirely for now).\n break;\n case \"outside_vault\":\n this.opts.log(`event for path outside vault ignored: ${event.path}`);\n break;\n case \"missing\":\n // File disappeared between event and parse — treat as delete.\n this.opts.log(`file missing on parse — removing ${relativePath}`);\n removeNote(this.opts.vault, event.path);\n break;\n }\n }\n}\n","/**\n * SuppressionSet — short-lived registry of paths the server itself just\n * touched, so the file watcher can ignore the resulting filesystem events.\n *\n * Entries auto-expire after `ttlMs` (default 2000). `consume(path)` returns\n * true exactly once per add — repeated consumes return false. This ensures\n * a legitimate later edit to the same file is NOT suppressed.\n *\n * # Phase 7 / Plan 07-07 / CAN-08 — hash-keyed suppression (additive)\n *\n * Phase 6 (RESEARCH §6 Pitfall 1) discovered that a pure path/TTL gate\n * cannot distinguish \"the agent's own write echoed back\" from \"the user\n * edited the file in another editor within the TTL window\". Plan 07-07\n * extends the API additively:\n *\n * - `add(path)` — existing path-only behavior; second arg may be a\n * number for `ttlMs` (legacy callers in writer/indexer pass this).\n * - `add(path, { ttlMs?, hash? })` — new options form. When `hash` is\n * recorded, `consume(path, hash)` only suppresses if hashes match;\n * a mismatch leaves the entry intact (so a later legitimate match\n * can still drop it).\n * - `consume(path)` — unconditional; matches today's semantics.\n * - `consume(path, hash)` — if the recorded entry has a hash, requires\n * equality; entries without a recorded hash always match (legacy\n * path-only entries fall through, so existing callers stay correct).\n *\n * Choice rationale (planner option (a) — overloaded `add`): the second\n * argument's type discriminates legacy vs. new shape. `typeof ttlMs ===\n * \"number\"` continues to mean \"TTL override\"; `typeof === \"object\"` is\n * the new options form. The option (b) split (`add` + `addHashed`) was\n * rejected on call-site simplicity grounds — the new `suppress_contract_write`\n * MCP tool wants the options-object form so its handler reads cleanly.\n *\n * # Trust boundary (THREAT-T-07-07-02 mitigation)\n *\n * TTL is bounded by the caller (the `suppress_contract_write` Zod schema\n * caps it at 30s). Hash mismatch on consume keeps the entry intact so\n * the next legitimate match still works — this guards against a\n * suppression entry \"swallowing\" a real external edit.\n *\n * @see plan 07-07 §\"Task 1\" — full behavior matrix.\n * @see ADR-007 §D-WATCH-PLUGIN-OUT — hash-keyed contract for the\n * plugin's YAML companion emission.\n */\n\nexport interface SuppressionOptions {\n /** Default TTL for new entries in ms. Default 2000. */\n ttlMs?: number;\n /** Override for testing: a clock function returning epoch ms. Default Date.now. */\n now?: () => number;\n}\n\n/** Per-entry options for the additive `add(path, opts)` overload. */\nexport interface SuppressionEntryOptions {\n /** Per-entry TTL override; falls back to the set's default. */\n ttlMs?: number;\n /**\n * Optional content hash. When present, `consume(path, hash)` only\n * suppresses on hash equality; mismatches leave the entry intact.\n * See file header for the full semantics matrix.\n */\n hash?: string;\n}\n\ninterface Entry {\n expiresAt: number;\n /** Recorded content hash (when the caller supplied one). */\n hash?: string;\n}\n\nexport class SuppressionSet {\n private readonly defaultTtlMs: number;\n private readonly now: () => number;\n private readonly entries = new Map<string, Entry>();\n\n constructor(options: SuppressionOptions = {}) {\n this.defaultTtlMs = options.ttlMs ?? 2000;\n this.now = options.now ?? Date.now;\n }\n\n /**\n * Mark a path as \"expect a filesystem event for this — please ignore it\".\n *\n * Legacy form: `add(path)` or `add(path, ttlMs)`.\n * Hash-keyed form: `add(path, { ttlMs?, hash? })`.\n *\n * @see file header for the full backwards-compatibility matrix.\n */\n add(path: string, ttlMsOrOpts?: number | SuppressionEntryOptions): void {\n this.prune();\n let ttl: number;\n let hash: string | undefined;\n if (typeof ttlMsOrOpts === \"number\") {\n ttl = ttlMsOrOpts;\n } else if (ttlMsOrOpts !== undefined) {\n ttl = ttlMsOrOpts.ttlMs ?? this.defaultTtlMs;\n hash = ttlMsOrOpts.hash;\n } else {\n ttl = this.defaultTtlMs;\n }\n const entry: Entry = { expiresAt: this.now() + ttl };\n if (hash !== undefined) entry.hash = hash;\n this.entries.set(path, entry);\n }\n\n /**\n * If path is suppressed, return true and (usually) remove the entry.\n *\n * Hash semantics:\n * - `consume(path)` — unconditional; removes the entry.\n * - `consume(path, undefined)` — same as above.\n * - `consume(path, hash)` — if the recorded entry has a hash\n * and it does NOT equal `hash`, leave the entry intact and return\n * false (RESEARCH §6 Pitfall 1: don't let an arbitrary external\n * edit consume our suppression slot). When hashes match, remove\n * and return true. When the recorded entry has no hash, treat it\n * as a legacy path-only entry and match unconditionally.\n */\n consume(path: string, hash?: string): boolean {\n this.prune();\n const entry = this.entries.get(path);\n if (!entry) return false;\n if (entry.expiresAt <= this.now()) {\n this.entries.delete(path);\n return false;\n }\n // Hash-aware path: when the caller supplies a hash AND the entry has\n // one, require equality. If they don't match, preserve the entry so\n // a later legitimate match can still consume it.\n if (hash !== undefined && entry.hash !== undefined && entry.hash !== hash) {\n return false;\n }\n this.entries.delete(path);\n return true;\n }\n\n /** Read-only check; does not consume. */\n has(path: string): boolean {\n this.prune();\n const entry = this.entries.get(path);\n if (!entry) return false;\n if (entry.expiresAt <= this.now()) {\n this.entries.delete(path);\n return false;\n }\n return true;\n }\n\n /** Drop expired entries. */\n prune(): void {\n const t = this.now();\n for (const [path, entry] of this.entries) {\n if (entry.expiresAt <= t) {\n this.entries.delete(path);\n }\n }\n }\n\n size(): number {\n this.prune();\n return this.entries.size;\n }\n}\n","/**\n * ObsidianFsChangeFeed — the ChangeFeed adapter for filesystem-backed\n * Obsidian vaults (ADR-002 §ChangeFeed; plan 01-05 task 02).\n *\n * # What this is\n *\n * The watch seam. Subscribers receive `ChangeEvent`s as the underlying\n * filesystem changes. Internally backed by a chokidar watcher configured\n * via the shared `buildChokidarOptions` helper (`./chokidar-config.ts`)\n * — the SAME four-field config used by the v1 `VaultWatcher`, preserved\n * BYTE-FOR-BYTE from v1 per RESEARCH Pitfall 6:\n *\n * - awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }\n * - ignored: [/(^|[\\\\/])\\../, \"**\\/*.tmp.*\"] (+ caller excludes)\n * - followSymlinks: false\n * - ignoreInitial: true\n *\n * Modifying these values breaks the suppression-set integration. The\n * conformance test (\"suppression marker registered → no ChangeEvent\n * emitted\") is the safety net.\n *\n * # Event mapping\n *\n * chokidar `add` → ChangeEvent { kind: \"create\", id, at }\n * chokidar `change` → ChangeEvent { kind: \"update\", id, at }\n * chokidar `unlink` → ChangeEvent { kind: \"delete\", id, at }\n *\n * Rename emission is DEFERRED to Phase 4 (RESEARCH A3 / Risk #3) — a\n * true OS-level rename surfaces in chokidar as `unlink` + `add` and\n * Phase 1 keeps that v1 behavior. `ChangeFeedCapabilities.emitsRename`\n * is FALSE for honest publication per Invariant I-7.\n *\n * # Suppression-set integration (Pitfall 6)\n *\n * The MCP server marks paths on a shared `SuppressionSet` immediately\n * before atomic-rename writes (see `handleWriteNote` / `handleDelete` /\n * `handleUpdateFrontmatter` in `src/server.ts`). On every chokidar event,\n * this feed checks `suppression.consume(relativePath)` first; if hit,\n * the event is dropped. This prevents the write → watch → re-index loop.\n *\n * # Filtering\n *\n * Only `.md` files emit events. Other artifacts (`.obsidian/*`, lock\n * files, etc.) are filtered by either chokidar's `ignored` regex or a\n * post-event suffix check — same as v1.\n *\n * # Lifecycle\n *\n * - `subscribe(handler)` registers a handler; multiple subscribers each\n * get a copy of every event. Returns a `Disposable` whose\n * `Symbol.dispose` unregisters the handler synchronously.\n * - `close()` is idempotent. After close, no more events fire. Future\n * `subscribe` calls register but receive no events (the watcher is\n * gone). The conformance suite gates this assertion on\n * `capabilities.watch === \"push\"`.\n *\n * # Coexistence with v1 VaultWatcher\n *\n * Phase 1 wires BOTH the v1 `VaultWatcher` (live-indexing path, drives\n * indexNote/removeNote) AND this `ObsidianFsChangeFeed` (registry-\n * exposed ChangeFeed seam, used by conformance tests + future Phase 2+\n * indexer rewiring) into the bootstrap. Both watch the same vault with\n * the SAME chokidar options — duplicate event volume, but each event is\n * cheap and suppression filters own-writes in both watchers. A future\n * plan will retire the v1 VaultWatcher in favor of an indexer that\n * subscribes through the ChangeFeed seam directly (RESEARCH §Recommended\n * Decomposition note).\n */\n\nimport chokidar from \"chokidar\";\nimport type { FSWatcher } from \"chokidar\";\nimport { sep as nativeSep } from \"node:path\";\nimport type { Vault } from \"../../../vault/index.js\";\nimport type { ChangeEvent, DocId, SourceHandle } from \"../../../types.js\";\nimport type { ChangeFeed, ChangeFeedCapabilities, Disposable } from \"../types.js\";\nimport { formatDocId, parseSourceHandle } from \"../../registry.js\";\nimport { SuppressionSet } from \"./suppression.js\";\nimport { buildChokidarOptions } from \"./chokidar-config.js\";\nimport { errorMessage } from \"../../../errors/format.js\";\n\nconst SCHEME = \"obsidian-fs\";\n\nexport interface ObsidianFsChangeFeedOptions {\n /** Vault providing config (path + name + exclude_globs). */\n vault: Vault;\n /**\n * Shared with `ObsidianFsDelivery` so own-writes (atomic rename\n * artifacts) don't fire events. The delivery adapter adds the\n * vault-relative path before `atomicWriteFile`; this feed\n * `consume()`s on every chokidar event. (Pitfall 6 invariant.)\n */\n suppression: SuppressionSet;\n /** Optional stderr logger; defaults to silent. */\n log?: (msg: string) => void;\n}\n\nexport class ObsidianFsChangeFeed implements ChangeFeed {\n readonly handle: SourceHandle;\n readonly capabilities: ChangeFeedCapabilities = {\n watch: \"push\",\n /**\n * Phase 1 emits delete+create rather than a tagged rename event.\n * Honest publication per Invariant I-7 — the conformance test\n * asserts no `{kind: \"rename\"}` event is observed when this flag\n * is false.\n */\n emitsRename: false,\n };\n\n private readonly vault: Vault;\n private readonly suppression: SuppressionSet;\n private readonly log: (msg: string) => void;\n private readonly handlers = new Set<(e: ChangeEvent) => void | Promise<void>>();\n private fsWatcher: FSWatcher | null = null;\n private startPromise: Promise<void> | null = null;\n private closed = false;\n\n constructor(options: ObsidianFsChangeFeedOptions) {\n this.vault = options.vault;\n this.suppression = options.suppression;\n this.log = options.log ?? ((_m) => {});\n this.handle = parseSourceHandle(`${SCHEME}://${this.vault.config.name}`);\n }\n\n subscribe(handler: (e: ChangeEvent) => void | Promise<void>): Disposable {\n if (this.closed) {\n // After close, register-but-never-fire is the contract floor for\n // the conformance suite. Returning an inert Disposable mirrors\n // what users get if they subscribe before start() has resolved.\n return { [Symbol.dispose]: () => void 0 };\n }\n this.handlers.add(handler);\n // Lazy start — first subscribe brings the watcher up. Subsequent\n // subscribes attach to the same watcher.\n if (!this.startPromise) {\n this.startPromise = this.start();\n }\n return {\n [Symbol.dispose]: () => {\n this.handlers.delete(handler);\n },\n };\n }\n\n /**\n * Wait until the chokidar watcher has reported \"ready\". Test-only\n * helper — the conformance test awaits this between `subscribe` and\n * its first synthetic event so the watcher has surveyed the dir.\n */\n async ready(): Promise<void> {\n if (this.startPromise) {\n await this.startPromise;\n }\n }\n\n async close(): Promise<void> {\n if (this.closed) return; // idempotent\n this.closed = true;\n this.handlers.clear();\n if (this.fsWatcher) {\n await this.fsWatcher.close();\n this.fsWatcher = null;\n }\n }\n\n // ─── internal ──────────────────────────────────────────────────────────\n\n private async start(): Promise<void> {\n if (this.closed) return;\n const vaultPath = this.vault.config.path;\n const excludes = this.vault.config.exclude_globs ?? [];\n\n const watcher = chokidar.watch(vaultPath, buildChokidarOptions(vaultPath, excludes));\n this.fsWatcher = watcher;\n\n watcher.on(\"add\", (absolutePath) => this.onFsEvent(absolutePath, \"create\"));\n watcher.on(\"change\", (absolutePath) => this.onFsEvent(absolutePath, \"update\"));\n watcher.on(\"unlink\", (absolutePath) => this.onFsEvent(absolutePath, \"delete\"));\n watcher.on(\"error\", (err) => {\n const message = errorMessage(err);\n this.log(`fs watcher error: ${message}`);\n });\n\n await new Promise<void>((resolve) => {\n watcher.once(\"ready\", () => resolve());\n });\n }\n\n private onFsEvent(absolutePath: string, kind: \"create\" | \"update\" | \"delete\"): void {\n if (this.closed) return;\n // Only emit for markdown files — same v1 filter.\n if (!absolutePath.endsWith(\".md\")) return;\n\n const relativePath = this.toRelative(absolutePath);\n\n // Pitfall 6: own-write suppression. The delivery adapter marked this\n // path on the shared SuppressionSet before its atomic rename; consume\n // the entry and drop the event so we don't loop.\n if (this.suppression.consume(relativePath)) {\n this.log(`suppressed ${kind} ${relativePath} (own write)`);\n return;\n }\n\n const id: DocId = formatDocId(SCHEME, this.vault.config.name, relativePath);\n const event: ChangeEvent = { kind, id, at: Date.now() };\n this.fanout(event);\n }\n\n private toRelative(absolutePath: string): string {\n const root = this.vault.config.path;\n let rel = absolutePath;\n if (rel.startsWith(root)) rel = rel.slice(root.length);\n if (rel.startsWith(nativeSep) || rel.startsWith(\"/\")) rel = rel.slice(1);\n return rel.split(nativeSep).join(\"/\");\n }\n\n private fanout(event: ChangeEvent): void {\n // Snapshot handlers before iterating — a handler may dispose during\n // its own callback, which would otherwise corrupt the iteration.\n for (const handler of [...this.handlers]) {\n try {\n const result = handler(event);\n if (result && typeof (result as Promise<void>).then === \"function\") {\n (result as Promise<void>).catch((err: unknown) => {\n const message = errorMessage(err);\n this.log(`handler error: ${message}`);\n });\n }\n } catch (err) {\n const message = errorMessage(err);\n this.log(`handler error: ${message}`);\n }\n }\n }\n}\n","/**\n * `obsidian-fs` ChangeFeed adapter barrel.\n *\n * Re-exports the relocated v1 VaultWatcher / DebouncedQueue / SuppressionSet\n * primitives PLUS the new `ObsidianFsChangeFeed` facade implementing\n * the `ChangeFeed` interface (ADR-002 §ChangeFeed, plan 01-05 task 02).\n *\n * Invariant I-1 (ADR-002): chokidar imports live ONLY under this directory.\n * Plan 01-06 ships the lint script that enforces this mechanically.\n */\n\nexport { VaultWatcher } from \"./watcher.js\";\nexport type { VaultWatcherOptions } from \"./watcher.js\";\nexport { DebouncedQueue } from \"./queue.js\";\nexport type { QueueEvent, DebouncedQueueOptions } from \"./queue.js\";\nexport { SuppressionSet } from \"./suppression.js\";\nexport type { SuppressionOptions } from \"./suppression.js\";\nexport { ObsidianFsChangeFeed } from \"./change-feed.js\";\nexport type { ObsidianFsChangeFeedOptions } from \"./change-feed.js\";\n","// Single literal source of truth for v1 tools/list. Imported by src/server.ts (runtime) and evals/v1-baseline/dump-tools.mjs (snapshot generator).\n//\n// Two exports:\n//\n// - `TOOLS`: ReadonlyArray of `{name, description, inputSchema}` — the\n// JSON Schema literal source of truth. Drives `dump-tools.mjs` and the\n// pinned `evals/v1-baseline/tools-list.snapshot.json`. MUST stay\n// JSON-serializable / snapshot-stable. Do not add non-serializable\n// fields here.\n//\n// - `TOOL_SCHEMAS`: Record<ToolName, ZodRawShape> — the Zod 4 raw\n// shapes paired with each tool. Passed to `McpServer.registerTool`\n// (SDK 1.29) for type-safe argument parsing + auto-derived\n// `tools/list` publication. The shapes carry per-field `.describe()`\n// calls so the SDK-published JSON Schema retains rich descriptions.\n//\n// Plan 01-05 design note (deviation from plan literal): the plan asked\n// for a single `TOOLS` entry carrying both `inputSchema` and `zodSchema`,\n// and for `registerTool` to receive `inputSchema: tool.inputSchema` (raw\n// JSON Schema literal). Both proved blocking under SDK 1.29:\n//\n// 1. Adding a Zod schema field onto each `TOOLS` entry breaks the\n// snapshot generator (Zod objects are not JSON-serializable; the\n// pinned snapshot would change shape).\n// 2. SDK 1.29 `registerTool` validates that `inputSchema` is either a\n// Zod schema instance or a Zod raw shape (see\n// node_modules/@modelcontextprotocol/sdk/.../mcp.js:861-872 —\n// `getZodSchemaObject` throws on plain JSON Schema). Passing the\n// raw JSON Schema literal is not supported by the API.\n//\n// The two-export design preserves the plan's INTENT:\n// - Snapshot stability (TOOLS literal unchanged).\n// - Single source of truth for v1 tools/list shape (TOOLS).\n// - Zod 4 at handler time + Zod-driven publication via the SDK's\n// own `toJsonSchemaCompat` (TOOL_SCHEMAS).\n// - End-to-end description propagation verified empirically — the\n// Pitfall 2 / SDK#1143 workaround is moot in SDK 1.29 (descriptions\n// pass through both the top-level `description` and per-field\n// `.describe()` chains).\n\nexport const TOOLS = [\n {\n name: \"list_vaults\",\n description:\n \"List configured vaults with their status (note count, last indexed run). \" +\n \"DEPRECATED since v2.0.0 — prefer MCP Resource `vault-memory://vaults` for agent discovery. \" +\n \"The tool remains callable through v2.x; removal scheduled for v3.0.0.\",\n inputSchema: { type: \"object\", properties: {} },\n },\n {\n name: \"read_note\",\n description: \"Read the full content + frontmatter of a note by its vault-relative path.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\"],\n properties: {\n vault: { type: \"string\", description: \"Configured vault name\" },\n path: {\n type: \"string\",\n description: \"Vault-relative path with forward slashes, ending in .md\",\n },\n },\n },\n },\n {\n name: \"search_semantic\",\n description: \"Semantic search via embedding cosine similarity. Searches all vaults by default.\",\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: { type: \"string\" },\n vaults: { type: \"array\", items: { type: \"string\" } },\n top_k: {\n type: \"integer\",\n minimum: 1,\n maximum: 100,\n default: 10,\n },\n exclude_paths: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"Glob patterns (e.g. '_research/eval.md', '**/index.md') of paths to exclude.\",\n },\n },\n },\n },\n {\n name: \"search_text\",\n description: \"Full-text BM25 search via SQLite FTS5. Best for exact-word and phrase matches.\",\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: {\n type: \"string\",\n description: \"FTS5 query — whitespace-separated tokens are AND'd; use OR explicitly.\",\n },\n vaults: { type: \"array\", items: { type: \"string\" } },\n top_k: {\n type: \"integer\",\n minimum: 1,\n maximum: 100,\n default: 10,\n },\n exclude_paths: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Glob patterns of paths to exclude.\",\n },\n },\n },\n },\n {\n name: \"search_hybrid\",\n description:\n \"Hybrid search: combines semantic (embedding) and BM25 (full-text) results via Reciprocal Rank Fusion. Best general-purpose query. Pass `expand: {hops: 1}` to auto-attach 1–2 hop typed-edge neighbors as `expansions[]` per hit (preserves ranking; runs after recency/authority rescore).\",\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: { type: \"string\" },\n vaults: { type: \"array\", items: { type: \"string\" } },\n top_k: {\n type: \"integer\",\n minimum: 1,\n maximum: 100,\n default: 10,\n },\n rrf_k: {\n type: \"integer\",\n minimum: 1,\n maximum: 1000,\n default: 60,\n description: \"RRF constant — higher dampens emphasis on top ranks.\",\n },\n exclude_paths: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Glob patterns of paths to exclude.\",\n },\n rerank: {\n type: \"boolean\",\n default: false,\n description:\n \"Apply a cross-encoder rerank over the top candidates. Requires `reranker_model` in server config; silently ignored otherwise.\",\n },\n recency_weight: {\n type: \"number\",\n default: 0,\n description:\n \"Phase 3 (D-07, ASM-07): additive recency term coefficient. final_score = rrf + recency_weight * exp(-age_days / half_life_days). Default 0 (no recency pressure — v1 behavior).\",\n },\n authority_weight: {\n type: \"number\",\n default: 0,\n description:\n \"Phase 3 (D-07, ASM-07): additive authority term coefficient. Adds `authority_weight * 1` for docs whose frontmatter has `authoritative: true`. Default 0.\",\n },\n half_life_days: {\n type: \"number\",\n minimum: 0,\n default: 30,\n description:\n \"Phase 3 (D-07): half-life for the recency exponential decay, in days. Default 30. Only meaningful when recency_weight > 0.\",\n },\n include_superseded: {\n type: \"boolean\",\n default: false,\n description:\n \"Phase 3 (D-08, ASM-08): when false (default), docs whose frontmatter has `status: superseded` are excluded at SQL level via the notes_status partial index. Set true to reveal them.\",\n },\n // ── Phase 4 / 04-04 / GRA-03 (D-15): additive auto-expansion ──\n // When omitted, search_hybrid behavior is byte-identical to v1.\n expand: {\n type: \"object\",\n required: [\"hops\"],\n description:\n \"Phase 4 (D-15, D-16): auto-attach 1–2 hop typed-edge neighbors as `expansions[]` per hit. Runs AFTER recency/authority rescore (D-16); never participates in score computation; top-K ranking unchanged.\",\n properties: {\n hops: { type: \"number\", enum: [1, 2] },\n direction: {\n type: \"string\",\n enum: [\"forward\", \"backward\", \"both\"],\n default: \"both\",\n },\n edge_types: {\n type: \"array\",\n items: {\n type: \"string\",\n enum: [\"wikilink\", \"mention\", \"frontmatter-ref\", \"hyperlink\"],\n },\n },\n },\n },\n },\n },\n },\n {\n name: \"list_backlinks\",\n description:\n \"Find all notes that link TO a given note. \" +\n \"DEPRECATED since v2.0.0 — prefer MCP Resource `vault-memory://backlinks/{vault}/{+docId}` \" +\n \"for agent discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\"],\n properties: {\n vault: { type: \"string\" },\n path: { type: \"string\" },\n },\n },\n },\n {\n name: \"list_forward_links\",\n description: \"List all wikilinks FROM a given note. Optionally include broken links.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\"],\n properties: {\n vault: { type: \"string\" },\n path: { type: \"string\" },\n include_broken: { type: \"boolean\", default: true },\n },\n },\n },\n {\n name: \"find_broken_links\",\n description: \"List all wikilinks in a vault that point to non-existent notes.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: { vault: { type: \"string\" } },\n },\n },\n {\n name: \"query_frontmatter\",\n description:\n \"Filter notes by their YAML frontmatter. Supports equality, $in, $exists, $contains predicates. Multiple keys are AND-combined.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"where\"],\n properties: {\n vault: { type: \"string\" },\n where: {\n type: \"object\",\n description:\n \"Field-name → predicate map. Predicate is a scalar (equality) or { $in: [...] } | { $exists: bool } | { $contains: scalar }.\",\n },\n limit: {\n type: \"integer\",\n minimum: 1,\n maximum: 1000,\n default: 100,\n },\n },\n },\n },\n {\n name: \"write_note\",\n description:\n \"Atomically create or overwrite a note. Requires write_enabled=true. Use expected_hash for safe overwrites (read the note first, pass its hash). Omit expected_hash only when creating a new note.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\", \"content\"],\n properties: {\n vault: { type: \"string\" },\n path: {\n type: \"string\",\n description: \"Vault-relative .md path, forward slashes.\",\n },\n content: {\n type: \"string\",\n description: \"Markdown body WITHOUT --- frontmatter delimiters.\",\n },\n frontmatter: {\n type: [\"object\", \"null\"],\n description: \"Optional frontmatter object. Set null to write no frontmatter block.\",\n },\n expected_hash: {\n type: \"string\",\n description: \"Required for overwrites — get it from read_note.\",\n },\n client_id: { type: \"string\" },\n },\n },\n },\n {\n name: \"update_frontmatter\",\n description:\n \"Modify a note's frontmatter only. The body is preserved bytegenau. Merge DSL: scalar=set, {$unset:true}=delete, {$push:x}=array append, {$pull:x}=array remove.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\", \"merge\"],\n properties: {\n vault: { type: \"string\" },\n path: { type: \"string\" },\n merge: {\n type: \"object\",\n description: \"Field → value | {$unset:bool} | {$push:scalar} | {$pull:scalar}\",\n },\n expected_hash: { type: \"string\" },\n client_id: { type: \"string\" },\n },\n },\n },\n {\n name: \"delete_note\",\n description: \"Delete a note. Requires write_enabled=true AND expected_hash (no blind deletes).\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"path\", \"expected_hash\"],\n properties: {\n vault: { type: \"string\" },\n path: { type: \"string\" },\n expected_hash: { type: \"string\" },\n client_id: { type: \"string\" },\n },\n },\n },\n {\n name: \"audit_log\",\n description:\n \"Query the write audit trail for a vault. Filterable by note path, operation type, or time. Default limit 50.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: {\n vault: { type: \"string\" },\n note_path: { type: \"string\" },\n op: { type: \"string\", enum: [\"create\", \"update\", \"delete\"] },\n since: {\n type: \"integer\",\n description: \"Epoch ms — entries at or after this timestamp.\",\n },\n limit: { type: \"integer\", minimum: 1, maximum: 1000, default: 50 },\n is_memory_sink_write: {\n type: \"boolean\",\n description:\n \"Filter rows to memory-sink writes only (true) or non-memory writes only (false). Omit to include all. See docs/tools/audit_log.md.\",\n },\n },\n },\n },\n {\n name: \"list_models\",\n description:\n \"List all embedding models registered for a vault, with dim, \" +\n \"active flag, and how many chunks have been embedded under each. \" +\n \"Use before start_shadow_index / switch_active_model. \" +\n \"DEPRECATED since v2.0.0 — prefer MCP Resource `vault-memory://models/{vault}` for agent \" +\n \"discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: { vault: { type: \"string\" } },\n },\n },\n {\n name: \"start_shadow_index\",\n description:\n \"Backfill embeddings for a secondary (shadow) model over every \" +\n \"chunk in the vault. The active model is untouched — search keeps \" +\n \"working during the run. Idempotent (resumable). Run \" +\n \"switch_active_model once complete to promote the shadow.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"model\"],\n properties: {\n vault: { type: \"string\" },\n model: {\n type: \"string\",\n description: \"Ollama model name, e.g. 'bge-m3' or 'embeddinggemma'.\",\n },\n batch_size: {\n type: \"integer\",\n minimum: 1,\n maximum: 256,\n description: \"Embed batch size — default 16.\",\n },\n },\n },\n },\n {\n name: \"switch_active_model\",\n description:\n \"Atomically promote a registered model to active. Fails with \" +\n \"ok:false / reason:'incomplete' if any chunk is missing a shadow \" +\n \"embedding for the target model.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"model_name\"],\n properties: {\n vault: { type: \"string\" },\n model_name: { type: \"string\" },\n },\n },\n },\n {\n name: \"vacuum_embeddings\",\n description:\n \"Drop orphaned embedding rows whose chunk_id no longer exists in \" +\n \"the chunks table. Safe and idempotent; does not touch live data. \" +\n \"Useful after migrations from pre-v0.7.0 schemas where chunk \" +\n \"deletion did not always cascade to the derived layer.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: { vault: { type: \"string\" } },\n },\n },\n {\n name: \"index_runs\",\n description: \"List recent index runs for a vault — what was scanned, when, how long, errors.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: {\n vault: { type: \"string\" },\n limit: { type: \"integer\", minimum: 1, maximum: 200, default: 20 },\n },\n },\n },\n {\n name: \"search\",\n // Tool description names \"Claude.ai\" + \"Deep-Research\" as the // vault-memory:claude-ok\n // real OB1-connector-ecosystem product names; not a Claude-only coupling.\n description:\n \"OB1-compatible search adapter. Returns a flat list of {id, title, url, snippet} for connector ecosystems (ChatGPT Custom Connectors, Claude.ai, Deep-Research). Backed by hybrid (semantic+BM25+RRF) search. For richer output use search_hybrid.\", // vault-memory:claude-ok\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: { type: \"string\" },\n limit: { type: \"integer\", minimum: 1, maximum: 50, default: 10 },\n },\n },\n },\n {\n name: \"fetch\",\n description:\n \"OB1-compatible fetch adapter. Resolves an opaque id (from `search`) to {id, title, text, url, metadata}. Backed by read_note.\",\n inputSchema: {\n type: \"object\",\n required: [\"id\"],\n properties: {\n id: {\n type: \"string\",\n description: \"Opaque id from `search` results, format: <vault>:<vault-relative-path>\",\n },\n },\n },\n },\n {\n name: \"vault_stats\",\n description:\n \"Vault overview for agent self-orientation: note/word counts, top tags, top frontmatter keys, embedding model, last index run. Omit `vault` to get all configured vaults. \" +\n \"DEPRECATED since v2.0.0 — prefer MCP Resource `vault-memory://stats/{vault}` for agent \" +\n \"discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.\",\n inputSchema: {\n type: \"object\",\n properties: {\n vault: { type: \"string\", description: \"Optional. Omit for all vaults.\" },\n },\n },\n },\n {\n name: \"recent_notes\",\n description:\n \"List recently modified notes (mtime DESC). Use for agent self-orientation: 'what has the user been working on lately?'. No vector search, just SQL. \" +\n \"DEPRECATED since v2.0.0 — prefer MCP Resource `vault-memory://recent/{vault}` for agent \" +\n \"discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.\",\n inputSchema: {\n type: \"object\",\n properties: {\n vault: { type: \"string\", description: \"Optional. Omit for all vaults.\" },\n limit: { type: \"integer\", minimum: 1, maximum: 200, default: 20 },\n since: {\n type: \"integer\",\n description: \"Optional unix-ms threshold. Only notes with mtime > since.\",\n },\n },\n },\n },\n {\n name: \"suggest_frontmatter\",\n description:\n \"Suggest frontmatter fields for a note based on folder-conventions, wikilink-neighborhood, and title/body content-heuristics. Returns {existing, suggestions, conflicts}. Two input modes: (1) existing note via {path}; (2) draft via {content, folder_hint, title}. At least one of path/content required. Suggestions sorted by confidence DESC; conflicts list disagreements between sources.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\"],\n properties: {\n vault: { type: \"string\" },\n path: {\n type: \"string\",\n description:\n \"Vault-relative path. Required for existing-note mode; for drafts, pass content instead (folder_hint controls folder-inference).\",\n },\n content: {\n type: \"string\",\n description:\n \"Draft markdown body. When set, content-heuristics layer runs. If path is set AND content is omitted, the existing note's stored content is used.\",\n },\n title: {\n type: \"string\",\n description:\n \"Title for content-heuristics. Falls back to path basename or first heading.\",\n },\n folder_hint: {\n type: \"string\",\n description:\n \"For draft mode: the target folder (e.g. 'Personen/'). Ignored when `path` is set.\",\n },\n },\n },\n },\n // ── Phase 2 memory tools (Plan 02-04 + 02-05) ─────────────────────────────\n {\n name: \"record_observation\",\n description:\n \"Record a new memory observation under the labeled MemorySink for a vault. \" +\n \"Required provenance properties (source, confidence, evidence, status, observed_at, type, superseded_by) \" +\n \"are auto-filled from arguments; `properties` is an escape hatch for contract-allowed extras \" +\n \"and overrides any sugar default (D-02 — caller-last merge). \" +\n \"Writes route through DeliveryAdapter.write() and pass through the centralized provenance validator.\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"claim\", \"evidence\", \"confidence\", \"type\"],\n properties: {\n vault: { type: \"string\", description: \"Vault name (registered in [vaults] config)\" },\n claim: {\n type: \"string\",\n description:\n \"Short natural-language statement of the observation (becomes title + body).\",\n },\n evidence: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"DocIds or quoted source spans supporting the claim; empty array allowed.\",\n },\n confidence: {\n type: \"string\",\n enum: [\"direct\", \"inferred\", \"uncertain\"],\n description: \"How the agent arrived at this claim.\",\n },\n type: {\n type: \"string\",\n description:\n \"Observation type per the sink contract (e.g. 'observation', 'hypothesis', 'decision').\",\n },\n sink: {\n type: \"string\",\n description:\n \"Memory sink name OR full obsidian-fs://… handle. Defaults to the vault's default sink.\",\n },\n properties: {\n type: \"object\",\n additionalProperties: true,\n description:\n \"Escape-hatch: contract-allowed extra properties; merged AFTER sugar args (caller wins).\",\n },\n },\n },\n },\n {\n name: \"supersede\",\n description:\n \"Mark an existing memory document as superseded by a replacement document. \" +\n \"Forward-only — the replacement doc is NOT touched; back-links are derived by the Phase 4 \" +\n 'graph layer at query time. Atomic single OCC update on the OLD doc; sets status=\"superseded\", ' +\n \"superseded_by, and superseded_reason.\",\n inputSchema: {\n type: \"object\",\n required: [\"doc_id\", \"replacement_doc_id\", \"reason\"],\n properties: {\n doc_id: {\n type: \"string\",\n description: \"DocId of the document being superseded.\",\n },\n replacement_doc_id: {\n type: \"string\",\n description: \"DocId of the replacement document.\",\n },\n reason: {\n type: \"string\",\n description: \"Why the old document is being retired; written to superseded_reason.\",\n },\n },\n },\n },\n // ── Phase 5 brief tools (Plan 05-02 / BRF-03) ────────────────────────────\n {\n name: \"compile_brief\",\n description:\n \"Compile a brief from caller-supplied source documents and write it to the briefs sink. \" +\n \"Resolves the LLM via the D-10 capability-first ladder (MCP Sampling → local Ollama → \" +\n \"caller `prepared_text` → structured error). Enforces D-11 wikilink emission per source \" +\n \"(appends a `## Sources` footer when the LLM omits them) and writes through DeliveryAdapter. \" +\n \"On target collision, auto-supersedes the prior brief via the Phase 2 supersede chain (D-12).\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"target\", \"source_doc_ids\", \"purpose\"],\n properties: {\n vault: { type: \"string\", description: \"Vault name (registered in [vaults] config)\" },\n target: {\n type: \"string\",\n description: \"Stable cross-version handle for the brief (e.g. 'atlas-q3').\",\n },\n source_doc_ids: {\n type: \"array\",\n items: { type: \"string\" },\n minItems: 1,\n maxItems: 50,\n description: \"DocIds the brief is compiled from; deduped, capped at 50 (D-03).\",\n },\n purpose: {\n type: \"string\",\n minLength: 1,\n maxLength: 500,\n description: \"Free-form purpose; bounded so list_briefs stays scannable.\",\n },\n max_tokens: {\n type: \"integer\",\n minimum: 1,\n default: 2000,\n description: \"Hint for the LLM ladder; default 2000.\",\n },\n prepared_text: {\n type: \"string\",\n description:\n \"D-10 tier 3 fallback when no LLM is reachable — verbatim body to stitch in.\",\n },\n sink: {\n type: \"string\",\n description: \"Override the default `_memory/_briefs` sink.\",\n },\n },\n },\n },\n {\n name: \"get_brief\",\n description:\n \"Look up a brief by target slug. D-13 decision tree: staleness dominates; age is \" +\n \"independent; follow the supersede chain to the terminal brief. Returns null when the \" +\n \"caller MUST recompile (stale + !allow_stale OR too_old + !allow_stale).\",\n inputSchema: {\n type: \"object\",\n required: [\"vault\", \"target\"],\n properties: {\n vault: { type: \"string\", description: \"Vault name (registered in [vaults] config)\" },\n target: { type: \"string\", description: \"Stable cross-version handle for the brief.\" },\n max_age_days: {\n type: \"integer\",\n minimum: 0,\n description: \"Reject briefs older than this many days unless allow_stale=true.\",\n },\n allow_stale: {\n type: \"boolean\",\n default: false,\n description:\n \"When true, return briefs flagged stale or too_old with annotation rather than null.\",\n },\n },\n },\n },\n // ── Phase 3 assembly tools (Plan 03-02 / ASM-02) ─────────────────────────\n {\n name: \"get_outline\",\n description:\n \"Return the navigable section tree for a document. Each OutlineNode \" +\n \"carries an `anchor` (the section's citation token), `heading_path` \" +\n \"(root → leaf), `heading_text`, `level`, and `chunk_ids` (v1 chunk-table \" +\n \"IDs in that section). Consume `anchor` + `heading_path` as the section-\" +\n \"level half of the citation packet. Unknown doc_id returns an error \" +\n \"response with {error:'doc_not_found', doc_id}.\",\n inputSchema: {\n type: \"object\",\n required: [\"doc_id\"],\n properties: {\n doc_id: {\n type: \"string\",\n description: \"Opaque DocId (obsidian-fs://<vault>/<path>) of the document\",\n },\n vaults: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional vault filter; usually omitted (the DocId names a vault).\",\n },\n },\n },\n },\n // ── Phase 3 assembly tools (Plan 03-03) ──────────────────────────────────\n {\n name: \"search_sections\",\n description:\n \"Section-level retrieval. Composes the v1 hybrid (semantic + BM25 + RRF) pipeline with \" +\n \"a chunk-to-section promotion step: runs hybrid with an inflated top_k = limit × 5, \" +\n \"promotes each chunk hit to its enclosing section, dedupes by (note, section anchor), \" +\n \"scores each section as the MAX of its constituent chunks, tie-breaks by \" +\n \"chunk_id_first ASC, and returns the top `limit` sections. Each hit carries an 8-field \" +\n \"citation packet (D-01) with a non-empty section heading_path PLUS the section anchor, \" +\n \"score, contributing chunk_ids, and an optional snippet from the best-scoring chunk. \" +\n \"Use when you want WHOLE-SECTION context, not a chunk window.\",\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: { type: \"string\" },\n limit: {\n type: \"integer\",\n minimum: 1,\n maximum: 50,\n default: 10,\n },\n vaults: { type: \"array\", items: { type: \"string\" } },\n recency_weight: {\n type: \"number\",\n minimum: 0,\n default: 0,\n description:\n \"Forward-compat with slice 03-05's authority/staleness rescore. \" +\n \"Accepted today; ignored until 03-05 lands.\",\n },\n authority_weight: {\n type: \"number\",\n minimum: 0,\n default: 0,\n description:\n \"Forward-compat with slice 03-05's authority/staleness rescore. \" +\n \"Accepted today; ignored until 03-05 lands.\",\n },\n include_superseded: {\n type: \"boolean\",\n default: false,\n description:\n \"Forward-compat with slice 03-05. When false (default), superseded docs are \" +\n \"filtered out at the chunk level inside hybrid; accepted today, ignored until 03-05.\",\n },\n },\n },\n },\n // ── Phase 2 memory tools (Plan 02-05) ────────────────────────────────────\n {\n name: \"recall\",\n description:\n \"Retrieve memory documents from one or more labeled MemorySinks, filtered by \" +\n \"provenance (min_confidence, types, max_age_days) and ranked by recency (observed_at \" +\n \"DESC). Returns citation packets (doc_id, source_handle, title, heading_path, mtime, \" +\n \"hash, display_url, properties) — the same 8-field shape Phase 3 assembly tools use. \" +\n \"Superseded documents are hidden by default.\",\n inputSchema: {\n type: \"object\",\n required: [\"query\"],\n properties: {\n query: {\n type: \"string\",\n description: \"Natural-language query; routes through hybrid (semantic + BM25) search.\",\n },\n min_confidence: {\n type: \"string\",\n enum: [\"direct\", \"inferred\", \"uncertain\"],\n description:\n \"Exclude docs whose confidence ordinal is lower than this (direct=3, inferred=2, uncertain=1).\",\n },\n types: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Restrict to docs whose `type` property is in this set.\",\n },\n max_age_days: {\n type: \"integer\",\n minimum: 1,\n description: \"Exclude docs whose `observed_at` is older than this many days.\",\n },\n sink: {\n type: \"string\",\n description:\n \"Memory sink name OR full obsidian-fs://… handle. Defaults to all configured sinks.\",\n },\n limit: {\n type: \"integer\",\n minimum: 1,\n maximum: 200,\n default: 20,\n description: \"Max results AFTER filter+sort.\",\n },\n vaults: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Restrict to these vault names; defaults to all configured.\",\n },\n },\n },\n },\n // ── Phase 3 assembly tools (Plan 03-04 / ASM-01) ─────────────────────────\n {\n name: \"get_document_bundle\",\n description:\n \"Document-tree retrieval. Returns a structured bundle for a single document: \" +\n \"{ anchor (citation packet + optional status/superseded_by), outline (section tree \" +\n \"via buildOutlineTree — same shape as get_outline.root), backlinks (citation packets \" +\n '+ property_snippet + relation:\"wikilink\"), forward_links (same shape; broken links ' +\n \"omitted), recent_edits (≤10 most recent audit_log rows mapped to {at, op, client_id, \" +\n \"is_memory_sink_write?}) }. Every citation packet is the full 8-field D-01 shape from \" +\n \"src/memory/citation-packet.ts. v2.0.0 accepts only depth:1 (one-hop links); the field \" +\n \"is zod-pinned to z.literal(1) for forward compatibility. recent_edits is keyed by the \" +\n \"anchor's CURRENT note path — pre-rename history is preserved in audit_log but not \" +\n \"surfaced here (Phase 4 widens). Unknown doc_id returns \" +\n '{ isError: true, error: \"doc_not_found\", doc_id }.',\n inputSchema: {\n type: \"object\",\n required: [\"doc_id\"],\n properties: {\n doc_id: {\n type: \"string\",\n description: \"Opaque DocId (obsidian-fs://<vault>/<path>) of the anchor document.\",\n },\n depth: {\n type: \"integer\",\n enum: [1],\n default: 1,\n description:\n \"Depth of the link walk. v2.0.0 accepts only depth:1 (one-hop). Phase 4 may widen.\",\n },\n vaults: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Optional vault filter; usually omitted (the DocId names a vault).\",\n },\n },\n },\n },\n // ── Phase 4 graph tools (Plan 04-03 / GRA-01) ───────────────────────────\n {\n name: \"expand\",\n description:\n \"Typed-edge BFS retrieval. Returns the typed-edge neighborhood of one or more \" +\n \"seed documents as a flat array of citation packets, each carrying \" +\n \"`via: {seed_doc_id, hop, edge_type, direction}` provenance. Hops hard-capped \" +\n \"at 2 (v2.0.0). Default direction = 'both'. Filterable by edge_type and by \" +\n \"document properties (strict equality, no operators). Memory-sink documents \" +\n \"(`_memory/...`) surface only when they are already linked from a user note in \" +\n \"the result set (per ADR-004 memory-namespace opacity rule). Frontmatter-ref \" +\n \"edges are extracted heuristically: `[[...]]` syntax in any property value OR \" +\n \"allowlisted property names (`assignee`, `owner`, `project`, `related`, \" +\n \"`parent`, `child`, `attendees`, `superseded_by`) matched against \" +\n \"`note_aliases`. `include_superseded` defaults to false (Phase 2 D-03 forward-\" +\n \"only supersede). Unknown seed_doc_ids do not throw — they are returned in a \" +\n \"`warnings: [{seed_doc_id, reason: 'unknown_doc'}]` array. Shortest path wins \" +\n \"on dedup; ties broken by (seed_doc_id, edge_type, direction).\",\n inputSchema: {\n type: \"object\",\n required: [\"seed_doc_ids\", \"hops\"],\n properties: {\n seed_doc_ids: {\n type: \"array\",\n minItems: 1,\n items: {\n type: \"string\",\n description: \"Opaque DocId (e.g. obsidian-fs://<vault>/<path>).\",\n },\n },\n hops: {\n type: \"number\",\n enum: [1, 2],\n description: \"Hop cap (1 or 2). v2.0.0 hard-caps at 2.\",\n },\n direction: {\n type: \"string\",\n enum: [\"forward\", \"backward\", \"both\"],\n default: \"both\",\n description: \"Edge traversal direction; default 'both'.\",\n },\n edge_types: {\n type: \"array\",\n items: {\n type: \"string\",\n enum: [\"wikilink\", \"mention\", \"frontmatter-ref\", \"hyperlink\"],\n },\n description: \"Optional filter on edge types; default = all four types.\",\n },\n filter_properties: {\n type: \"object\",\n additionalProperties: true,\n description: \"Strict-equality predicate on document properties (e.g. {type: 'Project'}).\",\n },\n include_superseded: {\n type: \"boolean\",\n default: false,\n description:\n \"When false (default), docs whose properties.status === 'superseded' are dropped.\",\n },\n },\n },\n },\n // ── Phase 4 graph tools (Plan 04-05 / GRA-02) ───────────────────────────\n {\n name: \"cluster\",\n description:\n \"Community detection over the typed-edge graph via Louvain \" +\n \"modularity (Blondel et al. 2008) using `graphology` + \" +\n \"`graphology-communities-louvain`. Deterministic: same input \" +\n \"produces byte-identical cluster_id assignment via DocId-sorted \" +\n \"node insertion + seeded RNG (`vault-memory-cluster-v1`). \" +\n \"cluster_id = smallest member DocId per community. Hard-capped at \" +\n \"5000 nodes; pass `force: true` to override. Either `query` \" +\n \"(composes search_hybrid + expand 1-hop) OR `seed_doc_ids` (uses \" +\n \"provided seeds + induced 1-hop neighborhood); not both — passing \" +\n \"both returns {ok:false, reason:'both_seeds_and_query'}. On the \" +\n \"`query` path with multiple vaults configured, the `vault` field \" +\n \"is required so search scope is deterministic; single-vault setups \" +\n \"may omit it (returns {ok:false, reason:'vault_required'} otherwise). \" +\n \"Returns per-cluster {cluster_id, size, members[], summary: {top_types, \" +\n \"top_titles, edge_density}}. No LLM enrichment — summary fields \" +\n \"are pure-deterministic computations (LLM enrichment is Phase 5 \" +\n \"brief layer's job). _memory opacity inherited from expand() \" +\n \"(Plan 04-03).\",\n inputSchema: {\n type: \"object\",\n required: [\"method\"],\n properties: {\n query: {\n type: \"string\",\n description:\n \"Natural-language query. When set, composes search_hybrid + expand(hops=1, both). Mutually exclusive with seed_doc_ids.\",\n },\n seed_doc_ids: {\n type: \"array\",\n minItems: 1,\n items: { type: \"string\" },\n description:\n \"1+ opaque DocIds. When set, cluster() uses these seeds + their induced 1-hop neighborhood. Mutually exclusive with query.\",\n },\n vault: {\n type: \"string\",\n description:\n \"Vault name to scope the `query` search against (CR-02). Required on the `query` path when multiple vaults are configured; optional on single-vault setups. Ignored on the `seed_doc_ids` path (the vault is inferred from each DocId).\",\n },\n method: {\n type: \"string\",\n enum: [\"edge-community\"],\n description: \"Clustering algorithm. v2.0.0 supports only 'edge-community' (Louvain).\",\n },\n query_top_k: {\n type: \"integer\",\n minimum: 1,\n maximum: 200,\n default: 50,\n description:\n \"Only used in the query path: how many top hits to retrieve before expansion. Default 50.\",\n },\n force: {\n type: \"boolean\",\n default: false,\n description:\n \"Bypass the 5000-node hard cap. When false (default), oversized inputs return {ok:false, reason:'node_count_exceeded'}.\",\n },\n },\n },\n },\n // ── Phase 3 assembly tools (Plan 03-06) ──────────────────────────────────\n {\n name: \"assemble_dossier\",\n description:\n \"Resolve a {type, key} pair to an anchor document and walk its backlinks \" +\n \"into a structured dossier: { anchor (citation packet), linked_documents \" +\n \"(citation packets + relation), property_rollups (linked_count, linked_types, \" +\n \"status_distribution) }. Strict properties.type match (D-03). The key matches \" +\n \"the candidate's title OR any entry in properties.aliases (D-04). \" +\n 'v2.0.0 returns relation:\"wikilink\" on every linked_documents entry (the v1 ' +\n \"wikilinks table is the only edge source); Phase 4 (GRA-04) widens to typed edges. \" +\n \"Superseded backlinks are NOT filtered — dossiers show the whole picture (CONTEXT D-04).\",\n inputSchema: {\n type: \"object\",\n required: [\"type\", \"key\"],\n properties: {\n type: {\n type: \"string\",\n description:\n \"Exact-match value for properties.type on the anchor document \" +\n \"(e.g. 'Person', 'Project', 'Meeting'). No fuzzy / synonym matching.\",\n },\n key: {\n type: \"string\",\n description:\n \"Candidate key. Matches the document's title OR any entry in \" +\n \"properties.aliases (a string[] from frontmatter). Exact-string match.\",\n },\n vaults: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Restrict to these vault names; defaults to all configured.\",\n },\n },\n },\n },\n // ── Phase 6 task-contract DSL (Plan 06-02 / D-A1 escape valve) ───────────\n {\n name: \"register_contracts_as_tools\",\n description:\n \"Explicit-control escape valve (D-A1) — scans the per-vault contract \" +\n \"registry and updates the dynamic MCP tool list (registers new contracts \" +\n \"as vm_<name> tools, unregisters removed ones) regardless of the \" +\n \"[contracts.auto_register_tools] config gate. Always callable. \" +\n \"Returns a per-vault diff of {registered, unregistered}. Omit `vault` \" +\n \"to apply to every configured vault.\",\n inputSchema: {\n type: \"object\",\n properties: {\n vault: {\n type: \"string\",\n description: \"Vault name; omit to apply to all vaults.\",\n },\n },\n },\n },\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-05, Q-DESCRIBE) ──────────\n {\n name: \"describe_contract\",\n description:\n \"Return the input JSON Schema + an auto-generated markdown summary for a \" +\n \"contract (Q-DESCRIBE). Pure function — does not execute the contract. \" +\n \"Summary lists Inputs / Sources / Sinks / Assembly (numbered) / write_back / \" +\n \"Output Shape. Omit `vault` on single-vault setups; on multi-vault setups, \" +\n \"pass `vault` to disambiguate (returns `{ok:false, reason:'ambiguous_vault'}` \" +\n \"otherwise).\",\n inputSchema: {\n type: \"object\",\n required: [\"name\"],\n properties: {\n name: {\n type: \"string\",\n description: \"Registered contract name (see register_contracts_as_tools).\",\n },\n vault: {\n type: \"string\",\n description: \"Vault name; omit on single-vault setups.\",\n },\n },\n },\n },\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-06) ──────────────────────\n {\n name: \"instantiate_contract\",\n description:\n \"Execute a registered contract end-to-end. Zod-validates inputs against the \" +\n \"contract's inputZodSchema (additionalProperties:false rejects typos). \" +\n \"Resolves source/sink overrides per D-A4b default chain (explicit → config → \" +\n \"contract literal → error if required); sinks are MemorySink-only per D-A4c \" +\n \"(MEM-05 invariant un-bypassable). Runs each assembly step through verbDispatcher \" +\n \"with template resolution + named-binding accumulation. write_back routes through \" +\n \"DeliveryAdapter.write() (MEM-05 chokepoint). Returns the Q-OUTPUT bundle \" +\n \"{steps, write_back} on success OR a structured InstantiateError envelope \" +\n \"(12 sealed reasons per ADR-006 §Decision 7). Omit `vault` on single-vault \" +\n \"setups; multi-vault setups require it (returns `ambiguous_vault` otherwise).\",\n inputSchema: {\n type: \"object\",\n required: [\"name\"],\n properties: {\n name: {\n type: \"string\",\n description: \"Registered contract name.\",\n },\n inputs: {\n type: \"object\",\n additionalProperties: true,\n description: \"Contract inputs; validated against the contract's inputZodSchema.\",\n },\n source_overrides: {\n type: \"object\",\n additionalProperties: { type: \"string\" },\n description:\n \"Override declared source handles by handle name (e.g. {default_source: 'obsidian-fs://x'}).\",\n },\n sink_overrides: {\n type: \"object\",\n additionalProperties: { type: \"string\" },\n description:\n \"Override declared sink handles by handle name. Targets MUST resolve through MemorySinkRegistry (D-A4c).\",\n },\n vault: {\n type: \"string\",\n description: \"Vault name; omit on single-vault setups.\",\n },\n },\n },\n },\n] as const;\n\nexport type ToolName = (typeof TOOLS)[number][\"name\"];\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TOOL_SCHEMAS — Zod 4 raw shapes per tool (passed to McpServer.registerTool)\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport { z, type ZodRawShape } from \"zod\";\n\n/**\n * Canonical DocId pattern (mirrors `DOC_ID_PATTERN` in\n * `src/adapters/registry.ts`). Inlined here so the snapshot generator\n * (`evals/v1-baseline/dump-tools.mjs`) — which is a plain Node ESM\n * script that imports `.ts` via Node's native type-stripping — does not\n * need to traverse into `./adapters/`; Node cannot resolve the `.js`\n * extension of a sibling `.ts` file at runtime when only one of the\n * pair exists.\n *\n * Single-source-of-truth invariant: any change to the canonical regex\n * in `src/adapters/registry.ts` MUST be mirrored here (and vice\n * versa). The `tool-registry.test.ts > supersede schema` cases pin the\n * expected reject/accept behavior and will fail if the two patterns\n * drift.\n */\nconst DOC_ID_PATTERN = /^[a-z][a-z0-9-]*:\\/\\/[^/]+\\/.+$/;\n\n/** Reusable predicate shape for `query_frontmatter.where` values. */\nconst PredicateSchema: z.ZodType<unknown> = z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.object({ $in: z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])) }),\n z.object({ $exists: z.boolean() }),\n z.object({ $contains: z.union([z.string(), z.number(), z.boolean(), z.null()]) }),\n]);\n\n/**\n * Per-tool Zod 4 raw shapes. Keys mirror TOOLS[].name; the shape is the\n * argument-object schema passed to `z.object({...})` (and to\n * `McpServer.registerTool({ inputSchema: shape })` per SDK 1.29).\n *\n * Tools with no input arguments declare `{}` (an empty raw shape — valid\n * per the SDK's `isZodRawShapeCompat` check).\n */\nexport const TOOL_SCHEMAS = {\n list_vaults: {},\n\n read_note: {\n vault: z.string(),\n path: z.string(),\n },\n\n search_semantic: {\n query: z.string().min(1),\n vaults: z.array(z.string()).optional(),\n top_k: z.number().int().positive().max(100).optional().default(10),\n exclude_paths: z.array(z.string()).optional(),\n },\n\n search_text: {\n query: z.string().min(1),\n vaults: z.array(z.string()).optional(),\n top_k: z.number().int().positive().max(100).optional().default(10),\n exclude_paths: z.array(z.string()).optional(),\n },\n\n search_hybrid: {\n query: z.string().min(1),\n vaults: z.array(z.string()).optional(),\n top_k: z.number().int().positive().max(100).optional().default(10),\n rrf_k: z.number().int().positive().max(1000).optional().default(60),\n exclude_paths: z.array(z.string()).optional(),\n rerank: z.boolean().optional().default(false),\n // Phase 3 / 03-05 additive params — D-07, D-08, ASM-07, ASM-08.\n // All `.optional()` with defaults that vanish when unset, so v1\n // callers see no behavior change.\n recency_weight: z.number().optional().default(0),\n authority_weight: z.number().optional().default(0),\n half_life_days: z.number().positive().optional().default(30),\n include_superseded: z.boolean().optional().default(false),\n // ── Phase 4 / 04-04 / GRA-03 (D-15): additive auto-expansion ──\n // Nested under a single optional `expand` object per D-15. When\n // omitted, hybridSearch behavior is byte-identical to v1 (the\n // guard `if (opts.expand && opts.expandDeps && ...)` at the end of\n // `src/search/hybrid.ts` short-circuits entirely). The literal-\n // union for `hops` enforces the D-05 hop cap at the boundary.\n expand: z\n .object({\n hops: z.union([z.literal(1), z.literal(2)]),\n direction: z.enum([\"forward\", \"backward\", \"both\"]).optional(),\n edge_types: z\n .array(z.enum([\"wikilink\", \"mention\", \"frontmatter-ref\", \"hyperlink\"]))\n .optional(),\n })\n .optional(),\n },\n\n list_backlinks: {\n vault: z.string(),\n path: z.string(),\n },\n\n list_forward_links: {\n vault: z.string(),\n path: z.string(),\n include_broken: z.boolean().optional().default(true),\n },\n\n find_broken_links: {\n vault: z.string(),\n },\n\n query_frontmatter: {\n vault: z.string(),\n where: z.record(z.string(), PredicateSchema),\n limit: z.number().int().positive().max(1000).optional().default(100),\n },\n\n write_note: {\n vault: z.string(),\n path: z.string(),\n content: z.string(),\n frontmatter: z.record(z.string(), z.unknown()).nullable().optional(),\n expected_hash: z.string().optional(),\n client_id: z.string().optional(),\n },\n\n update_frontmatter: {\n vault: z.string(),\n path: z.string(),\n merge: z.record(z.string(), z.unknown()),\n expected_hash: z.string().optional(),\n client_id: z.string().optional(),\n },\n\n delete_note: {\n vault: z.string(),\n path: z.string(),\n expected_hash: z.string(),\n client_id: z.string().optional(),\n },\n\n audit_log: {\n vault: z.string(),\n note_path: z.string().optional(),\n op: z.enum([\"create\", \"update\", \"delete\"]).optional(),\n since: z.number().int().nonnegative().optional(),\n limit: z.number().int().positive().max(1000).optional().default(50),\n // Plan 02-06 (MEM-08): additive optional filter. The MCP tool's\n // `description` string is INTENTIONALLY unchanged — Phase 1 byte-identity\n // is preserved. New capability is documented in docs/tools/audit_log.md.\n is_memory_sink_write: z.boolean().optional(),\n },\n\n list_models: {\n vault: z.string(),\n },\n\n start_shadow_index: {\n vault: z.string(),\n model: z.string().min(1),\n batch_size: z.number().int().positive().max(256).optional(),\n },\n\n switch_active_model: {\n vault: z.string(),\n model_name: z.string().min(1),\n },\n\n vacuum_embeddings: {\n vault: z.string(),\n },\n\n index_runs: {\n vault: z.string(),\n limit: z.number().int().positive().max(200).optional().default(20),\n },\n\n search: {\n query: z.string().min(1),\n limit: z.number().int().positive().max(50).optional().default(10),\n },\n\n fetch: {\n id: z.string().min(1),\n },\n\n vault_stats: {\n vault: z.string().optional(),\n },\n\n recent_notes: {\n vault: z.string().optional(),\n limit: z.number().int().positive().max(200).optional().default(20),\n since: z.number().int().nonnegative().optional(),\n },\n\n suggest_frontmatter: {\n vault: z.string(),\n path: z.string().optional(),\n content: z.string().optional(),\n title: z.string().optional(),\n folder_hint: z.string().optional(),\n },\n\n // ── Phase 2 memory tools (Plan 02-04) ───────────────────────────────────\n record_observation: {\n vault: z.string().min(1).describe(\"Vault name (registered in [vaults] config block)\"),\n claim: z\n .string()\n .min(1)\n .describe(\"Short natural-language statement of the observation (becomes title + body)\"),\n evidence: z\n .array(z.string())\n .describe(\"DocIds or quoted source spans supporting the claim; empty array allowed\"),\n confidence: z\n .enum([\"direct\", \"inferred\", \"uncertain\"])\n .describe(\"How the agent arrived at this claim\"),\n type: z\n .string()\n .min(1)\n .describe(\n \"Observation type per the sink contract (e.g. 'observation', 'hypothesis', 'decision')\",\n ),\n sink: z\n .string()\n .min(1)\n .optional()\n .describe(\n \"Memory sink name OR full obsidian-fs://… handle. Defaults to the vault's default sink.\",\n ),\n properties: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n \"Escape-hatch: contract-allowed extra properties; merged AFTER sugar args (caller wins)\",\n ),\n },\n\n supersede: {\n doc_id: z.string().regex(DOC_ID_PATTERN).describe(\"DocId of the document being superseded\"),\n replacement_doc_id: z\n .string()\n .regex(DOC_ID_PATTERN)\n .describe(\"DocId of the replacement document\"),\n reason: z\n .string()\n .min(1)\n .describe(\"Why the old document is being retired; written to superseded_reason\"),\n },\n\n // ── Phase 5 brief tools (Plan 05-02 / BRF-03, BRF-04) ───────────────────\n compile_brief: {\n vault: z.string().min(1).describe(\"Vault name (registered in [vaults] config block)\"),\n target: z\n .string()\n .min(1)\n .describe(\"Stable cross-version handle for the brief (e.g. 'atlas-q3')\"),\n source_doc_ids: z\n .array(z.string().regex(DOC_ID_PATTERN))\n .min(1)\n .max(50)\n .describe(\"DocIds the brief is compiled from; deduped, capped at 50 (D-03)\"),\n purpose: z\n .string()\n .min(1)\n .max(500)\n .describe(\"Free-form purpose; bounded so list_briefs stays scannable\"),\n max_tokens: z\n .number()\n .int()\n .positive()\n .optional()\n .default(2000)\n .describe(\"Hint for the LLM ladder; default 2000\"),\n prepared_text: z\n .string()\n .min(1)\n .optional()\n .describe(\"D-10 tier 3 fallback when no LLM is reachable — verbatim body to stitch in\"),\n sink: z.string().min(1).optional().describe(\"Override the default `_memory/_briefs` sink\"),\n },\n\n get_brief: {\n vault: z.string().min(1).describe(\"Vault name (registered in [vaults] config block)\"),\n target: z.string().min(1).describe(\"Stable cross-version handle for the brief\"),\n max_age_days: z\n .number()\n .int()\n .nonnegative()\n .optional()\n .describe(\"Reject briefs older than this many days unless allow_stale=true\"),\n allow_stale: z\n .boolean()\n .optional()\n .default(false)\n .describe(\n \"When true, return briefs flagged stale or too_old with annotation rather than null\",\n ),\n },\n\n // ── Phase 3 assembly tools (Plan 03-02 / ASM-02) ────────────────────────\n get_outline: {\n doc_id: z\n .string()\n .regex(DOC_ID_PATTERN)\n .describe(\"Opaque DocId (obsidian-fs://<vault>/<path>) of the document\"),\n vaults: z\n .array(z.string().min(1))\n .optional()\n .describe(\"Optional vault filter; usually omitted (the DocId names a vault)\"),\n },\n\n // ── Phase 3 assembly tools (Plan 03-03) ─────────────────────────────────\n search_sections: {\n query: z.string().min(1),\n limit: z.number().int().positive().max(50).optional().default(10),\n vaults: z.array(z.string().min(1)).optional(),\n // Forward-compat with slice 03-05's authority/staleness rescore.\n // Accepted today; ignored by the controller until 03-05 wires the\n // forwarding inside hybridSearch. See 03-03-DEVIATIONS.md.\n recency_weight: z.number().min(0).optional().default(0),\n authority_weight: z.number().min(0).optional().default(0),\n include_superseded: z.boolean().optional().default(false),\n },\n\n // ── Phase 2 memory tools (Plan 02-05) ───────────────────────────────────\n recall: {\n query: z\n .string()\n .min(1)\n .describe(\"Natural-language query; routes through hybrid (semantic + BM25) search\"),\n min_confidence: z\n .enum([\"direct\", \"inferred\", \"uncertain\"])\n .optional()\n .describe(\n \"Exclude docs whose confidence ordinal is lower than this (direct=3, inferred=2, uncertain=1)\",\n ),\n types: z\n .array(z.string().min(1))\n .optional()\n .describe(\"Restrict to docs whose `type` property is in this set\"),\n max_age_days: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\"Exclude docs whose `observed_at` is older than this many days\"),\n sink: z\n .string()\n .min(1)\n .optional()\n .describe(\n \"Memory sink name OR full obsidian-fs://… handle. Defaults to all configured sinks.\",\n ),\n limit: z\n .number()\n .int()\n .positive()\n .max(200)\n .optional()\n .describe(\"Maximum results AFTER filter+sort; default 20\"),\n vaults: z\n .array(z.string().min(1))\n .optional()\n .describe(\"Restrict to these vault names; defaults to all configured\"),\n },\n\n // ── Phase 3 assembly tools (Plan 03-04 / ASM-01) ────────────────────────\n get_document_bundle: {\n doc_id: z\n .string()\n .regex(DOC_ID_PATTERN)\n .describe(\"Opaque DocId (obsidian-fs://<vault>/<path>) of the anchor document\"),\n // v2.0.0 accepts only depth:1. The literal pin guarantees Zod\n // rejects any other value at the boundary so the controller does\n // not need to clamp. Phase 4 may widen additively (z.union of\n // literals, or `z.number().int().min(1).max(2)`).\n depth: z\n .literal(1)\n .optional()\n .default(1)\n .describe(\"Link-walk depth. v2.0.0: only 1 (one-hop). Phase 4 may widen.\"),\n vaults: z\n .array(z.string().min(1))\n .optional()\n .describe(\"Optional vault filter; usually omitted (the DocId names a vault)\"),\n },\n\n // ── Phase 4 graph tools (Plan 04-03 / GRA-01) ───────────────────────────\n expand: {\n seed_doc_ids: z\n .array(z.string().regex(DOC_ID_PATTERN))\n .min(1)\n .describe(\"1+ opaque DocIds (e.g. obsidian-fs://<vault>/<path>) — seeds of the BFS.\"),\n // Hops hard-capped at 2 (D-05) via Zod literal union — `hops: 3`\n // is rejected at the boundary; the controller does not clamp.\n hops: z\n .union([z.literal(1), z.literal(2)])\n .describe(\"Hop cap (1 or 2). v2.0.0 hard-caps at 2.\"),\n direction: z\n .enum([\"forward\", \"backward\", \"both\"])\n .optional()\n .default(\"both\")\n .describe(\"Edge traversal direction; default 'both'.\"),\n edge_types: z\n .array(z.enum([\"wikilink\", \"mention\", \"frontmatter-ref\", \"hyperlink\"]))\n .optional()\n .describe(\"Optional filter on edge types; default = all four types.\"),\n filter_properties: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"Strict-equality predicate on document properties (e.g. {type: 'Project'}).\"),\n include_superseded: z\n .boolean()\n .optional()\n .default(false)\n .describe(\"When false (default), docs whose properties.status === 'superseded' are dropped.\"),\n },\n\n // ── Phase 4 graph tools (Plan 04-05 / GRA-02) ───────────────────────────\n //\n // The cluster tool's schema is unusual: it requires EXACTLY ONE of\n // `query` or `seed_doc_ids` (mutual exclusion per D-15a). Zod can't\n // model \"exactly one\" in a raw shape directly — we declare the union\n // of both shapes in `SCHEMA_BUILDERS` below; here we publish the raw\n // shape so the MCP SDK's `tools/list` JSON Schema projection still\n // works. The runtime path goes through `buildToolSchema(\"cluster\")`\n // which calls the SCHEMA_BUILDERS entry.\n cluster: {\n query: z.string().min(1).optional(),\n seed_doc_ids: z.array(z.string().regex(DOC_ID_PATTERN)).min(1).optional(),\n // CR-02: `vault` scopes the `query` path on multi-vault setups so\n // search_hybrid is not silently restricted to whichever vault\n // sorts first in VaultManager insertion order. Optional at the\n // schema layer; the runtime cluster() entry enforces the\n // multi-vault-without-vault error.\n vault: z.string().min(1).optional(),\n method: z.literal(\"edge-community\"),\n query_top_k: z.number().int().positive().max(200).optional().default(50),\n force: z.boolean().optional().default(false),\n },\n\n // ── Phase 3 assembly tools (Plan 03-06) ─────────────────────────────────\n assemble_dossier: {\n type: z\n .string()\n .min(1)\n .describe(\n \"Exact-match value for properties.type on the anchor document (D-03 — no fuzzy match)\",\n ),\n key: z\n .string()\n .min(1)\n .describe(\n \"Candidate key — matches the document's title OR any entry in properties.aliases (D-04)\",\n ),\n vaults: z\n .array(z.string().min(1))\n .optional()\n .describe(\"Restrict to these vault names; defaults to all configured\"),\n },\n\n // ── Phase 6 task-contract DSL (Plan 06-02 / D-A1 escape valve) ─────────\n register_contracts_as_tools: {\n vault: z.string().min(1).optional().describe(\"Vault name; omit to apply to all vaults\"),\n },\n\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-05, Q-DESCRIBE) ────────\n describe_contract: {\n name: z.string().min(1).describe(\"Registered contract name (see register_contracts_as_tools)\"),\n vault: z.string().min(1).optional().describe(\"Vault name; omit on single-vault setups\"),\n },\n\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-06) ────────────────────\n instantiate_contract: {\n name: z.string().min(1).describe(\"Registered contract name\"),\n inputs: z\n .record(z.string(), z.unknown())\n .optional()\n .default({})\n .describe(\"Contract inputs; validated against the contract's inputZodSchema\"),\n source_overrides: z\n .record(z.string(), z.string())\n .optional()\n .describe(\"Override declared source handles by handle name\"),\n sink_overrides: z\n .record(z.string(), z.string())\n .optional()\n .describe(\n \"Override declared sink handles by handle name. Targets MUST resolve through MemorySinkRegistry (D-A4c).\",\n ),\n vault: z.string().min(1).optional().describe(\"Vault name; omit on single-vault setups\"),\n },\n} as const satisfies Record<string, ZodRawShape>;\n\n/**\n * Build a `z.object({...})` from a tool's raw shape. The\n * `suggest_frontmatter` tool layers an additional cross-field refinement\n * (path OR content required) — handled by the schema-builder map below.\n */\nconst SCHEMA_BUILDERS: Partial<Record<ToolName, () => z.ZodTypeAny>> = {\n suggest_frontmatter: () =>\n z\n .object(TOOL_SCHEMAS.suggest_frontmatter)\n .refine((v) => v.path !== undefined || v.content !== undefined, {\n message: \"suggest_frontmatter requires either `path` or `content`\",\n }),\n // Plan 04-05 / D-15a — EXACTLY ONE of `query` or `seed_doc_ids` must\n // be present. The runtime path also returns a structured\n // {ok:false, reason:'both_seeds_and_query'} error when both are set,\n // so this Zod refinement is the early-rejection gate at the MCP\n // boundary (cluster's internal validator handles the same case for\n // direct callers that bypass Zod).\n cluster: () =>\n z\n .object(TOOL_SCHEMAS.cluster)\n .refine(\n (v) =>\n (v.query !== undefined && v.seed_doc_ids === undefined) ||\n (v.query === undefined && v.seed_doc_ids !== undefined),\n {\n message:\n \"cluster requires EXACTLY ONE of `query` or `seed_doc_ids` (D-15a mutual exclusion)\",\n },\n ),\n};\n\n/**\n * Materialize the full Zod schema for a tool — wraps the raw shape in\n * `z.object({...})` and layers any tool-specific refinements. Called at\n * handler time inside `server.registerTool` for input validation.\n */\nexport function buildToolSchema(name: ToolName): z.ZodTypeAny {\n const builder = SCHEMA_BUILDERS[name];\n if (builder) return builder();\n return z.object(TOOL_SCHEMAS[name] as ZodRawShape);\n}\n","/**\n * Foundation types for Phase 6 task contracts (ADR-006).\n *\n * Pure type module — zero runtime imports. Plan 06-02/03/04 build on\n * these types; the loader/instantiator/describer land in later slices.\n *\n * Naming convention (CLAUDE.md): PascalCase types, snake_case YAML keys\n * (stored verbatim), `step_alias` is the YAML form; `stepAlias` is the\n * camelCase form at row boundary in `ContractAuditRow`.\n */\n\nimport type { z } from \"zod\";\n\n/**\n * Closed assembly-verb set (ADR-006 §Decision 2 / D-A2a / C-1).\n *\n * 11 baseline verbs + `\"literal\"` escape + `mcp://<server>/<tool>` peer.\n *\n * No write verbs in the set — writes happen exclusively via the\n * structurally-separate `write_back:` block. Promoting a peer-MCP verb\n * into the baseline enum is a v2.x decision driven by `aggregateVerbUsage`\n * data (D-A2b).\n */\nexport type AssemblyVerb =\n | \"search_hybrid\"\n | \"expand\"\n | \"cluster\"\n | \"recall\"\n | \"compile_brief\"\n | \"get_brief\"\n | \"query_frontmatter\"\n | \"list_backlinks\"\n | \"get_outline\"\n | \"search_sections\"\n | \"read_note\"\n | \"literal\"\n | `mcp://${string}/${string}`;\n\n/**\n * Path-matcher constant for the loader scan + ChangeFeed dispatch\n * (Pitfall F3 — non-recursive; `_contracts/memory/*.yaml` belongs to\n * the Phase 2 MemoryContract loader).\n */\nexport const CONTRACT_PATH_REGEX = /^_contracts\\/[^/]+\\.yaml$/;\n\n/**\n * A single step in an `assembly:` array. `as:` is the alias under which\n * the step's output is bound in the template environment.\n *\n * `value:` is populated ONLY when `verb === \"literal\"` (escape hatch\n * for hard-coded fixtures).\n */\nexport interface ContractStep {\n as: string;\n verb: AssemblyVerb;\n args?: Record<string, unknown>;\n value?: unknown;\n}\n\n/**\n * Contract-declared source / sink handle entry.\n *\n * `handle` is the literal URI shown in the YAML (e.g.\n * `\"obsidian-fs://my-vault\"`). `required` defaults to true; when false,\n * a missing override + missing default is not an error.\n */\nexport interface ContractHandleDecl {\n handle: string;\n required: boolean;\n}\n\n/** Backwards-compat alias — sources and sinks share the same shape today. */\nexport type ContractSourceDecl = ContractHandleDecl;\nexport type ContractSinkDecl = ContractHandleDecl;\n\n/**\n * Write-back spec — the chokepoint that produces a real DocId via\n * DeliveryAdapter.write (Invariant C-3, Pitfall F6).\n */\nexport interface WriteBackSpec {\n /** Sink handle (template expression or literal). */\n sink: string;\n document_kind: \"brief\" | \"observation\" | \"custom\";\n properties: Record<string, unknown>;\n /** Template expression that resolves to the body string. */\n body_from: string;\n}\n\n/** YAML inputs flat form: `{ <fieldName>: <jsonSchemaFragment> }`. */\nexport type ContractInputs = Record<string, unknown>;\n\n/**\n * Parsed-and-validated contract — registry entry shape.\n *\n * Caches the built input schema so `describe_contract` and\n * `instantiate_contract` skip the buildInputSchema round-trip.\n */\nexport interface ParsedContract {\n version: 1;\n name: string;\n description: string;\n inputs: ContractInputs;\n required: string[];\n sources: Record<string, ContractSourceDecl>;\n sinks: Record<string, ContractSinkDecl>;\n assembly: ContractStep[];\n output_shape?: object;\n write_back?: WriteBackSpec;\n /** Built once at load time — `z.fromJSONSchema(inputJsonSchema)`. */\n inputZodSchema: z.ZodObject<z.ZodRawShape>;\n /** Built once at load time — passed verbatim to MCP `tools/list`. */\n inputJsonSchema: object;\n}\n\n/** Caller-supplied override map keyed by handle name (not URI scheme). */\nexport type OverrideMap = Record<string, string>;\n\n/**\n * Closed error envelope (ADR-006 §Decision 7 + Q-OUTPUT + WARNING-6).\n *\n * 12 reasons, sealed for v2.0.0. The first 11 are orchestrator-level;\n * `ambiguous_vault` is server-dispatch-level (caller omitted `vault` and\n * multiple vaults are configured — surfaced in the same closed union to\n * keep callers parsing one discriminated type).\n */\nexport type InstantiateError =\n | { ok: false; reason: \"unknown_contract\"; name: string }\n | { ok: false; reason: \"invalid_inputs\"; issues: unknown }\n | {\n ok: false;\n reason: \"unknown_override_handle\";\n handle: string;\n valid_handles: string[];\n }\n | { ok: false; reason: \"missing_required_source\"; handle: string; hint: string }\n | {\n ok: false;\n reason: \"sink_override_not_a_memory_sink\";\n target: string;\n hint: string;\n }\n | { ok: false; reason: \"unresolved_template\"; expression: string }\n | { ok: false; reason: \"verb_not_available\"; verb: string }\n | {\n ok: false;\n reason: \"mcp_client_unavailable\";\n verb: string;\n client_name: string;\n }\n | { ok: false; reason: \"assembly_step_failed\"; step_alias: string; cause: string }\n | { ok: false; reason: \"write_back_failed\"; cause: string }\n | { ok: false; reason: \"validation_failed_on_output_shape\"; issues: unknown }\n | { ok: false; reason: \"ambiguous_vault\"; available_vaults: string[] };\n\n/**\n * Re-export of the DB row type for convenience — Plan 06-02/03 import\n * this name (NOT from `src/db/queries/contract-audit.js`) so the\n * dependency graph stays cleanly directed (contracts → db).\n */\nexport interface ContractAuditRow {\n kind: \"contract_step\" | \"contract_load_error\";\n contract?: string;\n verb?: string;\n stepAlias?: string;\n vault?: string;\n ts: number;\n errorMessage?: string;\n}\n","/**\n * TYPES_CATALOG — Phase 6 / D-A3b, ADR-006 §Decision 6.\n *\n * Resolves `$ref: \"#/types/<name>\"` in contract YAML input schemas.\n *\n * Additive evolution only:\n * - Phase 10 may extend `DocId.pattern` to also match `notion://...`\n * (additive). We MUST NEVER narrow.\n * - Adding a NEW type entry (e.g. `Workspace`) is allowed at minor\n * version bumps.\n *\n * `Object.freeze` enforces the additive-only contract structurally —\n * direct mutation throws in strict ESM.\n *\n * Adapter-seam discipline: zero `fs`/`path.join`/`gray-matter`/`chokidar`\n * imports. Pure data module.\n */\n\nexport const TYPES_CATALOG: Readonly<Record<string, object>> = Object.freeze({\n DocId: Object.freeze({\n type: \"string\",\n pattern: \"^[a-z][a-z0-9-]*://\",\n description: \"Opaque document identifier per ADR-001 (URI-style)\",\n }),\n Handle: Object.freeze({\n type: \"string\",\n pattern: \"^[a-z][a-z0-9-]*://\",\n description:\n \"Source or sink handle (currently identical to DocId; future-proofed for divergence)\",\n }),\n ChunkId: Object.freeze({\n type: \"string\",\n pattern: \"^[a-z][a-z0-9-]*://.+#chunk-[0-9a-f]{7}$\",\n description: \"Content-stable chunk identifier per Phase 5 ADR-005 H-5\",\n }),\n MemorySink: Object.freeze({\n type: \"string\",\n description: \"Registered MemorySink handle (see list_sinks)\",\n \"x-validator\": \"memory-sink\",\n }),\n});\n","/**\n * resolveRefs — Phase 6 / D-A3a, ADR-006 §Decision 6, T-06-01-01 gate.\n *\n * Resolves `$ref: \"#/types/<name>\"` nodes against TYPES_CATALOG. Any\n * other `$ref` form (HTTP URL, file://, JSON-Pointer beyond `#/types/`)\n * throws synchronously — Security: no HTTP fetches, no FS reads from\n * contract YAML.\n *\n * Spread order (RESEARCH Example 3): catalog entry first, YAML-author\n * additions on the same node second — author additions WIN. This lets\n * a contract override the catalog description without weakening the\n * pattern/type constraints (those are spread first; redundant author\n * `type`/`pattern` simply re-state them).\n *\n * Adapter-seam discipline: zero `fs`/`path.join`/`gray-matter`/`chokidar`\n * imports. Pure function.\n */\n\nimport { TYPES_CATALOG } from \"./types-catalog.js\";\n\nconst TYPES_REF_RE = /^#\\/types\\/(\\w+)$/;\n\nexport function resolveRefs(schema: unknown): unknown {\n if (Array.isArray(schema)) return schema.map(resolveRefs);\n if (schema !== null && typeof schema === \"object\") {\n const obj = schema as Record<string, unknown>;\n if (typeof obj[\"$ref\"] === \"string\") {\n const ref = obj[\"$ref\"];\n const match = ref.match(TYPES_REF_RE);\n if (!match) {\n throw new Error(`Unsupported $ref form (only '#/types/<name>' accepted): ${ref}`);\n }\n const typeName = match[1]!;\n const catalogEntry = (TYPES_CATALOG as Record<string, unknown>)[typeName];\n if (catalogEntry === undefined) {\n throw new Error(`Unknown $ref target: ${ref}`);\n }\n // Strip $ref before merging — author additions win (Example 3).\n // Use destructuring to keep TS strict-mode happy.\n const rest: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (k === \"$ref\") continue;\n rest[k] = resolveRefs(v);\n }\n return { ...(catalogEntry as Record<string, unknown>), ...rest };\n }\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n out[k] = resolveRefs(v);\n }\n return out;\n }\n return schema;\n}\n","/**\n * buildInputSchema — Phase 6 / D-A3a, ADR-006 §Decision 6.\n *\n * Wraps the YAML author's flat `inputs:` form into the canonical\n * `{type:'object', properties, required, additionalProperties: false}`\n * envelope, resolves `$ref` against TYPES_CATALOG, then produces a\n * `ZodObject` via `z.fromJSONSchema`.\n *\n * Pitfall F1: SDK 1.29 `registerTool({inputSchema})` REJECTS raw JSON\n * Schema literals — the inputSchema must be a real Zod schema.\n * `z.fromJSONSchema` is the chokepoint that converts the JSON shape\n * into a Zod schema the SDK accepts.\n *\n * Pitfall F2: `z.fromJSONSchema` honors `additionalProperties` from the\n * input. WITHOUT explicit `additionalProperties: false`, typo'd input\n * keys are silently dropped at runtime. The wrapper sets this\n * explicitly — verified by Test 11.\n *\n * Assumption A3 (verified by Test 12): the `\"x-validator\": \"memory-sink\"`\n * extension keyword passes through `z.fromJSONSchema` unchanged. The\n * memory-sink validation happens at instantiation time (Plan 06-03)\n * by inspecting the `jsonSchema.properties.*[\"x-validator\"]` field —\n * NOT the Zod schema.\n *\n * Adapter-seam discipline: only `zod` is imported. Zero `fs`/`path.join`/\n * `gray-matter`/`chokidar`/`yaml`.\n */\n\nimport { z } from \"zod\";\nimport { resolveRefs } from \"./json-schema-ref.js\";\n\nexport interface BuiltInputSchema {\n zodSchema: z.ZodObject<z.ZodRawShape>;\n jsonSchema: {\n type: \"object\";\n properties: Record<string, unknown>;\n required: string[];\n additionalProperties: false;\n };\n}\n\nexport function buildInputSchema(\n yamlInputs: Record<string, unknown>,\n required: string[] = [],\n): BuiltInputSchema {\n const resolvedProperties = resolveRefs(yamlInputs) as Record<string, unknown>;\n const jsonSchema = {\n type: \"object\" as const,\n properties: resolvedProperties,\n required,\n additionalProperties: false as const,\n };\n // Pitfall F1: fromJSONSchema produces a ZodObject the SDK accepts.\n // Cast is safe — we always pass an `object`-typed JSON Schema in.\n // Zod's `fromJSONSchema` accepts `JSONSchema` whose property bag is\n // typed as `Record<string, _JSONSchema>` — our `unknown` map is\n // structurally compatible at runtime (the contract YAML is JSON\n // Schema by construction), but TypeScript needs an explicit cast.\n const zodSchema = z.fromJSONSchema(\n jsonSchema as unknown as Parameters<typeof z.fromJSONSchema>[0],\n ) as z.ZodObject<z.ZodRawShape>;\n return { zodSchema, jsonSchema };\n}\n","/**\n * ContractRegistry — Phase 6 / D-A1c, ADR-006 §Decision 1, Invariant C-4.\n *\n * In-memory `Map<name, ParsedContract>` wrapper with first-wins\n * collision policy. A second `set(name, ...)` with the same name does\n * NOT replace the original; it returns a structured failure result so\n * the caller can record `contract_audit kind: 'contract_load_error'`.\n *\n * Caller writes the audit row (`src/contracts/audit.ts` —\n * `recordContractLoadError`); the registry stays free of DB imports.\n *\n * Adapter-seam discipline: zero `fs`/`path.join`/`gray-matter`/`chokidar`\n * imports. Pure in-memory data structure.\n */\n\nimport type { ParsedContract } from \"./types.js\";\n\nexport type RegistrySetResult = { ok: true } | { ok: false; reason: \"duplicate_name\" };\n\nexport class ContractRegistry {\n private readonly contracts = new Map<string, ParsedContract>();\n\n get size(): number {\n return this.contracts.size;\n }\n\n get(name: string): ParsedContract | undefined {\n return this.contracts.get(name);\n }\n\n /** D-A1c first-wins. Returns `{ok:false, reason:\"duplicate_name\"}` if `name` is already registered. */\n set(name: string, contract: ParsedContract): RegistrySetResult {\n if (this.contracts.has(name)) {\n return { ok: false, reason: \"duplicate_name\" };\n }\n this.contracts.set(name, contract);\n return { ok: true };\n }\n\n delete(name: string): boolean {\n return this.contracts.delete(name);\n }\n\n entries(): IterableIterator<[string, ParsedContract]> {\n return this.contracts.entries();\n }\n\n names(): string[] {\n return Array.from(this.contracts.keys());\n }\n}\n","/**\n * slugify — Phase 6 / D-A1c, ADR-006 §Decision 1.\n *\n * Converts a kebab-case contract name into a snake_case MCP tool name\n * with the configured `tool_prefix` prepended. Zero deps (RESEARCH\n * Anti-Patterns — no `lodash`, no `change-case`).\n *\n * Examples:\n * slugify(\"meeting-prep\", \"vm_\") → \"vm_meeting_prep\"\n * slugify(\"project-status\", \"\") → \"project_status\" (caller's\n * responsibility to enforce A7 .min(1))\n *\n * First-wins on collision is the registry's job (`ContractRegistry.set`).\n *\n * Adapter-seam discipline: pure function, no imports, no I/O.\n */\nexport function slugify(name: string, prefix: string): string {\n return prefix + name.replace(/-/g, \"_\");\n}\n","/**\n * Contract-audit writers — Phase 6 / Q-AUD, ADR-006 §Decision 4,\n * Invariant C-5.\n *\n * Security pattern (mitigates T-06-01-03 — Information Disclosure):\n * Function signatures explicitly EXCLUDE any `output` / `payload`\n * field. TypeScript strict-mode rejects a call site that attempts to\n * add one. Peer-MCP step outputs may contain sensitive data (private\n * PR text, customer records, secret tokens); we never capture them.\n *\n * `recordContractStep` / `recordContractLoadError` use ONLY the\n * `vault.db.contractAudit` namespace — they do NOT touch any other DB\n * table. `aggregateVerbUsage` re-exports the underlying query for\n * Plan 06-04's resource handler.\n *\n * Adapter-seam discipline: only `./types.js` + the typed query interface\n * are imported. Zero `fs`/`path.join`/`gray-matter`/`chokidar`.\n */\n\nimport type { ContractAuditQueries } from \"../db/queries/contract-audit.js\";\n\nexport interface ContractAuditDeps {\n contractAudit: ContractAuditQueries;\n}\n\nexport interface RecordContractStepArgs {\n contract: string;\n verb: string;\n step_alias: string;\n vault: string;\n}\n\nexport interface RecordContractLoadErrorArgs {\n file: string;\n error_message: string;\n vault: string;\n}\n\nexport interface VerbUsageRow {\n verb: string;\n invocation_count: number;\n last_seen: number;\n}\n\n/**\n * Write one `contract_audit kind: 'contract_step'` row. NEVER accepts an\n * output / payload field (C-5).\n */\nexport function recordContractStep(deps: ContractAuditDeps, args: RecordContractStepArgs): void {\n deps.contractAudit.insert({\n kind: \"contract_step\",\n contract: args.contract,\n verb: args.verb,\n stepAlias: args.step_alias,\n vault: args.vault,\n ts: Date.now(),\n });\n}\n\n/**\n * Write one `contract_audit kind: 'contract_load_error'` row. The `file`\n * is prefixed onto `error_message` for human-readable surfacing through\n * `list_contract_load_errors` (Plan 06-04 Resource).\n */\nexport function recordContractLoadError(\n deps: ContractAuditDeps,\n args: RecordContractLoadErrorArgs,\n): void {\n deps.contractAudit.insert({\n kind: \"contract_load_error\",\n vault: args.vault,\n ts: Date.now(),\n errorMessage: `${args.file}: ${args.error_message}`,\n });\n}\n\n/**\n * D-A2b promotion signal — verb usage histogram across `contract_step`\n * rows in this vault. Plan 06-04's resource handler pipes this through\n * `vault-memory://contract-verbs/{vault}`.\n */\nexport function aggregateVerbUsage(deps: ContractAuditDeps, vault: string): VerbUsageRow[] {\n return deps.contractAudit.aggregateVerbUsage(vault);\n}\n","/**\n * ContractFileSchema — Phase 6 / CON-01, ADR-006 §Decision 2.\n *\n * Zod schema for the YAML contract file shape. Plan 06-02's loader will\n * call `parseDocument(yamlText).toJS()` and feed the result here.\n *\n * Invariants enforced structurally:\n * - C-1: closed `assembly[].verb` set (11 baseline + literal + mcp://).\n * No write verbs in the enum — writes happen exclusively via\n * the structurally-separate `write_back:` block.\n * - Step aliases are unique across the assembly array (superRefine).\n * - `version: 1` is the only supported version in v2.0.0 (additive\n * evolution lands as `z.union([z.literal(1), z.literal(2)])` later).\n *\n * Authoring style mirrors `src/memory/contract/default-v1.ts`:\n * `.describe()` on every public field, `.superRefine` for cross-field\n * invariants.\n *\n * Adapter-seam discipline: only `zod`. Zero `fs`/`path.join`/`gray-matter`/\n * `chokidar`/`yaml`.\n */\n\nimport { z } from \"zod\";\n\nconst BASELINE_VERBS = [\n \"search_hybrid\",\n \"expand\",\n \"cluster\",\n \"recall\",\n \"compile_brief\",\n \"get_brief\",\n \"query_frontmatter\",\n \"list_backlinks\",\n \"get_outline\",\n \"search_sections\",\n \"read_note\",\n] as const;\n\nconst MCP_VERB_RE = /^mcp:\\/\\/[a-z][a-z0-9_-]*\\/[a-z][a-z0-9_-]*$/;\n\n/**\n * Verb schema = closed enum (baseline + literal) OR mcp:// peer pattern.\n * Anything else — including any v1 write tool name — fails validation.\n */\nconst VerbSchema = z.union([z.enum([...BASELINE_VERBS, \"literal\"]), z.string().regex(MCP_VERB_RE)]);\n\nconst StepSchema = z\n .object({\n as: z\n .string()\n .min(1)\n .regex(/^[a-z_][a-z0-9_]*$/, \"alias must be snake_case\")\n .describe(\"D-A2c — unique snake_case alias for this step's output\"),\n verb: VerbSchema.describe(\"Closed enum + literal + mcp:// extension (D-A2a / C-1)\"),\n args: z.record(z.string(), z.unknown()).optional(),\n value: z.unknown().optional(),\n })\n .describe(\"One step in an assembly: array\");\n\nconst HandleDeclSchema = z\n .object({\n handle: z.string().min(1),\n required: z.boolean().default(true),\n })\n .describe(\"Source or sink handle declaration (D-A4a)\");\n\nconst WriteBackSchema = z\n .object({\n sink: z.string().min(1).describe(\"Template expression OR literal sink handle\"),\n document_kind: z.enum([\"brief\", \"observation\", \"custom\"]),\n properties: z.record(z.string(), z.unknown()).default({}),\n body_from: z.string().min(1).describe(\"Template expression that resolves to the body string\"),\n })\n .describe(\"DeliveryAdapter.write chokepoint — only ground-truth DocId source (C-3)\");\n\nexport const ContractFileSchema = z\n .object({\n version: z.literal(1).describe(\"v2.0.0 supports version 1 only; v2.x may extend additively\"),\n name: z\n .string()\n .min(1)\n .regex(/^[a-z][a-z0-9-]*$/, \"name must be kebab-case\")\n .describe(\"Contract name — used by instantiate_contract and slugify\"),\n description: z.string().default(\"\"),\n inputs: z.record(z.string(), z.unknown()).default({}),\n required: z.array(z.string()).default([]),\n sources: z.record(z.string(), HandleDeclSchema).default({}),\n sinks: z.record(z.string(), HandleDeclSchema).default({}),\n assembly: z.array(StepSchema).min(1, \"assembly must contain at least one step\"),\n output_shape: z.unknown().optional(),\n write_back: WriteBackSchema.optional(),\n })\n .superRefine((data, ctx) => {\n // D-A2c: every step alias is unique across the assembly array.\n const aliases = new Set<string>();\n for (const step of data.assembly) {\n if (aliases.has(step.as)) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"assembly\"],\n message: `duplicate step alias '${step.as}'`,\n });\n }\n aliases.add(step.as);\n }\n });\n\nexport type ContractFileShape = z.infer<typeof ContractFileSchema>;\n","/**\n * startContractRegistry — Phase 6 / D-LOAD, ADR-006 §Decision 7.\n *\n * Boot scan + ChangeFeed subscriber for `_contracts/<single>.yaml` files\n * (Pitfall F3 — non-recursive; `_contracts/memory/*.yaml` belongs to the\n * Phase 2 MemoryContract loader). On each event:\n * - parse via `yaml@2.9 parseDocument(text).toJS()` (preserves comments\n * on a later round-trip per CON-01);\n * - Zod-validate via `ContractFileSchema`;\n * - resolve `$ref` via `resolveRefs`;\n * - build the cached input schema via `buildInputSchema`;\n * - register via `ContractRegistry.set(name, parsed)` (first-wins per\n * D-A1c — duplicate-name writes a `contract_load_error` audit row).\n *\n * Parse failures during a hot-reload event do NOT mutate the registry\n * (graceful degradation per D-LOAD): the prior version stays in place\n * and a `contract_load_error` audit row records the diagnostic.\n *\n * # Adapter-seam discipline\n *\n * Zero `fs` / `path.join` / `gray-matter` / `chokidar` imports. The loader\n * reads vault content exclusively through `SourceConnector.readDocument`\n * and `SourceConnector.listDocuments`; ChangeEvents arrive through the\n * `ChangeFeed.subscribe` seam. `yaml`'s `parseDocument` operates on text\n * already read by the source, not the filesystem.\n *\n * # Production end-to-end coverage (forward note)\n *\n * The existing Phase-1 `ObsidianFsSource` + `ObsidianFsChangeFeed` only\n * enumerate / watch `.md` files (see `scanner.ts:47` and `change-feed.ts:191`).\n * Until those adapters are widened to also surface `_contracts/*.yaml`,\n * the loader's boot scan + hot-reload paths only fire under tests (which\n * supply YAML-aware stubs). Server bootstrap wires the loader through\n * the existing seams so the registry, the audit table, and the\n * `register_contracts_as_tools` tool surface land in v2.0.0; widening\n * obsidian-fs to enumerate contract YAML is a follow-up tracked under\n * Phase 6 wave-4 (Plan 06-04). This file is the seam, not the surface.\n */\n\nimport { parseDocument } from \"yaml\";\nimport {\n CONTRACT_PATH_REGEX,\n type ParsedContract,\n type ContractInputs,\n type ContractStep,\n type ContractSourceDecl,\n type ContractSinkDecl,\n type WriteBackSpec,\n} from \"./types.js\";\nimport { ContractFileSchema, type ContractFileShape } from \"./schema.js\";\nimport { buildInputSchema } from \"./input-schema.js\";\nimport { resolveRefs } from \"./json-schema-ref.js\";\nimport { ContractRegistry } from \"./registry.js\";\nimport { recordContractLoadError, type ContractAuditDeps } from \"./audit.js\";\nimport { decomposeDocId } from \"../adapters/registry.js\";\nimport { sha256 } from \"../adapters/source/obsidian-fs/hash.js\";\nimport type { SuppressionSet } from \"../adapters/change-feed/obsidian-fs/suppression.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport type { ChangeFeed, ChangeEvent, Disposable } from \"../adapters/change-feed/types.js\";\nimport type { DocId, Document } from \"../types.js\";\n\n/**\n * Discriminator for the `onRegistryChange` callback (test hook). Boot\n * scan fires `\"boot\"` once after the scan completes; ChangeFeed events\n * fire `\"create\"` | `\"update\"` | `\"delete\"` on successful registry\n * mutation. NOT fired on parse failures (graceful degradation).\n */\nexport type RegistryChangeKind = \"boot\" | \"create\" | \"update\" | \"delete\";\n\nexport interface StartContractRegistryOpts {\n vault: Vault;\n feed: ChangeFeed;\n source: SourceConnector;\n auditDeps: ContractAuditDeps;\n onRegistryChange?: (kind: RegistryChangeKind) => void;\n /**\n * Phase 7 / Plan 07-07 / CAN-08. Shared SuppressionSet from the server\n * bootstrap. When provided, `handleChangeEvent` calls\n * `suppression.consume(file, hash)` BEFORE re-validating; suppressed\n * events with a matching hash short-circuit (no reload, no audit row,\n * no `onExternalReload` fire). When omitted, behavior matches Phase 6\n * (every event re-validates).\n *\n * @see ../adapters/change-feed/obsidian-fs/suppression.ts — the\n * hash-keyed `consume(path, hash)` semantics.\n */\n suppression?: SuppressionSet;\n /**\n * Phase 7 / Plan 07-07 / CAN-08. Fires AFTER a non-suppressed\n * create/update reload successfully re-registers the contract. The\n * server bootstrap uses this to emit the\n * `vault-memory://contracts/reloaded` MCP Resource notification so\n * the plugin's `ReloadNotifier` can surface an \"External edit\n * detected — reload editor?\" prompt without polling.\n *\n * Receives the contract file path (vault-relative `_contracts/<n>.yaml`).\n * NOT fired on parse failures, NOT fired on suppressed events, NOT\n * fired on delete (the plugin treats deletes as a separate concern).\n */\n onExternalReload?: (file: string) => void;\n}\n\nexport interface StartedContractRegistry {\n registry: ContractRegistry;\n dispose: () => void;\n}\n\n/**\n * Boot scan + ChangeFeed subscription. Returns a `Disposable` that\n * unsubscribes the feed handler. Idempotent boot scan: even when the\n * ChangeFeed emits an initial `create` for every existing file\n * (Pitfall F5), the registry's first-wins policy prevents duplicate\n * entries — the second attempt yields a `contract_load_error` audit row\n * (latest-error-visible is the desired behavior per D-LOAD).\n */\nexport async function startContractRegistry(\n opts: StartContractRegistryOpts,\n): Promise<StartedContractRegistry> {\n const registry = new ContractRegistry();\n\n // Closure-local map: contract file relative-path → registered contract\n // name. Used by `delete` / `rename` events to look up the registered\n // name (the file path is the only identity the ChangeFeed carries).\n const fileToName = new Map<string, string>();\n\n // ── Boot scan ─────────────────────────────────────────────────────────\n await bootScan(opts, registry, fileToName);\n opts.onRegistryChange?.(\"boot\");\n\n // ── ChangeFeed subscription ───────────────────────────────────────────\n const sub: Disposable = opts.feed.subscribe(async (event: ChangeEvent) => {\n await handleChangeEvent(event, opts, registry, fileToName);\n });\n\n return {\n registry,\n dispose: () => sub[Symbol.dispose](),\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Internal — boot scan\n// ─────────────────────────────────────────────────────────────────────────\n\nasync function bootScan(\n opts: StartContractRegistryOpts,\n registry: ContractRegistry,\n fileToName: Map<string, string>,\n): Promise<void> {\n for await (const ref of opts.source.listDocuments()) {\n const { resource } = decomposeDocId(ref.id);\n if (!CONTRACT_PATH_REGEX.test(resource)) continue;\n let text: string;\n try {\n const doc = await opts.source.readDocument(ref.id);\n text = extractText(doc);\n } catch (err) {\n recordContractLoadError(opts.auditDeps, {\n file: resource,\n error_message: messageOf(err),\n vault: opts.vault.config.name,\n });\n continue;\n }\n parseAndRegister(text, resource, opts, registry, fileToName);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Internal — ChangeFeed handler\n// ─────────────────────────────────────────────────────────────────────────\n\nasync function handleChangeEvent(\n event: ChangeEvent,\n opts: StartContractRegistryOpts,\n registry: ContractRegistry,\n fileToName: Map<string, string>,\n): Promise<void> {\n // Rename — adapter-native rename event. Handle as delete-old + create-new.\n // Renames are NOT suppression candidates (the plugin's YAML emit never\n // emits a rename event — only a create/update on the YAML path) so we\n // skip the suppression check here. `onExternalReload` does NOT fire on\n // rename; the plugin's open .contract view stays bound to its own\n // file path and the user's intent is unambiguous when they rename.\n if (event.kind === \"rename\") {\n const oldResource = decomposeDocId(event.old_id).resource;\n const newResource = decomposeDocId(event.new_id).resource;\n if (CONTRACT_PATH_REGEX.test(oldResource)) {\n deleteByFile(oldResource, registry, fileToName, opts);\n }\n if (CONTRACT_PATH_REGEX.test(newResource)) {\n await loadFromFeed(event.new_id, newResource, opts, registry, fileToName);\n opts.onRegistryChange?.(\"update\");\n } else if (CONTRACT_PATH_REGEX.test(oldResource)) {\n // Renamed OUT of `_contracts/` — pure delete.\n opts.onRegistryChange?.(\"delete\");\n }\n return;\n }\n\n const { resource } = decomposeDocId(event.id);\n if (!CONTRACT_PATH_REGEX.test(resource)) return;\n\n switch (event.kind) {\n case \"delete\": {\n if (deleteByFile(resource, registry, fileToName, opts)) {\n opts.onRegistryChange?.(\"delete\");\n }\n return;\n }\n case \"create\":\n case \"update\": {\n // Phase 7 / CAN-08 — hash-keyed echo suppression. Read the on-disk\n // body once, compute SHA-256, and ask the SuppressionSet whether\n // this event is the echo of a plugin-driven write. If yes, drop\n // silently (no audit row, no registry mutation, no callback fire).\n // If no, fall through to the existing re-validate path.\n //\n // We read the body here (rather than inside loadFromFeed) because\n // the suppression check needs the hash up-front. The body is\n // re-used downstream so the read isn't wasted.\n let text: string;\n try {\n const doc = await opts.source.readDocument(event.id);\n text = extractText(doc);\n } catch (err) {\n recordContractLoadError(opts.auditDeps, {\n file: resource,\n error_message: messageOf(err),\n vault: opts.vault.config.name,\n });\n return;\n }\n\n if (opts.suppression !== undefined) {\n const hash = sha256(text);\n if (opts.suppression.consume(resource, hash)) {\n // Echo of an own-write — drop silently per CAN-08 D-WATCH-PLUGIN-OUT.\n return;\n }\n }\n\n // For `update` semantics, drop the prior registration of this file\n // first so the new YAML can re-register (D-LOAD replace).\n if (event.kind === \"update\") {\n deleteByFile(resource, registry, fileToName, opts);\n }\n const ok = parseAndRegister(text, resource, opts, registry, fileToName);\n if (ok) {\n opts.onRegistryChange?.(event.kind);\n // CAN-08 D-WATCH-SERVER-NOTIFY — surface non-suppressed\n // external edits to subscribers (the plugin's ReloadNotifier).\n opts.onExternalReload?.(resource);\n }\n return;\n }\n }\n}\n\n/**\n * Delete the contract previously registered from `file` (if any).\n * Returns true iff something was removed.\n */\nfunction deleteByFile(\n file: string,\n registry: ContractRegistry,\n fileToName: Map<string, string>,\n _opts: StartContractRegistryOpts,\n): boolean {\n const name = fileToName.get(file);\n if (name === undefined) return false;\n registry.delete(name);\n fileToName.delete(file);\n return true;\n}\n\n/**\n * Read + parse + register the YAML at `id` (a DocId whose resource is\n * `file`). On any failure, write `contract_load_error` and return false;\n * the registry stays unmutated (D-LOAD graceful degradation).\n */\nasync function loadFromFeed(\n id: DocId,\n file: string,\n opts: StartContractRegistryOpts,\n registry: ContractRegistry,\n fileToName: Map<string, string>,\n): Promise<boolean> {\n let text: string;\n try {\n const doc = await opts.source.readDocument(id);\n text = extractText(doc);\n } catch (err) {\n recordContractLoadError(opts.auditDeps, {\n file,\n error_message: messageOf(err),\n vault: opts.vault.config.name,\n });\n return false;\n }\n return parseAndRegister(text, file, opts, registry, fileToName);\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Internal — parse + register\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Parse `text` as a YAML contract; validate; register. Writes\n * `contract_load_error` on any failure path (parse error, Zod failure,\n * duplicate name). Returns true iff `registry.set` succeeded.\n */\nfunction parseAndRegister(\n text: string,\n file: string,\n opts: StartContractRegistryOpts,\n registry: ContractRegistry,\n fileToName: Map<string, string>,\n): boolean {\n let parsed: ParsedContract;\n try {\n const docNode = parseDocument(text);\n const raw = docNode.toJS();\n const validated = ContractFileSchema.safeParse(raw);\n if (!validated.success) {\n throw new Error(`zod: ${JSON.stringify(validated.error.format())}`);\n }\n parsed = buildParsedContract(validated.data);\n } catch (err) {\n recordContractLoadError(opts.auditDeps, {\n file,\n error_message: messageOf(err),\n vault: opts.vault.config.name,\n });\n return false;\n }\n\n const result = registry.set(parsed.name, parsed);\n if (!result.ok) {\n recordContractLoadError(opts.auditDeps, {\n file,\n error_message: `duplicate_name: '${parsed.name}' already registered (first-wins per D-A1c)`,\n vault: opts.vault.config.name,\n });\n return false;\n }\n fileToName.set(file, parsed.name);\n return true;\n}\n\n/**\n * Compose a ParsedContract from validated YAML data. Builds the cached\n * Zod + JSON Schema (Pitfall F1/F2 chokepoint) and resolves $ref in\n * `output_shape` (D-A3a).\n */\nfunction buildParsedContract(data: ContractFileShape): ParsedContract {\n const inputs: ContractInputs = data.inputs as ContractInputs;\n const required = data.required;\n const built = buildInputSchema(inputs, required);\n const outputShape =\n data.output_shape !== undefined ? (resolveRefs(data.output_shape) as object) : undefined;\n\n // Narrow the optional shapes from the Zod-defaulted shape to the\n // ParsedContract surface. The Zod `HandleDeclSchema` fills `required`\n // with a boolean default; same shape on both sides.\n const sources = data.sources as Record<string, ContractSourceDecl>;\n const sinks = data.sinks as Record<string, ContractSinkDecl>;\n const assembly = data.assembly as ContractStep[];\n const writeBack = data.write_back as WriteBackSpec | undefined;\n\n const result: ParsedContract = {\n version: 1,\n name: data.name,\n description: data.description,\n inputs,\n required,\n sources,\n sinks,\n assembly,\n inputZodSchema: built.zodSchema,\n inputJsonSchema: built.jsonSchema,\n };\n if (outputShape !== undefined) result.output_shape = outputShape;\n if (writeBack !== undefined) result.write_back = writeBack;\n return result;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Internal — text extraction\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Extract the raw YAML text from a `Document`. The obsidian-fs source\n * publishes content as a single `paragraph` block (`blocks[0].text`);\n * future block-shaped adapters can populate the same field. Throws if\n * the Document has no block content — caller writes a load error.\n */\nfunction extractText(doc: Document): string {\n const block = doc.blocks[0];\n if (block === undefined) {\n throw new Error(\"Document has no blocks (cannot read contract YAML)\");\n }\n if (block.kind === \"paragraph\") return block.text;\n // For future block-shaped adapters, fall back to concatenating\n // paragraph blocks. Today no other adapter produces non-paragraph\n // contract documents.\n const paragraphs = doc.blocks.filter(\n (b): b is { kind: \"paragraph\"; text: string } => b.kind === \"paragraph\",\n );\n if (paragraphs.length === 0) {\n throw new Error(\"Document blocks contain no paragraph text\");\n }\n return paragraphs.map((b) => b.text).join(\"\\n\");\n}\n\nfunction messageOf(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n","/**\n * syncAutoRegistered — Phase 6 / D-A1, ADR-006 §Decision 1 (Pattern 4).\n *\n * Diff-based dynamic MCP Tool registration. Maintains a per-loader\n * `registered: Map<toolName, RegisteredTool>` that survives across calls;\n * each invocation:\n * 1. computes the desired set from the registry (`<prefix><name>` per\n * `slugify`);\n * 2. removes tools no longer desired via `RegisteredTool.remove()`;\n * 3. adds new tools via `server.registerTool(name, config, callback)`;\n * 4. calls `server.sendToolListChanged()` exactly ONCE per mutation\n * cycle (only when at least one add/remove occurred — idempotent\n * no-op when the diff is empty).\n *\n * No-op when `opts.enabled === false` (D-A1b default OFF). The\n * `register_contracts_as_tools` MCP Tool (Plan 06-02 Task 3) forces\n * `enabled: true` regardless of the per-vault config — that is the\n * explicit-control escape valve (D-A1).\n *\n * # Callback shim\n *\n * Each auto-registered tool's callback is a thin wrapper around\n * `opts.instantiateHandler(contractName, args)` (Plan 06-03 supplies the\n * real handler). The wrapper serializes the handler's return as a single\n * `text` content block — matching the v1 `ok()` shape used by\n * `src/server.ts`. Tool argument validation happens in the MCP SDK layer\n * BEFORE the wrapper fires, using the `parsed.inputZodSchema` (Pitfall\n * F1 — SDK 1.29 requires a Zod schema, not raw JSON Schema).\n *\n * # Adapter-seam discipline\n *\n * Imports only `@modelcontextprotocol/sdk` types + Plan 06-01 modules.\n * Zero `fs` / `path` / `yaml` / `chokidar`.\n */\n\nimport type { McpServer, RegisteredTool } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { ContractRegistry } from \"./registry.js\";\nimport type { ParsedContract } from \"./types.js\";\nimport { slugify } from \"./slug.js\";\n\nexport interface SyncAutoRegisteredOpts {\n /** D-A1b — per-vault gate. No-op when false. */\n enabled: boolean;\n /**\n * Plan 06-03 supplies the real handler; Plan 06-02 wires a stub\n * (`not_yet_implemented`) so auto-registration is observable today.\n * Invoked only when a registered `vm_<name>` tool is CALLED — never\n * during registration itself.\n */\n instantiateHandler: (contractName: string, args: unknown) => Promise<unknown>;\n}\n\n/**\n * Diff the registry against `registered`; perform adds/removes via the\n * SDK; fire `sendToolListChanged()` exactly once when at least one\n * change occurred.\n *\n * The `registered` map is OWNED by the caller (one per `startContractRegistry`\n * instance) — this function mutates it in place. That keeps each vault's\n * tool surface independently disposable: a server with two vaults has\n * two `registered` maps; removing vault A's tools does not touch B's\n * handles.\n */\nexport function syncAutoRegistered(\n server: McpServer,\n registry: ContractRegistry,\n prefix: string,\n registered: Map<string, RegisteredTool>,\n opts: SyncAutoRegisteredOpts,\n): void {\n if (!opts.enabled) return;\n\n // Build the desired set: <slug> → ParsedContract.\n const desired = new Map<string, ParsedContract>();\n for (const [name, parsed] of registry.entries()) {\n desired.set(slugify(name, prefix), parsed);\n }\n\n let mutated = false;\n\n // Remove gone — snapshot first since we mutate `registered`.\n for (const [toolName, regd] of Array.from(registered)) {\n if (!desired.has(toolName)) {\n regd.remove();\n registered.delete(toolName);\n mutated = true;\n }\n }\n\n // Add new.\n for (const [toolName, parsed] of desired) {\n if (registered.has(toolName)) continue;\n const contractName = parsed.name;\n const regd = server.registerTool(\n toolName,\n {\n description: parsed.description,\n inputSchema: parsed.inputZodSchema,\n },\n // The callback runs AFTER the SDK validates args against the Zod\n // schema, so `args` is typed-narrowed to the contract's inputs.\n async (args: unknown) => {\n const result = await opts.instantiateHandler(contractName, args);\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(result) }],\n };\n },\n ) as RegisteredTool;\n registered.set(toolName, regd);\n mutated = true;\n }\n\n if (mutated) server.sendToolListChanged();\n}\n","/**\n * resolveTemplate — Phase 6 / D-A2c / ADR-006 §Decision 5 / Invariant C-7.\n *\n * Mustache-style template resolver over a `{inputs, steps}` bindings table.\n * Pure function, zero deps.\n *\n * # Resolution rules\n *\n * 1. Whole-string `^\\{\\{<path>\\}\\}$` → returns the RAW typed value at\n * `<path>` (number, array, object, etc.) — NEVER stringified.\n * 2. Embedded `{{...}}` substitutions inside a larger string → each\n * lookup result is converted to a string (JSON.stringify for\n * non-string values) and concatenated with the surrounding text.\n * 3. Recursion: arrays and objects are walked; each leaf string is\n * resolved independently. Non-string leaves (number, null, boolean)\n * pass through unchanged. The first unresolved leaf short-circuits\n * the whole result.\n * 4. `<path>` syntax — `alias.field.nested[0]`. Split on `.` AND `[i]`\n * via the regex `/[.[\\]]/`; filter empty segments.\n *\n * # Security invariant (C-7, ADR-006 §Decision 5)\n *\n * `resolveTemplate` operates ONLY on contract YAML (read at boot time,\n * never user-supplied at call time). User inputs are looked UP from\n * the bindings table but the looked-up value is NEVER re-evaluated as\n * a template. Test 13 verifies this: if `inputs.x = \"{{inputs.y}}\"`,\n * then `resolveTemplate(\"{{inputs.x}}\", ...)` returns the raw string\n * `\"{{inputs.y}}\"`, not a recursive substitution.\n *\n * Mitigates threat T-06-03-01 (user-controlled template injection).\n *\n * # Adapter-seam discipline\n *\n * Zero `fs` / `path` / `gray-matter` / `chokidar` / `yaml` imports.\n * Pure function only.\n */\n\n/**\n * Binding table consumed by `resolveTemplate`. `inputs` carries the\n * caller-supplied values (resolves under `{{inputs.<name>}}`); `steps`\n * accumulates named-binding outputs (resolves under `{{<alias>.<field>}}`);\n * `handles` carries resolved source/sink handles (resolves directly\n * under `{{<handle_name>}}` without prefix, per RESEARCH Example 1).\n *\n * `handles` is internal to the orchestrator's binding step — it is\n * accessible from templates but NOT returned in the `bundle.steps`\n * field. The orchestrator merges it into the lookup root alongside\n * `steps` so contract YAML authors can write `{{default_sink}}`\n * without an `inputs.` prefix.\n */\nexport interface TemplateBindings {\n inputs: Record<string, unknown>;\n steps: Record<string, unknown>;\n /** Resolved source/sink handles. Accessible as bare `{{handle_name}}`. */\n handles?: Record<string, unknown>;\n}\n\n/**\n * Result envelope. Discriminated union — branch on `.ok` before\n * destructuring. On `false`, `expression` carries the offending\n * `{{...}}` token verbatim (so the orchestrator can surface it in the\n * `InstantiateError.unresolved_template.expression` field).\n */\nexport type TemplateResolveResult<T = unknown> =\n | { ok: true; value: T }\n | { ok: false; reason: \"unresolved_template\"; expression: string };\n\n/** Matches a single `{{<path>}}` token. */\nconst TOKEN_RE = /\\{\\{([^}]+)\\}\\}/g;\n/** Matches a string that is JUST a single template — no surrounding chars. */\nconst WHOLE_STRING_RE = /^\\{\\{([^}]+)\\}\\}$/;\n\n/**\n * Look up `path` against the bindings table. Returns the raw value or\n * `undefined` when any segment is missing.\n *\n * Path syntax — alias.field.nested[0]. The leading segment is treated\n * as a key on `{inputs, steps}` (we merge them into a single root\n * lookup space so contracts can reference `{{inputs.foo}}` or\n * `{{step1.bar}}` without prefixing).\n */\nfunction lookup(path: string, bindings: TemplateBindings): unknown {\n const segments = path.split(/[.[\\]]/).filter(Boolean);\n if (segments.length === 0) return undefined;\n // Unified namespace per RESEARCH Example 2:\n // `{{inputs.<name>}}` resolves through the `inputs` object;\n // `{{<step_alias>.<field>}}` resolves through `steps[<alias>]`;\n // `{{<handle_name>}}` resolves through `handles[<handle_name>]`.\n // Build the root by exposing the `inputs` object directly AND\n // spreading both the steps map and the (optional) handles map so each\n // alias is a top-level key.\n const root: Record<string, unknown> = {\n inputs: bindings.inputs,\n ...bindings.steps,\n ...(bindings.handles ?? {}),\n };\n let cur: unknown = root;\n for (const seg of segments) {\n if (cur === null || cur === undefined) return undefined;\n if (typeof cur !== \"object\") return undefined;\n // Numeric index handling (foo[0] → segments include \"0\").\n if (Array.isArray(cur)) {\n const idx = Number(seg);\n if (!Number.isInteger(idx)) return undefined;\n cur = cur[idx];\n continue;\n }\n cur = (cur as Record<string, unknown>)[seg];\n }\n return cur;\n}\n\n/**\n * Resolve one string value against the bindings. Implements rules (1)\n * and (2) above.\n */\nfunction resolveString(s: string, bindings: TemplateBindings): TemplateResolveResult {\n // Rule 1: whole-string single template → raw typed value.\n const whole = WHOLE_STRING_RE.exec(s);\n if (whole !== null) {\n const path = whole[1]!.trim();\n const v = lookup(path, bindings);\n if (v === undefined) {\n return { ok: false, reason: \"unresolved_template\", expression: `{{${path}}}` };\n }\n return { ok: true, value: v };\n }\n // Rule 2: embedded substitutions — string-concat.\n if (!s.includes(\"{{\")) return { ok: true, value: s };\n let unresolved: string | null = null;\n // Reset regex state for repeated use.\n TOKEN_RE.lastIndex = 0;\n const replaced = s.replace(TOKEN_RE, (_match, rawPath: string) => {\n if (unresolved !== null) return \"\";\n const path = rawPath.trim();\n const v = lookup(path, bindings);\n if (v === undefined) {\n unresolved = `{{${path}}}`;\n return \"\";\n }\n return typeof v === \"string\" ? v : JSON.stringify(v);\n });\n if (unresolved !== null) {\n return { ok: false, reason: \"unresolved_template\", expression: unresolved };\n }\n return { ok: true, value: replaced };\n}\n\n/**\n * Recursive resolver. Walks objects + arrays; leaf strings go through\n * `resolveString`; non-string leaves pass through unchanged. First\n * unresolved leaf short-circuits the whole result (Test 12).\n */\nexport function resolveTemplate<T = unknown>(\n value: unknown,\n bindings: TemplateBindings,\n): TemplateResolveResult<T> {\n if (typeof value === \"string\") {\n return resolveString(value, bindings) as TemplateResolveResult<T>;\n }\n if (Array.isArray(value)) {\n const out: unknown[] = [];\n for (const item of value) {\n const r = resolveTemplate(item, bindings);\n if (!r.ok) return r;\n out.push(r.value);\n }\n return { ok: true, value: out as T };\n }\n if (value !== null && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n const r = resolveTemplate(v, bindings);\n if (!r.ok) return r;\n out[k] = r.value;\n }\n return { ok: true, value: out as T };\n }\n // Pass-through for numbers, booleans, null, undefined.\n return { ok: true, value: value as T };\n}\n","/**\n * PeerMcpRegistry — Phase 6 / D-A2a peer-MCP / RESEARCH §Pattern 3 /\n * Pitfall F4.\n *\n * Lifecycle:\n *\n * - At server boot: `new PeerMcpRegistry(); await reg.start(configs);`\n * Each `[contracts.mcp_clients.<name>]` entry is spawned via\n * `StdioClientTransport(...)` and an MCP SDK `Client` is connected\n * over stdio. Connect failures DO NOT block boot — the registry\n * records the failed name as unavailable and writes a WARN line to\n * stderr (CONTEXT.md \"Claude's Discretion\": peer-MCP unreachable is\n * not a server-fatal condition).\n *\n * - At runtime: `verbDispatcher` consults the registry on every\n * `mcp://<server>/<tool>` verb. The peer-MCP call is wrapped in\n * `Promise.race([call, timeout(step_timeout_seconds * 1000)])` at\n * `verbs/mcp-extension.ts` (Q-TIMEOUT — peer-MCP only).\n *\n * - At shutdown: `process.on('SIGTERM' | 'SIGINT')` handlers in\n * `src/server.ts` call `reg.shutdown()`, which iterates every\n * PeerMcpClient and invokes `[Symbol.dispose]()` →\n * `transport.close()` → child process killed. Mitigates Pitfall F4\n * (orphaned child processes after parent crash).\n *\n * # Envelope peeling\n *\n * MCP `tools/call` returns `{content: [{type:'text', text: '...'}]}`.\n * The wrapper peels one layer: when the first content block is a\n * `text` and the text parses as JSON, return the parsed object; when\n * the text is not JSON, return the raw string; otherwise return the\n * full envelope. Callers see ergonomic data, not raw protocol shapes.\n *\n * # Testability\n *\n * `ClientFactory` is an optional constructor parameter — tests inject\n * a stub factory that returns a mock Client without spawning a real\n * child process. Plan 06-04's CON-09 smoketest exercises the real\n * `defaultConnect` path end-to-end.\n *\n * # Adapter-seam discipline\n *\n * Imports only `@modelcontextprotocol/sdk/client/*`. The\n * `StdioClientTransport` spawns a child via the SDK's own\n * `child_process.spawn` — encapsulated, not leaked into `src/contracts/`.\n */\n\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\n/** Single `[contracts.mcp_clients.<name>]` config entry. */\nexport interface PeerMcpClientConfig {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n}\n\n/**\n * One tool a peer exposes, as returned by MCP `tools/list`. Mirrors the\n * subset of the SDK's `ListToolsResult.tools[]` that the Sources\n * registry surfaces (SOURCES-REGISTRY.md §5.2). `inputSchema` is opaque\n * here — the inspector consumes it to type step args.\n */\nexport interface PeerMcpTool {\n name: string;\n description?: string;\n inputSchema?: Record<string, unknown>;\n}\n\n/**\n * Connection state for a source (SOURCES-REGISTRY.md §5.1):\n * - \"connected\" — connect succeeded AND tools/list succeeded ≥ once.\n * - \"unavailable\" — the (re)connect attempt failed.\n * - \"unreachable\" — connected at some point but a later tools/list failed.\n */\nexport type PeerMcpStatus = \"connected\" | \"unavailable\" | \"unreachable\";\n\n/** Read-only projection of a source's cached state for resource handlers. */\nexport interface PeerMcpClientInfo {\n status: PeerMcpStatus;\n tools: readonly PeerMcpTool[];\n /** Epoch-seconds of the last successful tools/list; null if never. */\n lastRefreshed: number | null;\n /** Captured error message when status is \"unavailable\"/\"unreachable\". */\n error?: string;\n}\n\n/** A live peer-MCP client managed by the registry. */\nexport interface PeerMcpClient {\n /** Forward a `tools/call` to the peer, peeling the MCP envelope. */\n callTool(name: string, args: unknown): Promise<unknown>;\n /** Fetch the peer's tools/list. Throws when the client is unavailable. */\n listTools(): Promise<PeerMcpTool[]>;\n /** False when the boot-time connect failed; calling `callTool` throws. */\n available: boolean;\n /** Kills the underlying child process. Idempotent (transport.close is). */\n [Symbol.dispose](): void;\n}\n\n/** Minimal client surface the registry depends on (subset of SDK `Client`). */\nexport interface PeerClientLike {\n callTool: Client[\"callTool\"];\n /** Present on the real SDK Client; optional so older stubs still satisfy the type. */\n listTools?: Client[\"listTools\"];\n}\n\n/**\n * Optional injection point for tests. Production code uses\n * `defaultConnect` which spawns a real child via `StdioClientTransport`.\n */\nexport type ClientFactory = (\n cfg: PeerMcpClientConfig,\n) => Promise<{ client: PeerClientLike; transport: { close(): void } }>;\n\n/** Internal per-source record: the wrapped client + its cached metadata. */\ninterface RegistryEntry {\n client: PeerMcpClient;\n status: PeerMcpStatus;\n tools: PeerMcpTool[];\n lastRefreshed: number | null;\n error?: string;\n}\n\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\nexport class PeerMcpRegistry {\n private entries = new Map<string, RegistryEntry>();\n private readonly clientFactory: ClientFactory | undefined;\n\n constructor(clientFactory?: ClientFactory) {\n this.clientFactory = clientFactory;\n }\n\n get size(): number {\n return this.entries.size;\n }\n\n /**\n * Boot every `[contracts.mcp_clients.<name>]` entry. Failures are\n * non-fatal: the name is recorded as unavailable and a WARN line is\n * written to stderr. Returns when all attempts have settled.\n *\n * On a successful connect we prime the tools cache via tools/list. A\n * tools/list failure does NOT mark the source unavailable — the\n * connection is live and callTool may still work — but the status\n * becomes \"unreachable\" so the UI can prompt a retry.\n */\n async start(configs: Record<string, PeerMcpClientConfig>): Promise<void> {\n for (const [name, cfg] of Object.entries(configs)) {\n await this.connectAndStore(name, cfg);\n }\n }\n\n get(name: string): PeerMcpClient | undefined {\n return this.entries.get(name)?.client;\n }\n\n /** All registered source names, in insertion order. */\n names(): string[] {\n return Array.from(this.entries.keys());\n }\n\n /** Cached metadata projection for one source; undefined if unknown. */\n getInfo(name: string): PeerMcpClientInfo | undefined {\n const e = this.entries.get(name);\n if (e === undefined) return undefined;\n return {\n status: e.status,\n tools: e.tools,\n lastRefreshed: e.lastRefreshed,\n ...(e.error !== undefined ? { error: e.error } : {}),\n };\n }\n\n /**\n * Register a new source at runtime: spawn + connect, then prime the\n * tools cache. Replaces any existing entry of the same name (the old\n * client is disposed first). Returns the resulting info projection.\n */\n async add(name: string, cfg: PeerMcpClientConfig): Promise<PeerMcpClientInfo> {\n const existing = this.entries.get(name);\n if (existing !== undefined) {\n try {\n existing.client[Symbol.dispose]();\n } catch {\n // Best-effort — replacing the entry regardless.\n }\n }\n await this.connectAndStore(name, cfg);\n // connectAndStore always sets an entry, so getInfo is non-undefined.\n return this.getInfo(name)!;\n }\n\n /**\n * Dispose a source and drop it from the registry. Idempotent —\n * removing an unknown name is a no-op that returns false.\n */\n remove(name: string): boolean {\n const e = this.entries.get(name);\n if (e === undefined) return false;\n try {\n e.client[Symbol.dispose]();\n } catch (err) {\n const msg = errorMessage(err);\n process.stderr.write(`[contracts] peer-MCP dispose error: ${msg}\\n`);\n }\n this.entries.delete(name);\n return true;\n }\n\n /**\n * Re-issue tools/list against the live client and refresh the cache.\n * Returns the updated info, or undefined if the name is unknown.\n *\n * If the client is currently unavailable this only updates the error;\n * re-spawning a failed source requires `add(name, cfg)` with the\n * config (the registry does not retain configs).\n */\n async refresh(name: string): Promise<PeerMcpClientInfo | undefined> {\n const e = this.entries.get(name);\n if (e === undefined) return undefined;\n if (!e.client.available) {\n e.status = \"unavailable\";\n return this.getInfo(name);\n }\n await this.primeTools(e);\n return this.getInfo(name);\n }\n\n /** Dispose every client and clear the internal map. Idempotent. */\n async shutdown(): Promise<void> {\n for (const e of this.entries.values()) {\n try {\n e.client[Symbol.dispose]();\n } catch (err) {\n const msg = errorMessage(err);\n process.stderr.write(`[contracts] peer-MCP dispose error: ${msg}\\n`);\n }\n }\n this.entries.clear();\n }\n\n // ─── internals ─────────────────────────────────────────────────────────\n\n /** Connect (factory or default), store the entry, prime tools cache. */\n private async connectAndStore(name: string, cfg: PeerMcpClientConfig): Promise<void> {\n try {\n const { client, transport } = this.clientFactory\n ? await this.clientFactory(cfg)\n : await this.defaultConnect(cfg);\n const entry: RegistryEntry = {\n client: wrapAvailable(client, transport),\n status: \"connected\",\n tools: [],\n lastRefreshed: null,\n };\n this.entries.set(name, entry);\n await this.primeTools(entry);\n } catch (err) {\n const msg = errorMessage(err);\n process.stderr.write(`[contracts] peer-MCP client '${name}' failed to start: ${msg}\\n`);\n this.entries.set(name, {\n client: wrapUnavailable(),\n status: \"unavailable\",\n tools: [],\n lastRefreshed: null,\n error: msg,\n });\n }\n }\n\n /**\n * Call tools/list and update the entry's cache + status. A failure\n * keeps the connection (status → \"unreachable\") rather than tearing it\n * down — the source is reachable for callTool even if discovery failed.\n */\n private async primeTools(entry: RegistryEntry): Promise<void> {\n try {\n const tools = await entry.client.listTools();\n entry.tools = tools;\n entry.lastRefreshed = nowSeconds();\n entry.status = \"connected\";\n delete entry.error;\n } catch (err) {\n entry.status = \"unreachable\";\n entry.error = errorMessage(err);\n }\n }\n\n private async defaultConnect(\n cfg: PeerMcpClientConfig,\n ): Promise<{ client: Client; transport: StdioClientTransport }> {\n const transport = new StdioClientTransport({\n command: cfg.command,\n args: cfg.args ?? [],\n env: cfg.env,\n });\n const client = new Client({ name: \"vault-memory-peer\", version: \"2.0.0\" });\n await client.connect(transport);\n return { client, transport };\n }\n}\n\nfunction wrapAvailable(client: PeerClientLike, transport: { close(): void }): PeerMcpClient {\n return {\n available: true,\n async callTool(name: string, args: unknown): Promise<unknown> {\n const res = await client.callTool({\n name,\n arguments: args as Record<string, unknown>,\n });\n // Peel MCP envelope: result.content[0] is typically\n // {type:'text', text: '...'}. Return parsed JSON when applicable.\n const content = (res as { content?: unknown }).content;\n if (Array.isArray(content) && content.length > 0) {\n const first = content[0] as { type?: string; text?: string };\n if (first.type === \"text\" && typeof first.text === \"string\") {\n try {\n return JSON.parse(first.text);\n } catch {\n return first.text;\n }\n }\n }\n return res;\n },\n async listTools(): Promise<PeerMcpTool[]> {\n // A peer without listTools support (older stub or a server that\n // doesn't advertise the tools capability) yields an empty set\n // rather than throwing — an empty palette is fine; a crash is not.\n if (typeof client.listTools !== \"function\") return [];\n const res = await client.listTools();\n const tools = (res as { tools?: unknown }).tools;\n if (!Array.isArray(tools)) return [];\n const out: PeerMcpTool[] = [];\n for (const t of tools) {\n if (!t || typeof t !== \"object\") continue;\n const name = (t as { name?: unknown }).name;\n if (typeof name !== \"string\") continue;\n const tool: PeerMcpTool = { name };\n const description = (t as { description?: unknown }).description;\n if (typeof description === \"string\") tool.description = description;\n const inputSchema = (t as { inputSchema?: unknown }).inputSchema;\n if (inputSchema && typeof inputSchema === \"object\") {\n tool.inputSchema = inputSchema as Record<string, unknown>;\n }\n out.push(tool);\n }\n return out;\n },\n [Symbol.dispose](): void {\n transport.close();\n },\n };\n}\n\nfunction wrapUnavailable(): PeerMcpClient {\n return {\n available: false,\n async callTool(): Promise<unknown> {\n throw new Error(\"peer-MCP client unavailable\");\n },\n async listTools(): Promise<PeerMcpTool[]> {\n throw new Error(\"peer-MCP client unavailable\");\n },\n [Symbol.dispose](): void {\n /* no-op */\n },\n };\n}\n","/**\n * callMcpVerb — Phase 6 / D-A2a peer-MCP extension / Q-TIMEOUT.\n *\n * Parses `mcp://<server>/<tool>` syntax, looks the client up in the\n * `PeerMcpRegistry`, forwards the args, and wraps the call in\n * `Promise.race([call, timeout(step_timeout_seconds * 1000)])` so a\n * hung peer cannot block contract instantiation indefinitely.\n *\n * # Q-TIMEOUT scope (ADR-006 §Decision 11)\n *\n * ONLY peer-MCP verbs are wrapped here. Baseline verbs route directly\n * through their handlers in `verbs/index.ts` without the race — they\n * use their own timeout discipline (SQLite query timeout, Ollama HTTP\n * timeout). Wrapping baseline verbs adds latency overhead for no\n * benefit.\n *\n * # Failure envelopes (ADR-006 §Decision 7, sealed for v2.0.0)\n *\n * - `{ok:false, reason:'verb_not_available', verb}` — regex rejected\n * the verb shape (defense in depth; Plan 06-01's Zod gate already\n * rejects malformed verbs at contract load time).\n * - `{ok:false, reason:'mcp_client_unavailable', verb, client_name}` —\n * the server name has no registered client OR the boot connect\n * failed.\n * - `{ok:false, reason:'assembly_step_failed', step_alias, cause}` —\n * either a timeout (`cause: 'timeout'`) or the underlying call\n * threw (`cause: <error message>`).\n *\n * # Adapter-seam discipline\n *\n * Zero `fs` / `path` / `gray-matter` / `chokidar` imports.\n */\n\nimport type { PeerMcpRegistry } from \"../mcp-clients.js\";\n\n/** Same shape as `verbDispatcher`'s `opts` so callers can pass through. */\nexport interface VerbDispatchOpts {\n stepAlias: string;\n timeoutSeconds: number;\n}\n\n/**\n * `mcp://<server>/<tool>` — both segments must be `[a-z][a-z0-9_-]*`.\n * Mirrors the Zod regex used by the contract loader (Plan 06-01\n * `schema.ts`). Pinned here so defense-in-depth dispatch rejects the\n * same shapes the loader rejects.\n */\nconst MCP_VERB_RE = /^mcp:\\/\\/([a-z][a-z0-9_-]*)\\/([a-z][a-z0-9_-]*)$/;\n\nexport async function callMcpVerb(\n verb: string,\n args: Record<string, unknown>,\n registry: PeerMcpRegistry,\n opts: VerbDispatchOpts,\n): Promise<unknown> {\n const match = MCP_VERB_RE.exec(verb);\n if (!match) {\n return { ok: false, reason: \"verb_not_available\", verb };\n }\n const serverName = match[1]!;\n const toolName = match[2]!;\n const client = registry.get(serverName);\n if (!client || !client.available) {\n return {\n ok: false,\n reason: \"mcp_client_unavailable\",\n verb,\n client_name: serverName,\n };\n }\n // Q-TIMEOUT: wrap ONLY peer-MCP verbs.\n const timeoutMs = Math.max(1, Math.floor(opts.timeoutSeconds * 1000));\n let timer: NodeJS.Timeout | undefined;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(\"timeout\")), timeoutMs);\n });\n try {\n return await Promise.race([client.callTool(toolName, args), timeoutPromise]);\n } catch (err) {\n const cause =\n err instanceof Error && err.message === \"timeout\"\n ? \"timeout\"\n : err instanceof Error\n ? err.message\n : String(err);\n return {\n ok: false,\n reason: \"assembly_step_failed\",\n step_alias: opts.stepAlias,\n cause,\n };\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n","/**\n * verbDispatcher — Phase 6 / D-A2a / ADR-006 §Decision 2 / Invariant C-1.\n *\n * Closed 11-verb baseline dispatcher + `\"literal\"` escape + `mcp://`\n * peer-MCP extension. Write verbs are NOT part of the assembly enum —\n * writes happen exclusively via the structurally-separate `write_back:`\n * block (Invariant C-1).\n *\n * # Baseline verb signatures (verified against existing implementations per RESEARCH §A9)\n *\n * - search_hybrid: ({query, vaults?, top_k?, recency_weight?, authority_weight?, include_superseded?, expand?}) → {hits}\n * - expand: ({seed_doc_ids, hops, direction?, edge_types?, filter_properties?, include_superseded?}) → {doc_ids, edges}\n * - cluster: ({seed_doc_ids?, query?, vault?, method, query_top_k?, force?}) → {clusters}\n * - recall: ({query, min_confidence?, types?, max_age_days?, sink?, vaults?, limit?}) → {hits}\n * - compile_brief: ({vault, target, source_doc_ids, purpose, max_tokens?, prepared_text?, sink?}) → {ok, doc_id, body?}\n * - get_brief: ({vault, target, max_age_days?, allow_stale?}) → Brief | {stale: true, ...} | null\n * - query_frontmatter: ({vault, where, limit?}) → {doc_ids, rows}\n * - list_backlinks: ({vault, path}) → {backlinks}\n * - get_outline: ({doc_id, vaults?}) → {nodes}\n * - search_sections: ({query, vaults?, limit?, recency_weight?, authority_weight?, include_superseded?}) → {hits}\n * - read_note: ({vault, path}) → {body, properties, ...}\n *\n * Each adapter passes contract YAML args (post-template-resolution)\n * verbatim to the verb handler — no reshaping. The contract author is\n * responsible for matching the verb's documented signature; Zod\n * validation at `instantiate_contract` time catches type mismatches.\n *\n * # Q-TIMEOUT (ADR-006 §Decision 11)\n *\n * `opts.timeoutSeconds` applies ONLY to `mcp://*` verbs (peer-MCP).\n * Baseline verbs are NOT wrapped — they use their own timeout\n * discipline. Test 11 verifies that an absurdly small\n * `timeoutSeconds` does not affect baseline dispatch.\n *\n * # Adapter-seam discipline\n *\n * Imports `../mcp-clients.js` (registry type), `../types.js`\n * (AssemblyVerb type), and `./mcp-extension.js`. Zero `fs` / `path` /\n * `gray-matter` / `chokidar` imports.\n */\n\nimport type { AssemblyVerb } from \"../types.js\";\nimport type { PeerMcpRegistry } from \"../mcp-clients.js\";\nimport { callMcpVerb, type VerbDispatchOpts } from \"./mcp-extension.js\";\n\nexport type { VerbDispatchOpts } from \"./mcp-extension.js\";\n\n/**\n * Dependencies injected into `verbDispatcher`. Each handler is a thin\n * thunk over the existing Phase 1-5 implementation — `instantiate.ts`\n * binds these against a specific Vault at call site.\n */\nexport interface VerbDeps {\n hybridSearch: (args: unknown) => Promise<unknown>;\n handleExpand: (args: unknown) => Promise<unknown>;\n handleCluster: (args: unknown) => Promise<unknown>;\n handleRecall: (args: unknown) => Promise<unknown>;\n handleCompileBrief: (args: unknown) => Promise<unknown>;\n handleGetBrief: (args: unknown) => Promise<unknown>;\n handleQueryFrontmatter: (args: unknown) => Promise<unknown>;\n handleListBacklinks: (args: unknown) => Promise<unknown>;\n handleGetOutline: (args: unknown) => Promise<unknown>;\n handleSearchSections: (args: unknown) => Promise<unknown>;\n handleReadNote: (args: unknown) => Promise<unknown>;\n peerMcpRegistry: PeerMcpRegistry;\n}\n\n/**\n * Dispatch one assembly step. Returns the verb's output OR a structured\n * error envelope; the orchestrator (`instantiate.ts`) inspects the\n * shape and either binds the output under the step's `as:` alias or\n * short-circuits with an `InstantiateError`.\n *\n * `step` carries the original step record so `literal` can peel\n * `step.value` (not `args`).\n */\nexport async function verbDispatcher(\n verb: AssemblyVerb,\n args: Record<string, unknown> | undefined,\n step: { value?: unknown } | undefined,\n deps: VerbDeps,\n opts: VerbDispatchOpts,\n): Promise<unknown> {\n // The `literal` escape hatch — emits `step.value` verbatim.\n if (verb === \"literal\") {\n return step?.value;\n }\n // Peer-MCP extension — wrapped in Q-TIMEOUT.\n if (typeof verb === \"string\" && verb.startsWith(\"mcp://\")) {\n return callMcpVerb(verb, args ?? {}, deps.peerMcpRegistry, opts);\n }\n // Closed baseline enum.\n switch (verb) {\n case \"search_hybrid\":\n return deps.hybridSearch(args);\n case \"expand\":\n return deps.handleExpand(args);\n case \"cluster\":\n return deps.handleCluster(args);\n case \"recall\":\n return deps.handleRecall(args);\n case \"compile_brief\":\n return deps.handleCompileBrief(args);\n case \"get_brief\":\n return deps.handleGetBrief(args);\n case \"query_frontmatter\":\n return deps.handleQueryFrontmatter(args);\n case \"list_backlinks\":\n return deps.handleListBacklinks(args);\n case \"get_outline\":\n return deps.handleGetOutline(args);\n case \"search_sections\":\n return deps.handleSearchSections(args);\n case \"read_note\":\n return deps.handleReadNote(args);\n default:\n // Defense-in-depth — the Zod schema at contract load rejects any\n // verb outside the closed enum; this is the runtime backstop.\n return { ok: false, reason: \"verb_not_available\", verb };\n }\n}\n","/**\n * instantiateContract — Phase 6 / CON-06 / D-A4a/b/c / Q-OUTPUT.\n *\n * The L4 orchestrator: takes a contract name + inputs (+ optional\n * source/sink overrides), executes the full 7-step pipeline from\n * RESEARCH §Architecture, and returns either the shaped bundle or a\n * structured `InstantiateError` envelope.\n *\n * # Pipeline (RESEARCH §Architecture (1)-(7))\n *\n * (1) Lookup contract → `unknown_contract` if missing.\n * (2) Zod-validate inputs against `parsed.inputZodSchema` (Pitfall F2:\n * additionalProperties:false rejects typos) → `invalid_inputs`.\n * (3) Resolve override handles. Reject unknown handles (validated\n * against `parsed.sources`/`parsed.sinks` keys). Sinks ADDITIONALLY\n * validate through `MemorySinkRegistry.resolveMemorySink` (D-A4c\n * — MEM-05 invariant un-bypassable). Default chain per D-A4b:\n * explicit override → config default → contract YAML literal →\n * error if required.\n * (4) Build template bindings: `inputs` carries the caller's data PLUS\n * resolved source/sink handles (so `{{default_source}}` works);\n * `steps` starts empty and accumulates as the loop runs.\n * (5) For each assembly step:\n * a. Resolve `{{templates}}` in step.args + step.value via\n * `resolveTemplate`. Unresolved → `unresolved_template`.\n * b. Dispatch via `verbDispatcher`. Thrown errors caught and\n * surfaced as `assembly_step_failed`. Structured-error\n * envelopes (`verb_not_available`, `mcp_client_unavailable`)\n * from the dispatcher pass through directly.\n * c. Write a `contract_audit kind:'contract_step'` row REGARDLESS\n * of success/failure (payload-free per C-5).\n * d. Bind output under `step.as` in `bindings.steps`.\n * (6) If `parsed.write_back` exists: resolve templates on `sink`,\n * `body_from`, `properties` and route through\n * `DeliveryAdapter.write` (MEM-05 chokepoint). Thrown → `write_back_failed`.\n * (7) If `parsed.output_shape` exists: build a Zod schema via\n * `z.fromJSONSchema(parsed.output_shape)` and `safeParse` the\n * `{steps, write_back}` bundle (Q-OUTPUT). Mismatch →\n * `validation_failed_on_output_shape`. Parse failure inside the\n * Zod build → stderr WARN + skip (graceful degradation).\n *\n * # Invariants (ADR-006)\n *\n * - C-1: The verb enum has NO write verbs. The dispatcher's `default`\n * branch rejects unknown verbs (defense-in-depth).\n * - C-2: All sinks pass through `MemorySinkRegistry.resolveMemorySink`\n * before the write_back path runs. Tested in Test 7.\n * - C-3: Only `DeliveryAdapter.write()`'s return value populates\n * `bundle.write_back.doc_id`. Peer-MCP outputs are advisory step\n * bindings — they cannot fabricate a DocId.\n * - C-5: `recordContractStep` is payload-free; its TypeScript\n * signature excludes any output/payload field.\n * - C-7: User-supplied input values are NEVER re-evaluated as\n * templates (verified in templates.test.ts Test 13).\n *\n * # Adapter-seam discipline\n *\n * Imports zod + sibling contracts modules + MemorySinkRegistry type +\n * DeliveryAdapter type. Zero `fs` / `path` / `gray-matter` /\n * `chokidar` / `yaml` imports.\n */\n\nimport { z } from \"zod\";\nimport type { ContractRegistry } from \"./registry.js\";\nimport type { InstantiateError, OverrideMap, ContractStep } from \"./types.js\";\nimport { resolveTemplate, type TemplateBindings } from \"./templates.js\";\nimport { verbDispatcher, type VerbDeps } from \"./verbs/index.js\";\nimport { recordContractStep, type ContractAuditDeps } from \"./audit.js\";\nimport type { MemorySinkRegistry } from \"../memory/registry.js\";\nimport type { DeliveryAdapter } from \"../adapters/delivery/types.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { Document, DocId } from \"../types.js\";\nimport { errorMessage } from \"../errors/format.js\";\n\n// ─────────────────────────────────────────────────────────────────────────\n// Public surface\n// ─────────────────────────────────────────────────────────────────────────\n\nexport interface InstantiateDeps extends VerbDeps, ContractAuditDeps {\n vault: Vault;\n registry: ContractRegistry;\n memorySinks: MemorySinkRegistry;\n delivery: DeliveryAdapter;\n /** From `[contracts.defaults]` — overrides contract YAML literals. */\n configDefaults: Record<string, string>;\n /** Q-TIMEOUT — applied ONLY to peer-MCP verbs. */\n stepTimeoutSeconds: number;\n}\n\nexport interface InstantiateArgs {\n name: string;\n inputs: Record<string, unknown>;\n source_overrides?: OverrideMap;\n sink_overrides?: OverrideMap;\n}\n\n/** Q-OUTPUT — the bundle shape returned to callers on success. */\nexport interface InstantiateBundle {\n steps: Record<string, unknown>;\n write_back: { doc_id: string; sink: string } | null;\n}\n\nexport type InstantiateResult = ({ ok: true } & InstantiateBundle) | InstantiateError;\n\n// ─────────────────────────────────────────────────────────────────────────\n// Orchestrator\n// ─────────────────────────────────────────────────────────────────────────\n\nexport async function instantiateContract(\n deps: InstantiateDeps,\n args: InstantiateArgs,\n): Promise<InstantiateResult> {\n // (1) Lookup.\n const parsed = deps.registry.get(args.name);\n if (!parsed) return { ok: false, reason: \"unknown_contract\", name: args.name };\n\n // (2) Zod-validate inputs (Pitfall F2: additionalProperties:false).\n const inputCheck = parsed.inputZodSchema.safeParse(args.inputs);\n if (!inputCheck.success) {\n return { ok: false, reason: \"invalid_inputs\", issues: inputCheck.error.format() };\n }\n\n // (3a) Reject unknown override handles for sources.\n const validSourceHandles = Object.keys(parsed.sources);\n for (const handle of Object.keys(args.source_overrides ?? {})) {\n if (!validSourceHandles.includes(handle)) {\n return {\n ok: false,\n reason: \"unknown_override_handle\",\n handle,\n valid_handles: validSourceHandles,\n };\n }\n }\n // (3b) Reject unknown override handles for sinks.\n const validSinkHandles = Object.keys(parsed.sinks);\n for (const handle of Object.keys(args.sink_overrides ?? {})) {\n if (!validSinkHandles.includes(handle)) {\n return {\n ok: false,\n reason: \"unknown_override_handle\",\n handle,\n valid_handles: validSinkHandles,\n };\n }\n }\n\n // (3c) Default chain per D-A4b: explicit → config → contract literal → error if required.\n const resolvedSources: Record<string, string> = {};\n for (const [handle, decl] of Object.entries(parsed.sources)) {\n const v =\n args.source_overrides?.[handle] ??\n deps.configDefaults[handle] ??\n (decl.handle === \"\" ? undefined : decl.handle);\n if (v === undefined && decl.required) {\n return {\n ok: false,\n reason: \"missing_required_source\",\n handle,\n hint: `pass via source_overrides or set [contracts.defaults.${handle}] in config.toml`,\n };\n }\n if (v !== undefined) resolvedSources[handle] = v;\n }\n const resolvedSinks: Record<string, string> = {};\n for (const [handle, decl] of Object.entries(parsed.sinks)) {\n const v =\n args.sink_overrides?.[handle] ??\n deps.configDefaults[handle] ??\n (decl.handle === \"\" ? undefined : decl.handle);\n if (v === undefined && decl.required) {\n return {\n ok: false,\n reason: \"missing_required_source\",\n handle,\n hint: `pass via sink_overrides or set [contracts.defaults.${handle}] in config.toml`,\n };\n }\n if (v !== undefined) {\n // (4) D-A4c MEM-05 invariant — must resolve through MemorySinkRegistry.\n try {\n deps.memorySinks.resolveMemorySink(v);\n } catch {\n return {\n ok: false,\n reason: \"sink_override_not_a_memory_sink\",\n target: v,\n hint: \"sinks must be a registered MemorySink handle (see list_sinks)\",\n };\n }\n resolvedSinks[handle] = v;\n }\n }\n\n // (5) Build template bindings. The three namespaces are kept separate\n // so the returned `bundle.steps` carries ONLY step outputs (not the\n // resolved source/sink handles). Both access patterns are supported:\n // - `{{default_sink}}` resolves via the `handles` map (bare name);\n // - `{{inputs.default_sink}}` resolves via `inputs.<handle>` (a\n // mirror copy is placed under `inputs` so contract authors who\n // prefer the explicit path notation are not blocked).\n // - `{{inputs.x}}` resolves caller-supplied data via `inputs`;\n // - `{{step1.y}}` resolves accumulated step outputs via `steps`.\n // Caller inputs cannot collide with declared handles (Zod\n // additionalProperties:false rejects unknown keys at input validation).\n const bindings: TemplateBindings = {\n inputs: { ...inputCheck.data, ...resolvedSources, ...resolvedSinks },\n steps: {},\n handles: { ...resolvedSources, ...resolvedSinks },\n };\n\n // (6) Execute steps.\n for (const step of parsed.assembly) {\n const stepResult = await runStep(deps, parsed.name, step, bindings);\n if (\"error\" in stepResult) {\n return stepResult.error;\n }\n bindings.steps[step.as] = stepResult.value;\n }\n\n // (7) Run write_back via DeliveryAdapter.write (MEM-05 chokepoint).\n let writeBackResult: { doc_id: string; sink: string } | null = null;\n if (parsed.write_back) {\n const wb = parsed.write_back;\n const sinkResolved = resolveTemplate(wb.sink, bindings);\n if (!sinkResolved.ok) {\n return { ok: false, reason: \"unresolved_template\", expression: sinkResolved.expression };\n }\n const bodyResolved = resolveTemplate(wb.body_from, bindings);\n if (!bodyResolved.ok) {\n return { ok: false, reason: \"unresolved_template\", expression: bodyResolved.expression };\n }\n const propsResolved = resolveTemplate(wb.properties, bindings);\n if (!propsResolved.ok) {\n return { ok: false, reason: \"unresolved_template\", expression: propsResolved.expression };\n }\n if (typeof bodyResolved.value !== \"string\") {\n return {\n ok: false,\n reason: \"write_back_failed\",\n cause: `body_from must resolve to a string, got ${typeof bodyResolved.value}`,\n };\n }\n const sinkResolvedString =\n typeof sinkResolved.value === \"string\" ? sinkResolved.value : String(sinkResolved.value);\n // Resolve the sink name/handle to its canonical full handle (e.g.\n // `obsidian-fs://test-vault/_memory/`). MemorySinkRegistry accepts\n // either form via resolveMemorySink.\n let sinkObj: { handle: unknown; vault: string; resolveToRelativePath: string };\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n sinkObj = deps.memorySinks.resolveMemorySink(sinkResolvedString) as any;\n } catch {\n return {\n ok: false,\n reason: \"write_back_failed\",\n cause: `sink \"${sinkResolvedString}\" did not resolve to a registered MemorySink`,\n };\n }\n const sinkHandle = sinkObj.handle as unknown as string;\n try {\n // Compose a Document patch — body lives in a single paragraph\n // block; the DeliveryAdapter assigns the final filename via the\n // contract's `naming` strategy.\n const doc: Partial<Document> = {\n blocks: [{ kind: \"paragraph\", text: bodyResolved.value }],\n properties: propsResolved.value as Record<string, unknown>,\n };\n // Synthesize a real DocId rooted in the sink folder. The\n // obsidian-fs delivery adapter's NAMING-AUTO logic rewrites the\n // last path segment per the bound MemoryContract's naming\n // strategy (date-slug for default-memory-v1, caller-provided for\n // default-brief-v1). We pick a placeholder slug from the\n // contract name + step alias namespace so the DocId is a valid\n // path even before the rewrite. Plan 06-04 may swap this for an\n // adapter-side allocator that returns the final DocId without a\n // placeholder round-trip.\n // Placeholder filename — the obsidian-fs adapter's NAMING-AUTO\n // logic rewrites this per the bound MemoryContract's naming\n // strategy. The extension is adapter-specific (markdown for\n // obsidian-fs) but we never hard-code it here per ADR-002 I-5;\n // the adapter appends the extension when it rewrites the path.\n const placeholderName = String(parsed.name).replace(/[^a-z0-9-]/gi, \"_\");\n const placeholderResource = sinkObj.resolveToRelativePath + placeholderName;\n const placeholderId =\n `obsidian-fs://${sinkObj.vault}/${placeholderResource}` as unknown as DocId;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const writeRes: any = await deps.delivery.write(placeholderId, doc, {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n sink: sinkHandle as any,\n });\n if (writeRes && writeRes.ok === false) {\n return {\n ok: false,\n reason: \"write_back_failed\",\n cause: String(writeRes.reason ?? writeRes.message ?? \"unknown write failure\"),\n };\n }\n writeBackResult = {\n doc_id: String(writeRes.doc_id),\n sink: sinkHandle,\n };\n } catch (err) {\n const cause = errorMessage(err);\n return { ok: false, reason: \"write_back_failed\", cause };\n }\n }\n\n // (8) Validate bundle against output_shape (Q-OUTPUT).\n const bundle: InstantiateBundle = {\n steps: bindings.steps,\n write_back: writeBackResult,\n };\n if (parsed.output_shape) {\n try {\n const outputSchema = z.fromJSONSchema(\n parsed.output_shape as unknown as Parameters<typeof z.fromJSONSchema>[0],\n );\n const check = outputSchema.safeParse(bundle);\n if (!check.success) {\n return {\n ok: false,\n reason: \"validation_failed_on_output_shape\",\n issues: check.error.format(),\n };\n }\n } catch (err) {\n // The contract YAML's output_shape is not a Zod-parseable JSON\n // Schema. Log + skip (graceful degradation) — the contract\n // author can iterate without breaking the slice.\n const msg = errorMessage(err);\n process.stderr.write(`[contracts] output_shape validation skipped: ${msg}\\n`);\n }\n }\n\n return { ok: true, ...bundle };\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Internals\n// ─────────────────────────────────────────────────────────────────────────\n\ninterface StepValue {\n value: unknown;\n}\ninterface StepError {\n error: InstantiateError;\n}\n\n/**\n * Run one assembly step: resolve templates → dispatch → record audit\n * (always) → return the bound output OR a structured error.\n */\nasync function runStep(\n deps: InstantiateDeps,\n contractName: string,\n step: ContractStep,\n bindings: TemplateBindings,\n): Promise<StepValue | StepError> {\n // (a) Resolve templates on args + value.\n const resolvedArgs = step.args\n ? resolveTemplate(step.args, bindings)\n : { ok: true as const, value: undefined };\n if (!resolvedArgs.ok) {\n writeAuditRow(deps, contractName, step);\n return {\n error: {\n ok: false,\n reason: \"unresolved_template\",\n expression: resolvedArgs.expression,\n },\n };\n }\n const resolvedValue =\n step.value !== undefined\n ? resolveTemplate(step.value, bindings)\n : { ok: true as const, value: undefined };\n if (!resolvedValue.ok) {\n writeAuditRow(deps, contractName, step);\n return {\n error: {\n ok: false,\n reason: \"unresolved_template\",\n expression: resolvedValue.expression,\n },\n };\n }\n\n // (b) Dispatch verb.\n let output: unknown;\n try {\n output = await verbDispatcher(\n step.verb,\n resolvedArgs.value as Record<string, unknown> | undefined,\n { value: resolvedValue.value },\n deps,\n { stepAlias: step.as, timeoutSeconds: deps.stepTimeoutSeconds },\n );\n } catch (err) {\n writeAuditRow(deps, contractName, step);\n const cause = errorMessage(err);\n return {\n error: {\n ok: false,\n reason: \"assembly_step_failed\",\n step_alias: step.as,\n cause,\n },\n };\n }\n\n // (c) Write audit row.\n writeAuditRow(deps, contractName, step);\n\n // (d) If the dispatcher returned a structured error envelope, surface it.\n if (\n output !== null &&\n typeof output === \"object\" &&\n \"ok\" in (output as Record<string, unknown>) &&\n (output as { ok: boolean }).ok === false\n ) {\n // The dispatcher emits one of:\n // - {ok:false, reason:\"verb_not_available\", verb}\n // - {ok:false, reason:\"mcp_client_unavailable\", verb, client_name}\n // - {ok:false, reason:\"assembly_step_failed\", step_alias, cause}\n // All three are valid InstantiateError reasons.\n return { error: output as InstantiateError };\n }\n\n return { value: output };\n}\n\nfunction writeAuditRow(deps: InstantiateDeps, contractName: string, step: ContractStep): void {\n recordContractStep(deps, {\n contract: contractName,\n verb: step.verb,\n step_alias: step.as,\n vault: deps.vault.config.name,\n });\n}\n","/**\n * describeContract — Phase 6 / CON-05 / Q-DESCRIBE.\n *\n * Pure function over `ParsedContract` returning the contract's input\n * JSON Schema + an auto-generated markdown summary. Used by the\n * `describe_contract` MCP tool so agents can discover what a contract\n * does before instantiating it.\n *\n * # Output\n *\n * { ok: true,\n * json_schema: <ParsedContract.inputJsonSchema>,\n * summary: <markdown> }\n * | { ok: false, reason: \"unknown_contract\", name }\n *\n * The summary contains the headings in RESEARCH §Q-DESCRIBE order:\n * `## Inputs`, `## Sources`, `## Sinks`, `## Assembly`, `## write_back`,\n * `## Output Shape`. Sections with no content are omitted. The\n * `Assembly` section renders steps as a numbered list — agents (and\n * humans) consume this directly without parsing the YAML.\n *\n * # Adapter-seam discipline\n *\n * Zero `fs` / `path` / `gray-matter` / `chokidar` / `yaml` imports.\n * Pure function over an in-memory ParsedContract.\n */\n\nimport type { ContractRegistry } from \"./registry.js\";\nimport type { ParsedContract } from \"./types.js\";\n\n/**\n * Plain-language gloss for each baseline assembly verb, so the rendered\n * `## Assembly` section reads as steps a non-technical user can follow —\n * not bare function names. Keyed by the 11 baseline verbs (src/contracts/\n * schema.ts BASELINE_VERBS). `literal` and `mcp://…` peer verbs fall back\n * to a generic gloss.\n */\nconst VERB_GLOSS: Record<string, string> = {\n read_note: \"Read a note's content\",\n search_hybrid: \"Search the vault (semantic + keyword)\",\n search_sections: \"Search for matching sections within notes\",\n query_frontmatter: \"Find notes by their properties (frontmatter)\",\n expand: \"Gather notes linked to the starting note (follow the graph)\",\n cluster: \"Group the gathered notes into related communities\",\n recall: \"Recall earlier agent observations from memory\",\n compile_brief: \"Compile the gathered notes into a brief\",\n get_brief: \"Fetch an already-compiled brief\",\n list_backlinks: \"List notes that link back to this one\",\n get_outline: \"Read a note's heading outline\",\n};\n\nfunction glossFor(verb: string): string {\n if (VERB_GLOSS[verb]) return VERB_GLOSS[verb]!;\n if (verb === \"literal\") return \"Use a fixed inline value\";\n if (verb.startsWith(\"mcp://\")) return `Call an external tool (${verb})`;\n return verb;\n}\n\nexport interface DescribeDeps {\n registry: ContractRegistry;\n}\n\nexport interface DescribeArgs {\n name: string;\n}\n\nexport type DescribeResult =\n | { ok: true; json_schema: object; summary: string }\n | { ok: false; reason: \"unknown_contract\"; name: string };\n\nexport function describeContract(deps: DescribeDeps, args: DescribeArgs): DescribeResult {\n const parsed = deps.registry.get(args.name);\n if (!parsed) return { ok: false, reason: \"unknown_contract\", name: args.name };\n return {\n ok: true,\n json_schema: parsed.inputJsonSchema,\n summary: renderSummary(parsed),\n };\n}\n\nfunction renderSummary(parsed: ParsedContract): string {\n const lines: string[] = [];\n lines.push(`# ${parsed.name}`);\n lines.push(\"\");\n if (parsed.description) {\n lines.push(parsed.description);\n lines.push(\"\");\n }\n\n // ## Inputs\n if (Object.keys(parsed.inputs).length > 0) {\n lines.push(\"## Inputs\");\n for (const [name, spec] of Object.entries(parsed.inputs)) {\n const s = (spec ?? {}) as Record<string, unknown>;\n const type =\n typeof s.type === \"string\"\n ? s.type\n : typeof s[\"$ref\"] === \"string\"\n ? `\\`${String(s[\"$ref\"])}\\``\n : \"any\";\n const required = parsed.required.includes(name) ? \"required\" : \"optional\";\n const desc = typeof s.description === \"string\" ? s.description : \"\";\n const descSuffix = desc ? `: ${desc}` : \"\";\n lines.push(`- **${name}** (${type}, ${required})${descSuffix}`);\n }\n lines.push(\"\");\n }\n\n // ## Sources\n if (Object.keys(parsed.sources).length > 0) {\n lines.push(\"## Sources\");\n for (const [handle, decl] of Object.entries(parsed.sources)) {\n const req = decl.required ? \"required\" : \"optional\";\n lines.push(`- **${handle}** → \\`${decl.handle}\\` (${req})`);\n }\n lines.push(\"\");\n }\n\n // ## Sinks\n if (Object.keys(parsed.sinks).length > 0) {\n lines.push(\"## Sinks\");\n for (const [handle, decl] of Object.entries(parsed.sinks)) {\n const req = decl.required ? \"required\" : \"optional\";\n lines.push(`- **${handle}** → \\`${decl.handle}\\` (${req} MemorySink)`);\n }\n lines.push(\"\");\n }\n\n // ## Assembly — rendered as plain-language steps so a non-technical user\n // can follow what the contract does, with the verb + arg keys kept inline\n // for agents/authors who want the precise call.\n if (parsed.assembly.length > 0) {\n lines.push(\"## Assembly\");\n parsed.assembly.forEach((step, i) => {\n const argsRender = step.args ? `(${Object.keys(step.args).join(\", \")})` : \"()\";\n lines.push(\n `${i + 1}. **${step.as}** — ${glossFor(step.verb)} _(\\`${step.verb}${argsRender}\\`)_`,\n );\n });\n lines.push(\"\");\n }\n\n // ## write_back\n if (parsed.write_back) {\n lines.push(\"## write_back\");\n lines.push(\n `Writes a ${parsed.write_back.document_kind} document to \\`${parsed.write_back.sink}\\` ` +\n `with body from \\`${parsed.write_back.body_from}\\`.`,\n );\n lines.push(\"\");\n }\n\n // ## Output Shape\n if (parsed.output_shape) {\n lines.push(\"## Output Shape\");\n const props = ((parsed.output_shape as { properties?: Record<string, unknown> }).properties ??\n {}) as Record<string, unknown>;\n const compact = Object.entries(props)\n .map(([k, v]) => {\n const o = (v ?? {}) as { type?: string; $ref?: string };\n const t = typeof o.type === \"string\" ? o.type : (o.$ref ?? \"any\");\n return `${k}: ${t}`;\n })\n .join(\", \");\n lines.push(`\\`{${compact}}\\``);\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\").trim() + \"\\n\";\n}\n","/**\n * Contract MCP Resources — Plan 06-04 / CON-04 + D-A2b.\n *\n * Two pure read-only Resource handlers; both registered in\n * `src/server.ts` via `server.registerResource(...)`. Resources do NOT\n * count toward the REL-08 tool budget per Phase 5 BRF-09 precedent.\n *\n * - `readListContracts(deps, opts?)` — projects the per-vault\n * `ContractRegistry` into `{total, contracts: [{name, description,\n * vault, source_count, sink_count, write_back: boolean}]}`. Optional\n * `opts.source` filters to contracts whose ANY declared source's\n * handle starts with the given prefix.\n *\n * - `readListContractVerbs(deps)` — returns\n * `{baseline: [<11 verbs>], custom: [{verb, declared_in,\n * used_by_contracts, invocation_count, last_seen}]}`. The baseline\n * set is constant (ADR-006 §Decision 3). The `custom` entries are\n * computed from `contract_audit.aggregateVerbUsage(vault)` filtered\n * to `mcp://` verbs. `used_by_contracts` is derived by scanning\n * `contract_audit.listByKind('contract_step', {vault})` for distinct\n * contract names per verb (no schema change needed).\n *\n * # Adapter-seam discipline\n *\n * Zero `fs`/`path.join`/`gray-matter`/`chokidar`/`yaml` imports — pure\n * data projection over the registry + DB query interface.\n */\n\nimport type { ContractRegistry } from \"./registry.js\";\nimport type { ContractAuditQueries } from \"../db/queries/contract-audit.js\";\n\n// ─────────────────────────────────────────────────────────────────────────\n// list_contracts (CON-04)\n// ─────────────────────────────────────────────────────────────────────────\n\nexport interface ListContractsDeps {\n registry: ContractRegistry;\n vaultName: string;\n}\n\nexport interface ListContractsOpts {\n /** Filter to contracts whose ANY source handle starts with this prefix. */\n source?: string;\n}\n\nexport interface ListContractsEntry {\n name: string;\n description: string;\n vault: string;\n source_count: number;\n sink_count: number;\n write_back: boolean;\n}\n\nexport interface ListContractsResource {\n total: number;\n contracts: ListContractsEntry[];\n}\n\nexport function readListContracts(\n deps: ListContractsDeps,\n opts: ListContractsOpts = {},\n): ListContractsResource {\n const out: ListContractsEntry[] = [];\n for (const [name, parsed] of deps.registry.entries()) {\n if (opts.source !== undefined) {\n const anyMatch = Object.values(parsed.sources).some((s) => s.handle.startsWith(opts.source!));\n if (!anyMatch) continue;\n }\n out.push({\n name,\n description: parsed.description,\n vault: deps.vaultName,\n source_count: Object.keys(parsed.sources).length,\n sink_count: Object.keys(parsed.sinks).length,\n write_back: parsed.write_back !== undefined,\n });\n }\n return { total: out.length, contracts: out };\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// list_contract_verbs (D-A2b)\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * The 11 baseline verbs per ADR-006 §Decision 3. `literal` is\n * intentionally NOT in this list — it's an escape-hatch, not a callable\n * verb usable by promotion signal aggregation.\n */\nexport const BASELINE_VERBS: readonly string[] = Object.freeze([\n \"search_hybrid\",\n \"expand\",\n \"cluster\",\n \"recall\",\n \"compile_brief\",\n \"get_brief\",\n \"query_frontmatter\",\n \"list_backlinks\",\n \"get_outline\",\n \"search_sections\",\n \"read_note\",\n]);\n\nexport interface ListContractVerbsDeps {\n contractAudit: ContractAuditQueries;\n vaultName: string;\n}\n\nexport interface ListContractVerbsEntry {\n verb: string;\n declared_in: string;\n used_by_contracts: string[];\n invocation_count: number;\n last_seen: number;\n}\n\nexport interface ListContractVerbsResource {\n baseline: readonly string[];\n custom: ListContractVerbsEntry[];\n}\n\nexport function readListContractVerbs(deps: ListContractVerbsDeps): ListContractVerbsResource {\n const usage = deps.contractAudit.aggregateVerbUsage(deps.vaultName);\n // List ALL `contract_step` rows once and reduce in-process so the\n // `used_by_contracts` join is O(N) without adding a SQL helper.\n // Larger budget than aggregateVerbUsage covers — verbs with high\n // invocation_count will appear repeatedly in the rows but we group\n // them via a Map.\n const rows = deps.contractAudit.listByKind(\"contract_step\", {\n vault: deps.vaultName,\n limit: 10_000,\n });\n const verbToContracts = new Map<string, Set<string>>();\n for (const r of rows) {\n if (r.verb === undefined || r.contract === undefined) continue;\n if (!verbToContracts.has(r.verb)) verbToContracts.set(r.verb, new Set());\n verbToContracts.get(r.verb)!.add(r.contract);\n }\n\n const custom = usage\n .filter((u) => u.verb.startsWith(\"mcp://\"))\n .map(\n (u): ListContractVerbsEntry => ({\n verb: u.verb,\n declared_in: extractDeclaredIn(u.verb),\n used_by_contracts: Array.from(verbToContracts.get(u.verb) ?? []).sort(),\n invocation_count: u.invocation_count,\n last_seen: u.last_seen,\n }),\n );\n\n return { baseline: BASELINE_VERBS, custom };\n}\n\nfunction extractDeclaredIn(verb: string): string {\n const m = verb.match(/^mcp:\\/\\/([a-z][a-z0-9_-]*)\\//);\n return m ? `[contracts.mcp_clients.${m[1]}]` : \"[contracts.mcp_clients]\";\n}\n","/**\n * Sources MCP Resources — SOURCES-REGISTRY.md §5 (Stage 2).\n *\n * Three pure read-only projections over the live `PeerMcpRegistry`.\n * Registered in `src/server.ts` via `server.registerResource(...)`.\n * Resources do NOT count toward the REL-08 tool budget (Phase 5 BRF-09\n * precedent, same as the contracts/contract-verbs resources).\n *\n * - `readListSources(reg)` — `{sources: [{name, transport, command,\n * args, status, tool_count, last_refreshed, error?}]}`. The host\n * (vault-memory itself) is NOT included — the plugin prepends it as\n * a synthetic entry. `env` is intentionally omitted (may hold\n * secrets; SOURCES-REGISTRY.md §5.1).\n *\n * - `readSourceTools(reg, name)` — `{name, status, last_refreshed,\n * tools: [...]}`. `tools` is the cached tools/list payload; `[]` when\n * the source is not connected.\n *\n * - `readSourceTool(reg, name, tool)` — a single tool's schema, inlined\n * from the cached list (no extra peer call). `{found:false}` when the\n * source or tool is unknown.\n *\n * The registry does not retain per-source config beyond what it was\n * started/added with, so `command`/`args`/`transport` are accepted as a\n * lookup map passed alongside the registry (server threads the live\n * `config.contracts.mcp_clients` plus any runtime-added entries).\n *\n * # Adapter-seam discipline\n *\n * Zero fs/path/yaml/chokidar imports — pure data projection over the\n * registry interface + a plain config map.\n */\n\nimport type { PeerMcpRegistry, PeerMcpStatus, PeerMcpTool } from \"./mcp-clients.js\";\n\n/** Connection/transport metadata for one source (config-derived). */\nexport interface SourceConfigMeta {\n command: string;\n args: readonly string[];\n}\n\nexport interface ListSourcesEntry {\n name: string;\n transport: \"stdio\";\n command: string;\n args: readonly string[];\n status: PeerMcpStatus;\n tool_count: number;\n last_refreshed: number | null;\n error?: string;\n}\n\nexport interface ListSourcesResource {\n sources: ListSourcesEntry[];\n}\n\n/**\n * Project every registered source into the list shape. `configMeta`\n * supplies command/args per source name; sources missing from the map\n * fall back to empty command/args (still listed — the registry is\n * authoritative for existence).\n */\nexport function readListSources(\n reg: PeerMcpRegistry,\n configMeta: Record<string, SourceConfigMeta>,\n): ListSourcesResource {\n const sources: ListSourcesEntry[] = [];\n for (const name of reg.names()) {\n const info = reg.getInfo(name);\n if (info === undefined) continue;\n const meta = configMeta[name];\n const entry: ListSourcesEntry = {\n name,\n transport: \"stdio\",\n command: meta?.command ?? \"\",\n args: meta?.args ?? [],\n status: info.status,\n tool_count: info.tools.length,\n last_refreshed: info.lastRefreshed,\n };\n if (info.error !== undefined) entry.error = info.error;\n sources.push(entry);\n }\n return { sources };\n}\n\nexport interface SourceToolsResource {\n name: string;\n status: PeerMcpStatus;\n last_refreshed: number | null;\n tools: readonly PeerMcpTool[];\n error?: string;\n}\n\n/** Per-source cached tools/list. `{error}` carries the unknown-source case. */\nexport function readSourceTools(\n reg: PeerMcpRegistry,\n name: string,\n): SourceToolsResource | { error: string } {\n const info = reg.getInfo(name);\n if (info === undefined) {\n return { error: `unknown source: ${name}` };\n }\n const out: SourceToolsResource = {\n name,\n status: info.status,\n last_refreshed: info.lastRefreshed,\n tools: info.tools,\n };\n if (info.error !== undefined) out.error = info.error;\n return out;\n}\n\nexport interface SourceToolResource {\n found: true;\n name: string;\n tool: PeerMcpTool;\n}\n\n/** A single tool's schema, inlined from the cache. */\nexport function readSourceTool(\n reg: PeerMcpRegistry,\n name: string,\n toolName: string,\n): SourceToolResource | { found: false; error: string } {\n const info = reg.getInfo(name);\n if (info === undefined) {\n return { found: false, error: `unknown source: ${name}` };\n }\n const tool = info.tools.find((t) => t.name === toolName);\n if (tool === undefined) {\n return { found: false, error: `unknown tool: ${name}/${toolName}` };\n }\n return { found: true, name, tool };\n}\n","/**\n * src/contracts barrel — Plans 06-01 / 06-02 / 06-03 surface.\n *\n * Plan 06-04 adds: resources (vault-memory://contract-verbs/{vault})\n * and the reference-contracts test fixtures.\n */\n\nexport type {\n AssemblyVerb,\n ContractStep,\n ContractHandleDecl,\n ContractSourceDecl,\n ContractSinkDecl,\n WriteBackSpec,\n ContractInputs,\n ParsedContract,\n OverrideMap,\n InstantiateError,\n ContractAuditRow,\n} from \"./types.js\";\nexport { CONTRACT_PATH_REGEX } from \"./types.js\";\n\nexport { TYPES_CATALOG } from \"./types-catalog.js\";\nexport { resolveRefs } from \"./json-schema-ref.js\";\nexport { buildInputSchema, type BuiltInputSchema } from \"./input-schema.js\";\nexport { ContractRegistry, type RegistrySetResult } from \"./registry.js\";\nexport { slugify } from \"./slug.js\";\nexport {\n recordContractStep,\n recordContractLoadError,\n aggregateVerbUsage,\n type ContractAuditDeps,\n type RecordContractStepArgs,\n type RecordContractLoadErrorArgs,\n type VerbUsageRow,\n} from \"./audit.js\";\nexport { ContractFileSchema, type ContractFileShape } from \"./schema.js\";\nexport {\n startContractRegistry,\n type StartContractRegistryOpts,\n type StartedContractRegistry,\n type RegistryChangeKind,\n} from \"./loader.js\";\nexport { syncAutoRegistered, type SyncAutoRegisteredOpts } from \"./auto-register.js\";\nexport { resolveTemplate, type TemplateBindings, type TemplateResolveResult } from \"./templates.js\";\nexport {\n PeerMcpRegistry,\n type PeerMcpClient,\n type PeerMcpClientConfig,\n type ClientFactory,\n} from \"./mcp-clients.js\";\nexport { verbDispatcher, type VerbDeps, type VerbDispatchOpts } from \"./verbs/index.js\";\nexport { callMcpVerb } from \"./verbs/mcp-extension.js\";\nexport {\n instantiateContract,\n type InstantiateDeps,\n type InstantiateArgs,\n type InstantiateBundle,\n type InstantiateResult,\n} from \"./instantiate.js\";\nexport {\n describeContract,\n type DescribeDeps,\n type DescribeArgs,\n type DescribeResult,\n} from \"./describe.js\";\nexport {\n readListContracts,\n readListContractVerbs,\n BASELINE_VERBS,\n type ListContractsDeps,\n type ListContractsOpts,\n type ListContractsEntry,\n type ListContractsResource,\n type ListContractVerbsDeps,\n type ListContractVerbsEntry,\n type ListContractVerbsResource,\n} from \"./resources.js\";\n\nexport {\n readListSources,\n readSourceTools,\n readSourceTool,\n type SourceConfigMeta,\n type ListSourcesEntry,\n type ListSourcesResource,\n type SourceToolsResource,\n type SourceToolResource,\n} from \"./sources-resources.js\";\n\nexport type { PeerMcpTool, PeerMcpStatus, PeerMcpClientInfo } from \"./mcp-clients.js\";\n","/**\n * Audit log + index-run reporting — user-facing layer.\n *\n * Thin wrapper over `AuditQueries` that enriches raw audit rows with\n * note-path / note-title context (best effort — null if the note has\n * been hard-deleted from the `notes` table).\n *\n * See `./README.md` for the audit + permission semantics.\n */\n\nimport type { Vault } from \"../vault/index.js\";\nimport type { ListWritesFilter } from \"../db/queries/audit.js\";\n\nconst DEFAULT_AUDIT_LIMIT = 50;\nconst MAX_AUDIT_LIMIT = 1000;\nconst DEFAULT_RUNS_LIMIT = 20;\nconst MAX_RUNS_LIMIT = 200;\n\nexport interface AuditLogEntry {\n /** Write event id (sortable, monotonically increasing). */\n id: number;\n /** Note path (relative to vault root), or null if note was hard-deleted. */\n notePath: string | null;\n /** Note title at time of write — best-effort, may be null if deleted. */\n noteTitle: string | null;\n op: \"create\" | \"update\" | \"delete\";\n previousHash: string | null;\n newHash: string | null;\n /** Hash the writer expected on disk; mismatch = conflict prevention triggered. */\n expectedHash: string | null;\n clientId: string | null;\n diffSummary: string | null;\n /** Epoch ms. */\n at: number;\n /**\n * Plan 02-06 (MEM-08): true iff this write was routed under a configured\n * MemorySink (agent observation, supersede). False for regular user writes\n * and for any audit row predating migration 009 (those rows surface as\n * `false` per the column default). Filter via the `is_memory_sink_write`\n * filter on `getAuditLog` / the `audit_log` MCP tool.\n */\n is_memory_sink_write: boolean;\n}\n\nexport interface IndexRunEntry {\n runId: string;\n vaultName: string;\n modelName: string | null;\n trigger: string;\n startedAt: number;\n finishedAt: number | null;\n durationMs: number | null;\n notesIndexed: number;\n notesUpdated: number;\n notesDeleted: number;\n chunksCreated: number;\n error: string | null;\n}\n\nexport interface GetAuditLogInput {\n vault: Vault;\n notePath?: string;\n op?: \"create\" | \"update\" | \"delete\";\n /** Epoch ms — only entries at or after this timestamp. */\n since?: number;\n limit?: number;\n /**\n * Plan 02-06 (MEM-08): when set, restricts the result to memory-sink\n * writes (`true`) or non-memory writes (`false`). When omitted, both\n * kinds are included — preserves the v1 audit_log default behavior.\n */\n is_memory_sink_write?: boolean;\n}\n\nexport interface GetIndexRunsInput {\n vault: Vault;\n limit?: number;\n}\n\nfunction clampLimit(value: number | undefined, fallback: number, max: number): number {\n if (value === undefined) return fallback;\n if (!Number.isFinite(value) || value <= 0) return fallback;\n const n = Math.floor(value);\n return n > max ? max : n;\n}\n\nexport function getAuditLog(input: GetAuditLogInput): AuditLogEntry[] {\n const { vault } = input;\n const limit = clampLimit(input.limit, DEFAULT_AUDIT_LIMIT, MAX_AUDIT_LIMIT);\n\n const filter: ListWritesFilter = { limit };\n\n if (input.notePath !== undefined) {\n const note = vault.db.notes.getByPath(input.notePath);\n if (!note) return [];\n filter.noteId = note.id;\n }\n if (input.op !== undefined) filter.op = input.op;\n if (input.since !== undefined) filter.since = input.since;\n if (input.is_memory_sink_write !== undefined) {\n filter.isMemorySinkWrite = input.is_memory_sink_write;\n }\n\n const rows = vault.db.audit.listWrites(filter);\n\n return rows.map((row): AuditLogEntry => {\n const note = vault.db.notes.getById(row.note_id);\n return {\n id: row.id,\n notePath: note?.path ?? null,\n noteTitle: note?.title ?? null,\n op: row.op,\n previousHash: row.previous_hash,\n newHash: row.new_hash,\n expectedHash: row.expected_hash,\n clientId: row.client_id,\n diffSummary: row.diff_summary,\n at: row.at,\n // SQLite returns the column as 0 | 1; convert to JS boolean at the\n // audit-layer boundary so callers (MCP audit_log + tests) see the\n // documented `is_memory_sink_write: boolean` shape.\n is_memory_sink_write: row.is_memory_sink_write === 1,\n };\n });\n}\n\nexport function getIndexRuns(input: GetIndexRunsInput): IndexRunEntry[] {\n const { vault } = input;\n const limit = clampLimit(input.limit, DEFAULT_RUNS_LIMIT, MAX_RUNS_LIMIT);\n\n const rows = vault.db.audit.listRuns(limit);\n\n return rows.map((row): IndexRunEntry => {\n let modelName: string | null = null;\n if (row.model_id !== null) {\n const all = vault.db.models.listAll();\n const found = all.find((m) => m.id === row.model_id);\n modelName = found?.name ?? null;\n }\n const durationMs = row.finished_at !== null ? row.finished_at - row.started_at : null;\n return {\n runId: row.run_id,\n vaultName: row.vault_name,\n modelName,\n trigger: row.trigger,\n startedAt: row.started_at,\n finishedAt: row.finished_at,\n durationMs,\n notesIndexed: row.notes_indexed,\n notesUpdated: row.notes_updated,\n notesDeleted: row.notes_deleted,\n chunksCreated: row.chunks_created,\n error: row.error,\n };\n });\n}\n","export { getAuditLog, getIndexRuns } from \"./audit.js\";\nexport type { AuditLogEntry, IndexRunEntry, GetAuditLogInput, GetIndexRunsInput } from \"./audit.js\";\n","/**\n * Vault-domain MCP handler factory.\n *\n * Tools: list_vaults, vault_stats, recent_notes, audit_log, list_models,\n * start_shadow_index, switch_active_model, vacuum_embeddings, index_runs.\n *\n * Extracted verbatim from the inline `handlers` literal + standalone\n * `handle*` functions in `src/server.ts`. Behavior-neutral: each arrow\n * maps the same args to the same domain call, now closing over `deps.*`\n * instead of `serve()` locals.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. SQLite access\n * is via the `vault.db` query namespaces (L0 substrate), not raw fs.\n */\n\nimport type { VaultManager } from \"../../vault/index.js\";\nimport { aggregateTopTags, aggregateTopFrontmatterKeys } from \"../utils.js\";\nimport {\n listModels,\n startShadowIndex,\n switchActiveModel,\n vacuumEmbeddings,\n} from \"../../indexer/index.js\";\nimport { getAuditLog, getIndexRuns } from \"../../audit/index.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nexport function handleListVaults(manager: VaultManager): object {\n const vaults = manager.list().map((v) => {\n const noteCount = v.db.notes.countAll();\n const runs = v.db.audit.listRuns(1);\n const lastRun = runs[0];\n return {\n name: v.config.name,\n path: v.config.path,\n embedding_model: v.config.embedding_model ?? null,\n note_count: noteCount,\n write_enabled: v.config.write_enabled ?? false,\n last_run: lastRun\n ? {\n run_id: lastRun.run_id,\n started_at: lastRun.started_at,\n finished_at: lastRun.finished_at,\n error: lastRun.error,\n }\n : null,\n };\n });\n return { vaults, count: vaults.length };\n}\n\ninterface VaultStatsRow {\n vault: string;\n vault_path: string;\n total_notes: number;\n total_words: number;\n embedding_model: string | null;\n indexed_at: number | null;\n top_tags: Array<{ tag: string; count: number }>;\n top_frontmatter_keys: Array<{ key: string; count: number }>;\n}\n\nexport function handleVaultStats(manager: VaultManager, vaultFilter: string | undefined): object {\n const targets = vaultFilter ? [manager.require(vaultFilter)] : manager.list();\n\n const stats: VaultStatsRow[] = targets.map((v) => {\n const total_notes = v.db.notes.countAll();\n const wordRow = v.db.handle\n .prepare<[], { total: number | null }>(\"SELECT SUM(word_count) AS total FROM notes\")\n .get();\n const lastRun = v.db.audit.listRuns(1)[0];\n const activeModel = v.db.models.getActive();\n\n return {\n vault: v.config.name,\n vault_path: v.config.path,\n total_notes,\n total_words: wordRow?.total ?? 0,\n embedding_model: activeModel?.name ?? v.config.embedding_model ?? null,\n indexed_at: lastRun?.finished_at ?? null,\n top_tags: aggregateTopTags(v.db.handle, 10),\n top_frontmatter_keys: aggregateTopFrontmatterKeys(v.db.handle, 10),\n };\n });\n\n if (vaultFilter) {\n // `targets` is non-empty when vaultFilter is set, because manager.require\n // throws on miss — so stats[0] is guaranteed. The assertion narrows the\n // type for the caller.\n return stats[0] as VaultStatsRow;\n }\n return { vaults: stats, count: stats.length };\n}\n\ninterface RecentNoteRow {\n vault: string;\n path: string;\n title: string | null;\n mtime: number;\n word_count: number | null;\n tags: string[] | null;\n}\n\nexport function handleRecentNotes(\n manager: VaultManager,\n vaultFilter: string | undefined,\n limit: number,\n since: number | undefined,\n): object {\n const targets = vaultFilter ? [manager.require(vaultFilter)] : manager.list();\n\n const all: RecentNoteRow[] = [];\n for (const v of targets) {\n const rows =\n since !== undefined\n ? v.db.handle\n .prepare<\n [number, number],\n {\n path: string;\n title: string | null;\n mtime: number;\n word_count: number | null;\n frontmatter: string | null;\n }\n >(\n \"SELECT path, title, mtime, word_count, frontmatter FROM notes WHERE mtime > ? ORDER BY mtime DESC LIMIT ?\",\n )\n .all(since, limit)\n : v.db.handle\n .prepare<\n [number],\n {\n path: string;\n title: string | null;\n mtime: number;\n word_count: number | null;\n frontmatter: string | null;\n }\n >(\n \"SELECT path, title, mtime, word_count, frontmatter FROM notes ORDER BY mtime DESC LIMIT ?\",\n )\n .all(limit);\n\n for (const r of rows) {\n let tags: string[] | null = null;\n if (r.frontmatter) {\n try {\n const fm = JSON.parse(r.frontmatter) as { tags?: unknown };\n if (Array.isArray(fm.tags)) {\n tags = fm.tags.filter((t): t is string => typeof t === \"string\");\n }\n } catch {\n // ignore\n }\n }\n all.push({\n vault: v.config.name,\n path: r.path,\n title: r.title,\n mtime: r.mtime,\n word_count: r.word_count,\n tags,\n });\n }\n }\n\n // Cross-vault merge: re-sort by mtime and trim.\n all.sort((a, b) => b.mtime - a.mtime);\n return { notes: all.slice(0, limit), count: Math.min(all.length, limit) };\n}\n\nexport function makeVaultHandlers(deps: HandlerDeps): Partial<Record<ToolName, Handler>> {\n const { manager, ollama } = deps;\n return {\n list_vaults: async () => handleListVaults(manager),\n vault_stats: async (a) => {\n const p = a as { vault?: string };\n return handleVaultStats(manager, p.vault);\n },\n recent_notes: async (a) => {\n const p = a as { vault?: string; limit: number; since?: number };\n return handleRecentNotes(manager, p.vault, p.limit, p.since);\n },\n audit_log: async (a) => {\n const p = a as {\n vault: string;\n note_path?: string;\n op?: \"create\" | \"update\" | \"delete\";\n since?: number;\n limit: number;\n is_memory_sink_write?: boolean;\n };\n const vault = manager.require(p.vault);\n // Plan 02-06 (MEM-08): the new optional filter is purely additive.\n // Omitting it preserves Phase 1 behavior (include all rows).\n const entries = getAuditLog({\n vault,\n notePath: p.note_path,\n op: p.op,\n since: p.since,\n limit: p.limit,\n ...(p.is_memory_sink_write !== undefined\n ? { is_memory_sink_write: p.is_memory_sink_write }\n : {}),\n });\n return { entries, count: entries.length };\n },\n list_models: async (a) => {\n const p = a as { vault: string };\n const vault = manager.require(p.vault);\n const models = listModels(vault);\n return { models, count: models.length };\n },\n start_shadow_index: async (a) => {\n const p = a as { vault: string; model: string; batch_size?: number };\n const vault = manager.require(p.vault);\n return startShadowIndex({\n vault,\n model: p.model,\n ollama,\n batchSize: p.batch_size,\n log: (m) => process.stderr.write(`[shadow:${vault.config.name}] ${m}\\n`),\n });\n },\n switch_active_model: async (a) => {\n const p = a as { vault: string; model_name: string };\n const vault = manager.require(p.vault);\n return switchActiveModel(vault, p.model_name);\n },\n vacuum_embeddings: async (a) => {\n const p = a as { vault: string };\n const vault = manager.require(p.vault);\n return vacuumEmbeddings(vault);\n },\n index_runs: async (a) => {\n const p = a as { vault: string; limit: number };\n const vault = manager.require(p.vault);\n const runs = getIndexRuns({ vault, limit: p.limit });\n return { runs, count: runs.length };\n },\n };\n}\n","/**\n * Folder-convention learner.\n *\n * For a given vault-relative path, gather frontmatter conventions from\n * sibling notes (same folder prefix). The learner emits per-key:\n * - presence prevalence (how many sibling notes have the key)\n * - dominant value (if any single value covers >50% of populated notes)\n *\n * SQL-only — no embeddings, no LLM. Fast.\n *\n * Fallback: when the immediate folder has <3 sibling notes, the learner\n * walks UP one path segment (e.g. `Intelligence Impact/INIM-BDEV/Meetings/`\n * falls back to `Intelligence Impact/INIM-BDEV/`) until it finds enough\n * siblings OR reaches the vault root. This prevents the \"single note in a\n * new folder gets no suggestions\" failure mode.\n */\n\nimport type { Vault } from \"../vault/index.js\";\n\n/**\n * A single per-key inference result from the folder layer.\n */\nexport interface FolderConventionEntry {\n /** Frontmatter key name (e.g. \"class\", \"status\", \"tags\"). */\n key: string;\n /** Number of sibling notes (in the resolved folder) that have this key. */\n presenceCount: number;\n /** Total sibling notes in the resolved folder (denominator). */\n siblingCount: number;\n /** Presence ratio: presenceCount / siblingCount. */\n prevalence: number;\n /**\n * If a single value covers >50% of notes-with-this-key, the dominant\n * value. Otherwise null (split inference — no value, just the key).\n * Stored as JSON-typed: string, number, boolean, or array of strings\n * for the `tags` case.\n */\n dominantValue: unknown | null;\n /** Coverage of the dominant value among notes-with-this-key. */\n dominantValueRatio: number;\n}\n\n/**\n * The resolved folder used for the inference, plus the entries.\n * `resolvedFolder` may not be the original note's immediate folder —\n * see fallback rules above.\n */\nexport interface FolderConventionResult {\n resolvedFolder: string;\n siblingCount: number;\n fellBackFrom: string | null;\n entries: FolderConventionEntry[];\n}\n\n/** Minimum sibling notes required before we trust folder inference. */\nconst MIN_SIBLINGS = 3;\n\n/** Maximum levels to walk up before giving up. */\nconst MAX_FALLBACK_LEVELS = 4;\n\n/**\n * Resolve the folder for a given vault-relative note path.\n *\n * - `Personen/Joerg.md` → `Personen/`\n * - `Intelligence Impact/INIM-BDEV/Meetings/2026-05-12.md`\n * → `Intelligence Impact/INIM-BDEV/Meetings/`\n * - `note-at-root.md` → `\"\"` (the vault root)\n */\nexport function folderOf(notePath: string): string {\n const idx = notePath.lastIndexOf(\"/\");\n return idx === -1 ? \"\" : notePath.slice(0, idx + 1);\n}\n\n/**\n * Walk up one folder level. `Foo/Bar/` → `Foo/`. `Foo/` → `\"\"`. `\"\"` → null.\n */\nfunction parentFolder(folder: string): string | null {\n if (folder === \"\") return null;\n const trimmed = folder.endsWith(\"/\") ? folder.slice(0, -1) : folder;\n const idx = trimmed.lastIndexOf(\"/\");\n if (idx === -1) return \"\";\n return trimmed.slice(0, idx + 1);\n}\n\ninterface SiblingRow {\n path: string;\n frontmatter: string | null;\n}\n\n/**\n * Count sibling notes (any path starting with `folder`, excluding the\n * input note itself when applicable). Empty folder string means vault root.\n */\nfunction countSiblings(vault: Vault, folder: string, excludePath: string | null): number {\n const handle = vault.db.handle;\n if (folder === \"\") {\n // Vault root: notes with no `/` in path. The simplest reliable filter.\n const row = handle\n .prepare<\n [string | null],\n { c: number }\n >(\"SELECT COUNT(*) AS c FROM notes WHERE instr(path, '/') = 0 AND path != COALESCE(?, '')\")\n .get(excludePath);\n return row?.c ?? 0;\n }\n const row = handle\n .prepare<\n [string, string | null],\n { c: number }\n >(\"SELECT COUNT(*) AS c FROM notes WHERE path LIKE ? || '%' AND path != COALESCE(?, '')\")\n .get(folder, excludePath);\n return row?.c ?? 0;\n}\n\nfunction fetchSiblings(vault: Vault, folder: string, excludePath: string | null): SiblingRow[] {\n const handle = vault.db.handle;\n if (folder === \"\") {\n return handle\n .prepare<\n [string | null],\n SiblingRow\n >(\"SELECT path, frontmatter FROM notes WHERE instr(path, '/') = 0 AND path != COALESCE(?, '')\")\n .all(excludePath);\n }\n return handle\n .prepare<\n [string, string | null],\n SiblingRow\n >(\"SELECT path, frontmatter FROM notes WHERE path LIKE ? || '%' AND path != COALESCE(?, '')\")\n .all(folder, excludePath);\n}\n\n/**\n * Resolve the folder for inference, walking up if too few siblings.\n * Returns the chosen folder and the original folder (if different).\n */\nexport function resolveInferenceFolder(\n vault: Vault,\n notePath: string,\n excludePath: string | null = notePath,\n): { folder: string; fellBackFrom: string | null; siblingCount: number } {\n const start = folderOf(notePath);\n let current: string | null = start;\n let levels = 0;\n while (current !== null && levels < MAX_FALLBACK_LEVELS) {\n const count = countSiblings(vault, current, excludePath);\n if (count >= MIN_SIBLINGS || current === \"\") {\n return {\n folder: current,\n fellBackFrom: current === start ? null : start,\n siblingCount: count,\n };\n }\n current = parentFolder(current);\n levels++;\n }\n return { folder: \"\", fellBackFrom: start, siblingCount: 0 };\n}\n\n/**\n * Aggregate frontmatter keys + dominant values across a set of sibling rows.\n *\n * We tolerate dirty frontmatter (parse failures, primitives, nulls) without\n * crashing — same defensive posture as the v0.9.0 vault_stats aggregates.\n */\nfunction aggregateEntries(siblings: SiblingRow[]): FolderConventionEntry[] {\n const total = siblings.length;\n if (total === 0) return [];\n\n // For each key: count occurrences + collect values seen.\n const keyPresence = new Map<string, number>();\n const keyValues = new Map<string, Map<string, number>>();\n\n for (const row of siblings) {\n if (!row.frontmatter) continue;\n let fm: unknown;\n try {\n fm = JSON.parse(row.frontmatter);\n } catch {\n continue;\n }\n if (!fm || typeof fm !== \"object\" || Array.isArray(fm)) continue;\n\n const obj = fm as Record<string, unknown>;\n for (const [key, value] of Object.entries(obj)) {\n keyPresence.set(key, (keyPresence.get(key) ?? 0) + 1);\n // Normalize the value to a comparable string for the dominant-value\n // bucket. Arrays and objects get a deterministic JSON form so e.g.\n // `tags: [\"a\",\"b\"]` collides only with itself.\n const valKey = stableStringify(value);\n if (!keyValues.has(key)) keyValues.set(key, new Map());\n const bucket = keyValues.get(key)!;\n bucket.set(valKey, (bucket.get(valKey) ?? 0) + 1);\n }\n }\n\n const entries: FolderConventionEntry[] = [];\n for (const [key, presenceCount] of keyPresence) {\n const valueBucket = keyValues.get(key)!;\n const [domValStr, domCount] = pickDominant(valueBucket);\n const dominantValue = domCount / presenceCount > 0.5 ? safeParse(domValStr) : null;\n entries.push({\n key,\n presenceCount,\n siblingCount: total,\n prevalence: presenceCount / total,\n dominantValue,\n dominantValueRatio: domCount / presenceCount,\n });\n }\n\n // Sort by prevalence DESC, then key ASC for stable output.\n entries.sort((a, b) => {\n if (b.prevalence !== a.prevalence) return b.prevalence - a.prevalence;\n return a.key.localeCompare(b.key);\n });\n return entries;\n}\n\nfunction pickDominant(bucket: Map<string, number>): [string, number] {\n let bestKey = \"\";\n let bestCount = 0;\n for (const [k, c] of bucket) {\n if (c > bestCount) {\n bestKey = k;\n bestCount = c;\n }\n }\n return [bestKey, bestCount];\n}\n\nfunction stableStringify(v: unknown): string {\n if (v === undefined) return \"null\";\n return JSON.stringify(v, Object.keys((v as object) ?? {}).sort());\n}\n\nfunction safeParse(s: string): unknown {\n try {\n return JSON.parse(s);\n } catch {\n return null;\n }\n}\n\n/**\n * Primary entry point. Returns folder-based frontmatter convention for\n * the input note (which may or may not yet exist in the DB — the path is\n * what matters).\n *\n * Pass `excludePath: null` when inferring for a brand-new note that isn't\n * indexed yet (so no sibling is wrongly skipped).\n */\nexport function inferFromFolder(\n vault: Vault,\n notePath: string,\n options: { excludePath?: string | null } = {},\n): FolderConventionResult {\n const excludePath = options.excludePath ?? notePath;\n const { folder, fellBackFrom, siblingCount } = resolveInferenceFolder(\n vault,\n notePath,\n excludePath,\n );\n const siblings = fetchSiblings(vault, folder, excludePath);\n return {\n resolvedFolder: folder,\n siblingCount,\n fellBackFrom,\n entries: aggregateEntries(siblings),\n };\n}\n","/**\n * Neighbor-based frontmatter inference.\n *\n * For a given note path, gather frontmatter conventions from the notes\n * directly linked to it — forward (notes this one points TO) and\n * backward (notes that link TO this one).\n *\n * Why this works: in a curated vault, a note's wikilink-neighborhood\n * carries semantic context that the folder may not. Example: a meeting\n * note `2026-05-12 Sondierung.md` links to `[[Jörg]]` (Person) and\n * `[[INIM-BDEV]]` (Project) — the link-cluster of typical \"meeting\"\n * notes will look the same.\n *\n * The neighbor learner is weaker than folder-conventions (more indirect)\n * but rescues cases where folder structure is shallow or unconvention'd.\n */\n\nimport type { Vault } from \"../vault/index.js\";\n\nexport interface NeighborInferenceEntry {\n /** Frontmatter key seen in neighbors. */\n key: string;\n /** Number of neighbors that have the key. */\n neighborCount: number;\n /** Total neighbors considered (denominator). */\n totalNeighbors: number;\n /** Presence ratio. */\n prevalence: number;\n /** Dominant value across neighbors-with-this-key, if any. */\n dominantValue: unknown | null;\n /** Coverage of the dominant value. */\n dominantValueRatio: number;\n}\n\nexport interface NeighborInferenceResult {\n /** Number of forward links resolved to existing notes. */\n forwardCount: number;\n /** Number of backlinks. */\n backwardCount: number;\n /** Combined unique neighbor count (denominator for prevalence). */\n totalNeighbors: number;\n entries: NeighborInferenceEntry[];\n}\n\ninterface NeighborRow {\n path: string;\n frontmatter: string | null;\n}\n\n/**\n * Gather all neighbor notes (forward + backward links), deduplicated by\n * note id.\n *\n * For a note that does not yet exist in the DB (brand-new), backlinks\n * cannot be computed (nothing links to it yet). Only forward-links from\n * the parsed content can contribute — but parsing happens upstream.\n * In that case the caller passes the parsed wikilinks directly via\n * `additionalForwardTargets`.\n */\nfunction gatherNeighbors(\n vault: Vault,\n notePath: string,\n additionalForwardTargets: string[] = [],\n): NeighborRow[] {\n const seenIds = new Set<number>();\n const out: NeighborRow[] = [];\n\n const note = vault.db.notes.getByPath(notePath);\n\n // Backward: who links to this note's path (only meaningful if the note\n // exists in DB; backlinks reference target_note id OR a target_path\n // for unresolved links).\n if (note) {\n const back = vault.db.wikilinks.getBacklinks(note.id);\n for (const row of back) {\n if (seenIds.has(row.sourceNoteId)) continue;\n const src = vault.db.notes.getById(row.sourceNoteId);\n if (!src) continue;\n seenIds.add(src.id);\n out.push({ path: src.path, frontmatter: src.frontmatter });\n }\n\n // Forward: links this note has (already in DB).\n const forward = vault.db.wikilinks.getForwardLinks(note.id);\n for (const row of forward) {\n if (row.targetNoteId === null) continue;\n if (seenIds.has(row.targetNoteId)) continue;\n const target = vault.db.notes.getById(row.targetNoteId);\n if (!target) continue;\n seenIds.add(target.id);\n out.push({ path: target.path, frontmatter: target.frontmatter });\n }\n }\n\n // Fallback / new-note path: caller-supplied wikilink targets resolved\n // via path lookup. These are unresolved-link strings from parser\n // (e.g. \"Personen/Jörg\" — no .md).\n for (const target of additionalForwardTargets) {\n const candidate = vault.db.notes.getByPath(`${target}.md`) ?? vault.db.notes.getByPath(target);\n if (!candidate) continue;\n if (seenIds.has(candidate.id)) continue;\n seenIds.add(candidate.id);\n out.push({ path: candidate.path, frontmatter: candidate.frontmatter });\n }\n\n return out;\n}\n\nfunction aggregateEntries(neighbors: NeighborRow[]): NeighborInferenceEntry[] {\n const total = neighbors.length;\n if (total === 0) return [];\n\n const keyPresence = new Map<string, number>();\n const keyValues = new Map<string, Map<string, number>>();\n\n for (const row of neighbors) {\n if (!row.frontmatter) continue;\n let fm: unknown;\n try {\n fm = JSON.parse(row.frontmatter);\n } catch {\n continue;\n }\n if (!fm || typeof fm !== \"object\" || Array.isArray(fm)) continue;\n\n const obj = fm as Record<string, unknown>;\n for (const [key, value] of Object.entries(obj)) {\n keyPresence.set(key, (keyPresence.get(key) ?? 0) + 1);\n const valKey = JSON.stringify(value, Object.keys((value as object) ?? {}).sort());\n if (!keyValues.has(key)) keyValues.set(key, new Map());\n const bucket = keyValues.get(key)!;\n bucket.set(valKey, (bucket.get(valKey) ?? 0) + 1);\n }\n }\n\n const entries: NeighborInferenceEntry[] = [];\n for (const [key, presenceCount] of keyPresence) {\n const valueBucket = keyValues.get(key)!;\n let bestKey = \"\";\n let bestCount = 0;\n for (const [k, c] of valueBucket) {\n if (c > bestCount) {\n bestKey = k;\n bestCount = c;\n }\n }\n const dominantValue = bestCount / presenceCount > 0.5 ? safeParse(bestKey) : null;\n entries.push({\n key,\n neighborCount: presenceCount,\n totalNeighbors: total,\n prevalence: presenceCount / total,\n dominantValue,\n dominantValueRatio: bestCount / presenceCount,\n });\n }\n\n entries.sort((a, b) => {\n if (b.prevalence !== a.prevalence) return b.prevalence - a.prevalence;\n return a.key.localeCompare(b.key);\n });\n return entries;\n}\n\nfunction safeParse(s: string): unknown {\n try {\n return JSON.parse(s);\n } catch {\n return null;\n }\n}\n\n/**\n * Primary entry point. For a note path, returns the frontmatter\n * conventions visible across its linked neighbors.\n *\n * `additionalForwardTargets`: vault-relative paths (without `.md`) for\n * wikilinks that haven't been indexed yet — typically passed by the\n * tool handler when the input is a draft content blob rather than an\n * indexed note.\n */\nexport function inferFromNeighbors(\n vault: Vault,\n notePath: string,\n additionalForwardTargets: string[] = [],\n): NeighborInferenceResult {\n const neighbors = gatherNeighbors(vault, notePath, additionalForwardTargets);\n\n // Approximate forward/backward split — not strictly needed for the\n // aggregate, but useful in the tool response so the agent can see\n // where the signal came from.\n const note = vault.db.notes.getByPath(notePath);\n let forwardCount = 0;\n let backwardCount = 0;\n if (note) {\n forwardCount = vault.db.wikilinks\n .getForwardLinks(note.id)\n .filter((r) => r.targetNoteId !== null).length;\n backwardCount = vault.db.wikilinks.getBacklinks(note.id).length;\n }\n\n return {\n forwardCount,\n backwardCount,\n totalNeighbors: neighbors.length,\n entries: aggregateEntries(neighbors),\n };\n}\n","/**\n * Content-based heuristic inference.\n *\n * A set of vault-agnostic Title/Body pattern matchers. Each rule emits\n * suggested frontmatter when the input note matches its pattern. Rules\n * are intentionally narrow and self-explanatory — the user (or agent)\n * should be able to read the rule list and predict what will be inferred.\n *\n * Confidence is fixed per rule. Multiple rules CAN match (e.g. a meeting\n * note that mentions a person) — the resolver upstream combines them.\n *\n * No LLM, no embeddings. Pure deterministic RegEx + string scanning.\n */\n\nexport interface ContentHeuristicEntry {\n /** Frontmatter key the rule contributes (e.g. \"class\", \"type\"). */\n key: string;\n /** Suggested value. */\n value: unknown;\n /** Fixed confidence per rule (0..1). */\n confidence: number;\n /** Which rule fired, for transparency in the tool response. */\n rule: string;\n}\n\nexport interface ContentHeuristicResult {\n entries: ContentHeuristicEntry[];\n /** Rule names that matched (for the agent's debugging). */\n matchedRules: string[];\n}\n\ninterface HeuristicRule {\n name: string;\n /**\n * Returns the suggested entries when this rule matches; empty array\n * means the rule did not fire.\n */\n match: (input: HeuristicInput) => Omit<ContentHeuristicEntry, \"rule\">[];\n}\n\ninterface HeuristicInput {\n title: string;\n bodyHead: string; // first ~2000 chars of body\n fullBody: string;\n}\n\nconst DEFAULT_CONFIDENCE = 0.7;\nconst STRONG_CONFIDENCE = 0.85;\nconst WEAK_CONFIDENCE = 0.5;\n\n/**\n * Email — matches Title-like \"E-Mail von X\", \"Mail von X\", \"Email from X\",\n * OR a body starting with \"From:\" / \"Von:\" header (forwarded mail style).\n */\nconst emailRule: HeuristicRule = {\n name: \"email-title-or-header\",\n match: ({ title, bodyHead }) => {\n const titleMatch =\n /^(E-?Mail|Email|Mail)\\s+(von|from)\\s+\\S+/i.test(title) || /^(Re|Fwd|AW|WG):\\s/i.test(title);\n const headerMatch = /^(From|Von):\\s+\\S+/im.test(bodyHead) && /^(To|An):\\s+\\S+/im.test(bodyHead);\n if (!titleMatch && !headerMatch) return [];\n return [\n { key: \"class\", value: \"Email\", confidence: STRONG_CONFIDENCE },\n { key: \"type\", value: \"email\", confidence: STRONG_CONFIDENCE },\n ];\n },\n};\n\n/**\n * Meeting — multi-language: Meeting, Treffen, Call, Sondierung, Termin,\n * Standup, Sync. Title-leading keyword OR a YYYY-MM-DD prefix + such a\n * keyword.\n */\nconst meetingRule: HeuristicRule = {\n name: \"meeting-title-keyword\",\n match: ({ title, bodyHead }) => {\n const keywords =\n /\\b(Meeting|Treffen|Call|Sondierung|Termin|Standup|Sync|Kickoff|Kick-off|Jour\\s*fixe|Workshop)\\b/i;\n const isMeeting =\n keywords.test(title) ||\n /^\\d{4}-\\d{2}-\\d{2}.*\\b(Meeting|Treffen|Call|Sondierung)/i.test(title);\n if (!isMeeting) return [];\n // Many meeting notes have an \"Attendees:\" / \"Teilnehmer:\" line — bump\n // confidence when we see one.\n const attendeesPresent = /^(Attendees|Teilnehmer|Participants):/im.test(bodyHead);\n const conf = attendeesPresent ? STRONG_CONFIDENCE : DEFAULT_CONFIDENCE;\n return [\n { key: \"class\", value: \"Meeting\", confidence: conf },\n { key: \"type\", value: \"meeting\", confidence: conf },\n ];\n },\n};\n\n/**\n * Person — short title that looks like a personal name (1-4 capitalized\n * tokens), AND body mentions LinkedIn URL, an email address with the\n * person's name, or a phone-number pattern.\n *\n * Deliberately narrow: many notes have person names in titles (e.g.\n * meeting notes) — we require corroborating signals from the body.\n */\nconst personRule: HeuristicRule = {\n name: \"person-name-title-with-corroboration\",\n match: ({ title, bodyHead }) => {\n const nameLike = /^[A-ZÄÖÜ][a-zäöüß'\\-]+( [A-ZÄÖÜ][a-zäöüß'\\-]+){0,3}$/.test(title.trim());\n if (!nameLike) return [];\n const corroborating =\n /linkedin\\.com\\/in\\//i.test(bodyHead) ||\n /\\b[\\w._-]+@[\\w.-]+\\.[a-z]{2,}\\b/i.test(bodyHead) ||\n /\\+?\\d[\\d\\s\\-./()]{6,}/.test(bodyHead);\n if (!corroborating) return [];\n return [\n { key: \"class\", value: \"Person\", confidence: STRONG_CONFIDENCE },\n { key: \"type\", value: \"person\", confidence: STRONG_CONFIDENCE },\n { key: \"participation\", value: [], confidence: WEAK_CONFIDENCE },\n ];\n },\n};\n\n/**\n * Reading note / clipping — body starts with a markdown link to a URL\n * (common Obsidian Web Clipper format), or has a `source:` URL in the\n * first ~500 chars.\n */\nconst clippingRule: HeuristicRule = {\n name: \"clipping-source-url\",\n match: ({ bodyHead }) => {\n const headSnippet = bodyHead.slice(0, 500);\n const hasMdLink = /^\\s*\\[.+\\]\\(https?:\\/\\/[^\\s)]+\\)/m.test(headSnippet);\n const hasSourceField = /^source:\\s*https?:\\/\\//im.test(headSnippet);\n if (!hasMdLink && !hasSourceField) return [];\n return [\n { key: \"class\", value: \"Clipping\", confidence: DEFAULT_CONFIDENCE },\n { key: \"tags\", value: [\"clippings\"], confidence: DEFAULT_CONFIDENCE },\n ];\n },\n};\n\n/**\n * Fact / short-status — very short body (<150 chars), one-line subject,\n * looks like a captured fact or status update.\n *\n * Confidence intentionally low — many short notes are not facts but\n * fragments, drafts, etc.\n */\nconst factRule: HeuristicRule = {\n name: \"short-fact\",\n match: ({ fullBody }) => {\n const trimmed = fullBody.trim();\n if (trimmed.length === 0 || trimmed.length > 150) return [];\n // Reject if it contains multiple paragraphs (likely fragment, not fact).\n if (/\\n\\s*\\n/.test(trimmed)) return [];\n return [{ key: \"class\", value: \"Fact\", confidence: WEAK_CONFIDENCE }];\n },\n};\n\n/**\n * Date prefix → `created` and (for date-prefixed names) `meeting_date`.\n * Common Obsidian convention.\n */\nconst dateInTitleRule: HeuristicRule = {\n name: \"date-prefix-in-title\",\n match: ({ title }) => {\n const m = title.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (!m) return [];\n const iso = `${m[1]}-${m[2]}-${m[3]}`;\n return [{ key: \"created\", value: iso, confidence: STRONG_CONFIDENCE }];\n },\n};\n\nconst RULES: readonly HeuristicRule[] = [\n emailRule,\n meetingRule,\n personRule,\n clippingRule,\n factRule,\n dateInTitleRule,\n];\n\n/**\n * Run all rules against the input note. Multiple rules CAN fire (e.g.\n * a date-prefix meeting note matches both `dateInTitleRule` and\n * `meetingRule`). The combiner handles cross-rule conflicts upstream;\n * here we just emit every match.\n */\nexport function inferFromContent(input: { title: string; body: string }): ContentHeuristicResult {\n const heuristicInput: HeuristicInput = {\n title: input.title,\n bodyHead: input.body.slice(0, 2000),\n fullBody: input.body,\n };\n\n const entries: ContentHeuristicEntry[] = [];\n const matchedRules: string[] = [];\n\n for (const rule of RULES) {\n const matches = rule.match(heuristicInput);\n if (matches.length > 0) {\n matchedRules.push(rule.name);\n for (const m of matches) {\n entries.push({ ...m, rule: rule.name });\n }\n }\n }\n\n return { entries, matchedRules };\n}\n","/**\n * Combiner — fuses folder-conventions, neighbor-inference, and content-\n * heuristics into a single structured suggestion bundle.\n *\n * Output shape (per the v0.10.0 contract):\n *\n * {\n * existing: [...], // keys already present in the note's frontmatter\n * suggestions: [...], // new keys with one agreed value (highest confidence)\n * conflicts: [...] // keys where sources disagree (existing or new)\n * }\n *\n * Confidence calibration per source:\n * - folder: raw prevalence (already in [0, 1])\n * - neighbor: prevalence × 0.6 (dampened — indirect signal)\n * - content: fixed per rule (0.5 / 0.7 / 0.85 → see content-heuristics)\n */\n\nimport type { Vault } from \"../vault/index.js\";\nimport { inferFromFolder, type FolderConventionResult } from \"./folder-conventions.js\";\nimport { inferFromNeighbors, type NeighborInferenceResult } from \"./neighbor-inference.js\";\nimport { inferFromContent, type ContentHeuristicResult } from \"./content-heuristics.js\";\n\nconst NEIGHBOR_DAMPING = 0.6;\nconst MIN_PRESENTATION_CONFIDENCE = 0.2;\n\nexport type SourceTag = \"folder\" | \"neighbor\" | \"content\";\n\nexport interface FrontmatterExisting {\n key: string;\n value: unknown;\n}\n\nexport interface FrontmatterSuggestion {\n key: string;\n /** Suggested value. `null` means \"key only, no concrete value\" — agent\n * should ask the user to fill it in. */\n suggestedValue: unknown | null;\n /** Combined confidence (max across sources that agreed). */\n confidence: number;\n /** Which sources contributed (in order of confidence DESC). */\n sources: SourceTag[];\n /**\n * Optional rule name for content-heuristics matches (helps the user\n * understand why something was suggested).\n */\n rule?: string;\n}\n\nexport interface FrontmatterConflict {\n key: string;\n /**\n * Each candidate value, with its source and confidence. The agent (or\n * user) picks one explicitly.\n */\n candidates: Array<{\n value: unknown;\n source: SourceTag | \"existing\";\n confidence: number;\n rule?: string;\n }>;\n}\n\nexport interface SuggestFrontmatterInput {\n vault: Vault;\n /**\n * The note's vault-relative path. May NOT yet exist in the DB — the\n * folder learner uses the path prefix, the neighbor learner uses\n * additionalForwardTargets (parsed from content).\n */\n path: string;\n /** Optional existing frontmatter on the note. Used for the `existing`\n * classification and conflict detection. */\n existingFrontmatter?: Record<string, unknown> | null;\n /** Optional content for the heuristics layer. If omitted, the layer\n * is skipped (only folder + neighbor remain). */\n content?: string;\n /** Title (for content-heuristics). Falls back to the basename. */\n title?: string;\n /** Wikilink targets parsed from the (possibly draft) content. Used by\n * neighbor-inference when the note isn't indexed yet. */\n draftWikilinkTargets?: string[];\n /** Optional path to exclude from folder inference. Defaults to `path`. */\n excludePath?: string | null;\n}\n\nexport interface SuggestFrontmatterResult {\n /** Keys already present in the note's frontmatter (no conflict). */\n existing: FrontmatterExisting[];\n /** New (or value-clarifying) suggestions, sorted by confidence DESC. */\n suggestions: FrontmatterSuggestion[];\n /** Disagreements between sources, or existing-vs-suggestion mismatches. */\n conflicts: FrontmatterConflict[];\n /** Diagnostic info — useful when the agent wants to explain the result. */\n diagnostics: {\n folder: FolderConventionResult;\n neighbor: NeighborInferenceResult;\n content: ContentHeuristicResult;\n };\n}\n\ninterface Candidate {\n source: SourceTag;\n value: unknown | null;\n confidence: number;\n rule?: string;\n}\n\n/**\n * Stable canonical string for value comparison. Arrays preserved in order;\n * objects key-sorted. Mirrors the canonical-JSON convention used elsewhere\n * in the codebase (reader/hash.ts) for the same reason: equality must be\n * robust to JS object-property order quirks.\n */\nfunction valueKey(v: unknown): string {\n if (v === null || v === undefined) return \"null\";\n if (Array.isArray(v)) {\n return \"[\" + v.map(valueKey).join(\",\") + \"]\";\n }\n if (typeof v === \"object\") {\n const obj = v as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return \"{\" + keys.map((k) => JSON.stringify(k) + \":\" + valueKey(obj[k])).join(\",\") + \"}\";\n }\n return JSON.stringify(v);\n}\n\n/**\n * Core orchestration entrypoint. Runs all three learners and combines\n * their output into the structured response.\n */\nexport function suggestFrontmatter(input: SuggestFrontmatterInput): SuggestFrontmatterResult {\n const title = input.title ?? defaultTitleFromPath(input.path);\n\n const folder = inferFromFolder(input.vault, input.path, {\n excludePath: input.excludePath ?? input.path,\n });\n const neighbor = inferFromNeighbors(input.vault, input.path, input.draftWikilinkTargets ?? []);\n const content =\n input.content !== undefined\n ? inferFromContent({ title, body: input.content })\n : { entries: [], matchedRules: [] };\n\n return combineSuggestions({\n existingFrontmatter: input.existingFrontmatter ?? null,\n folder,\n neighbor,\n content,\n });\n}\n\nfunction defaultTitleFromPath(path: string): string {\n const base = path.split(\"/\").pop() ?? path;\n return base.replace(/\\.md$/i, \"\");\n}\n\n/**\n * Pure combiner — exposed separately so unit tests can construct\n * synthetic inputs without spinning up a vault.\n */\nexport function combineSuggestions(args: {\n existingFrontmatter: Record<string, unknown> | null;\n folder: FolderConventionResult;\n neighbor: NeighborInferenceResult;\n content: ContentHeuristicResult;\n}): SuggestFrontmatterResult {\n const { existingFrontmatter, folder, neighbor, content } = args;\n\n // Build a `key -> candidates[]` map from the three sources.\n const candidates = new Map<string, Candidate[]>();\n\n const push = (key: string, c: Candidate): void => {\n if (!candidates.has(key)) candidates.set(key, []);\n candidates.get(key)!.push(c);\n };\n\n // Folder layer.\n for (const e of folder.entries) {\n if (e.prevalence < MIN_PRESENTATION_CONFIDENCE) continue;\n push(e.key, {\n source: \"folder\",\n value: e.dominantValue,\n confidence: e.prevalence,\n });\n }\n\n // Neighbor layer (dampened).\n for (const e of neighbor.entries) {\n const conf = e.prevalence * NEIGHBOR_DAMPING;\n if (conf < MIN_PRESENTATION_CONFIDENCE) continue;\n push(e.key, {\n source: \"neighbor\",\n value: e.dominantValue,\n confidence: conf,\n });\n }\n\n // Content layer.\n for (const e of content.entries) {\n push(e.key, {\n source: \"content\",\n value: e.value,\n confidence: e.confidence,\n rule: e.rule,\n });\n }\n\n const existing: FrontmatterExisting[] = [];\n const suggestions: FrontmatterSuggestion[] = [];\n const conflicts: FrontmatterConflict[] = [];\n\n const fm = existingFrontmatter ?? {};\n const existingKeys = new Set(Object.keys(fm));\n\n // Process each key from candidates + every existing key (so existing\n // keys that no source touched still land in `existing`).\n const allKeys = new Set<string>([...candidates.keys(), ...existingKeys]);\n\n for (const key of allKeys) {\n const cands = candidates.get(key) ?? [];\n const existingValue = existingKeys.has(key) ? fm[key] : undefined;\n const hasExisting = existingValue !== undefined;\n const existingValueKey = hasExisting ? valueKey(existingValue) : null;\n\n // Group candidates by value-key to find disagreement and combine\n // confidence within an agreed value.\n const byValue = new Map<string, Candidate[]>();\n for (const c of cands) {\n if (c.value === null) {\n // Null value means \"key only\". Bucket separately so it doesn't\n // collide with a concrete-value candidate.\n const k = \"__keyonly__\";\n if (!byValue.has(k)) byValue.set(k, []);\n byValue.get(k)!.push(c);\n } else {\n const k = valueKey(c.value);\n if (!byValue.has(k)) byValue.set(k, []);\n byValue.get(k)!.push(c);\n }\n }\n\n const distinctValueCount = Array.from(byValue.keys()).filter((k) => k !== \"__keyonly__\").length;\n\n if (hasExisting) {\n // Anything in candidates that disagrees with the existing value is\n // a conflict; anything that agrees is silently absorbed.\n const agreeingBucket = byValue.get(existingValueKey!);\n if (agreeingBucket) {\n // Existing is corroborated. Drop the agreeing candidate, treat as\n // pure existing.\n byValue.delete(existingValueKey!);\n }\n const disagreeingValues = Array.from(byValue.entries()).filter(([k]) => k !== \"__keyonly__\");\n if (disagreeingValues.length === 0) {\n // No conflict — existing stays as-is.\n existing.push({ key, value: existingValue });\n } else {\n // Conflict between existing and one or more inferred values.\n const candidatesList: FrontmatterConflict[\"candidates\"] = [\n {\n value: existingValue,\n source: \"existing\",\n confidence: 1.0,\n },\n ];\n for (const [, group] of disagreeingValues) {\n const best = pickBestCandidate(group);\n candidatesList.push({\n value: best.value,\n source: best.source,\n confidence: best.confidence,\n ...(best.rule ? { rule: best.rule } : {}),\n });\n }\n conflicts.push({ key, candidates: candidatesList });\n }\n } else {\n // No existing value — emit a suggestion or a conflict between\n // disagreeing inference sources.\n if (distinctValueCount > 1) {\n // Sources disagree on the value. Emit a conflict.\n const candidatesList: FrontmatterConflict[\"candidates\"] = [];\n for (const [k, group] of byValue) {\n if (k === \"__keyonly__\") continue;\n const best = pickBestCandidate(group);\n candidatesList.push({\n value: best.value,\n source: best.source,\n confidence: best.confidence,\n ...(best.rule ? { rule: best.rule } : {}),\n });\n }\n // Sort candidates by confidence DESC for stable agent UX.\n candidatesList.sort((a, b) => b.confidence - a.confidence);\n conflicts.push({ key, candidates: candidatesList });\n } else if (distinctValueCount === 1) {\n // All sources that suggest a value agree. Pick the best one,\n // combine confidence by max.\n const [valueKeyStr, group] = Array.from(byValue.entries()).find(\n ([k]) => k !== \"__keyonly__\",\n )!;\n const best = pickBestCandidate(group);\n const sources = uniqueSources(group);\n suggestions.push({\n key,\n suggestedValue: best.value,\n confidence: best.confidence,\n sources,\n ...(best.rule ? { rule: best.rule } : {}),\n });\n void valueKeyStr;\n } else {\n // Only key-only candidates (no concrete value). Suggest the key\n // with `suggestedValue: null` — agent should ask user to fill in.\n const group = byValue.get(\"__keyonly__\")!;\n const best = pickBestCandidate(group);\n suggestions.push({\n key,\n suggestedValue: null,\n confidence: best.confidence,\n sources: uniqueSources(group),\n });\n }\n }\n }\n\n // Stable sorting for the response: suggestions DESC by confidence,\n // conflicts ASC by key (no clear order signal there).\n suggestions.sort((a, b) => {\n if (b.confidence !== a.confidence) return b.confidence - a.confidence;\n return a.key.localeCompare(b.key);\n });\n conflicts.sort((a, b) => a.key.localeCompare(b.key));\n existing.sort((a, b) => a.key.localeCompare(b.key));\n\n return {\n existing,\n suggestions,\n conflicts,\n diagnostics: { folder, neighbor, content },\n };\n}\n\nfunction pickBestCandidate(group: Candidate[]): Candidate {\n // Callers always pass at least one candidate — the `if (group)` check\n // above gates this. Defensive throw rather than non-null-assertion\n // keeps the failure mode loud if the invariant ever breaks.\n if (group.length === 0) {\n throw new Error(\"pickBestCandidate called with empty group\");\n }\n let best: Candidate = group[0]!;\n for (const c of group) {\n if (c.confidence > best.confidence) best = c;\n }\n return best;\n}\n\nfunction uniqueSources(group: Candidate[]): SourceTag[] {\n const seen = new Set<SourceTag>();\n const out: SourceTag[] = [];\n // Order: by confidence DESC.\n const sorted = [...group].sort((a, b) => b.confidence - a.confidence);\n for (const c of sorted) {\n if (seen.has(c.source)) continue;\n seen.add(c.source);\n out.push(c.source);\n }\n return out;\n}\n","/**\n * Schema-inference module — public API for the `suggest_frontmatter`\n * MCP tool.\n *\n * Pipeline:\n * inferFromFolder — folder-convention prevalence + dominant values\n * inferFromNeighbors — wikilink-neighborhood prevalence + dominant values\n * inferFromContent — title/body pattern matchers (deterministic rules)\n * combineSuggestions — merge the three, resolve conflicts, classify as\n * existing / suggestions / conflicts\n *\n * Each layer returns a confidence in [0, 1]; the combiner uses the MAX\n * across sources when more than one agrees, and emits a conflict entry\n * when sources disagree on a value for the same key.\n */\n\nexport { inferFromFolder, folderOf } from \"./folder-conventions.js\";\nexport type { FolderConventionEntry, FolderConventionResult } from \"./folder-conventions.js\";\n\nexport { inferFromNeighbors } from \"./neighbor-inference.js\";\nexport type { NeighborInferenceEntry, NeighborInferenceResult } from \"./neighbor-inference.js\";\n\nexport { inferFromContent } from \"./content-heuristics.js\";\nexport type { ContentHeuristicEntry, ContentHeuristicResult } from \"./content-heuristics.js\";\n\nexport { suggestFrontmatter, combineSuggestions } from \"./combiner.js\";\nexport type {\n FrontmatterSuggestion,\n FrontmatterConflict,\n FrontmatterExisting,\n SuggestFrontmatterResult,\n SuggestFrontmatterInput,\n} from \"./combiner.js\";\n","/**\n * Notes-domain MCP handler factory.\n *\n * Tools: read_note, write_note, update_frontmatter, delete_note,\n * query_frontmatter, suggest_frontmatter.\n *\n * Extracted verbatim from the inline `handlers` literal + standalone\n * `handle*` functions in `src/server.ts`. Behavior-neutral.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. All file IO\n * goes through the adapter registry (`resolveSource` / `resolveDelivery`)\n * and the `vault.db` query namespaces.\n */\n\nimport type { VaultManager, Vault } from \"../../vault/index.js\";\nimport type { AdapterRegistry } from \"../../adapters/registry.js\";\nimport { formatDocId, parseSourceHandle } from \"../../adapters/registry.js\";\nimport type { Document, WikilinkRef } from \"../../types.js\";\nimport { queryFrontmatter, updateFrontmatter } from \"../../frontmatter/index.js\";\nimport { suggestFrontmatter } from \"../../schema/index.js\";\nimport {\n countWords,\n safeParseFrontmatter,\n defaultBasename,\n normalizeFolderHint,\n} from \"../utils.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\n/**\n * Read a note via the v2 SourceConnector seam (Plan 01-03 Task 06).\n *\n * The v1 wire shape `{path, title, content, frontmatter, hash, mtime,\n * word_count}` is preserved byte-for-byte; only the INTERNAL data path\n * changed: the handler now resolves the source by handle, mints a DocId,\n * and reads a Document via `source.readDocument(id)`. The mapping back\n * to the v1 shape happens at this boundary.\n *\n * Side effect: reads the file fresh from disk on every call (where v1\n * served the DB-cached row). In a normally-running server the catch-up\n * scan + watcher keep DB ≈ disk, so behavior is observationally\n * identical; the path goes through the seam either way.\n */\nexport async function handleReadNote(\n registry: AdapterRegistry,\n vaultName: string,\n path: string,\n): Promise<object> {\n const handle = parseSourceHandle(`obsidian-fs://${vaultName}`);\n let source;\n try {\n source = registry.resolveSource(handle);\n } catch {\n // Preserve the v1 error message shape for unknown-vault cases.\n throw new Error(`Note not found: ${vaultName}/${path}`);\n }\n const id = formatDocId(\"obsidian-fs\", vaultName, path);\n let doc: Document;\n try {\n doc = await source.readDocument(id);\n } catch {\n throw new Error(`Note not found: ${vaultName}/${path}`);\n }\n\n // Map Document → v1 read_note response shape.\n // - `frontmatter` is `doc.properties` minus the adapter-injected\n // `wikilinks: WikilinkRef[]` (D-05). The v1 shape never carried the\n // wikilinks key; preserve that.\n const { wikilinks: _wikilinks, ...frontmatterOnly } = doc.properties as Record<\n string,\n unknown\n > & {\n wikilinks?: WikilinkRef[];\n };\n const hasFrontmatter = Object.keys(frontmatterOnly).length > 0;\n // Single-paragraph BodyShape=\"flat-text\" — body lives in blocks[0].text.\n const content = doc.blocks[0]?.kind === \"paragraph\" ? doc.blocks[0].text : \"\";\n\n return {\n path,\n title: doc.title,\n content,\n frontmatter: hasFrontmatter ? frontmatterOnly : null,\n hash: doc.hash,\n mtime: doc.mtime,\n word_count: countWords(content),\n };\n}\n\n/**\n * write_note handler. Routes through `registry.resolveDelivery(handle).write`\n * (plan 01-04 task 06) while preserving the v1 wire shape: caller sees\n * `{ok, noteId, newHash, created, reason?, ...}`. The DocId mapping happens\n * at the seam — v2 returns doc_id: DocId; we derive v1 noteId from the DB\n * row after a successful write.\n *\n * The v1 `client_id` arg, when supplied, overrides the constructor-injected\n * default per D-02. When omitted, the delivery's lazy clientId getter reads\n * `server.getClientVersion()?.name` at call time.\n */\nasync function handleWriteNote(\n registry: AdapterRegistry,\n vault: Vault,\n parsed: {\n vault: string;\n path: string;\n content: string;\n frontmatter?: Record<string, unknown> | null;\n expected_hash?: string;\n client_id?: string;\n },\n): Promise<object> {\n const handle = parseSourceHandle(`obsidian-fs://${parsed.vault}`);\n const delivery = registry.resolveDelivery(handle);\n const docId = formatDocId(\"obsidian-fs\", parsed.vault, parsed.path);\n\n const partial: Partial<Document> = {\n blocks: [{ kind: \"paragraph\", text: parsed.content }],\n properties: parsed.frontmatter ?? {},\n };\n const opts: { expectedHash?: string; clientId?: string } = {};\n if (parsed.expected_hash !== undefined) opts.expectedHash = parsed.expected_hash;\n if (parsed.client_id !== undefined) opts.clientId = parsed.client_id;\n\n const res = await delivery.write(docId, partial, opts);\n if (!res.ok) {\n // Preserve v1 conflict shape — handlers used to forward writeNote's\n // v1 WriteConflict directly; reshape to match. Phase 2 envelope fields\n // (sinkName / suggestion) are propagated unchanged when present so\n // callers receive actionable diagnostics on `sink_write_blocked` and\n // the other Phase 2 refusal codes.\n const out: Record<string, unknown> = {\n ok: false,\n reason: res.reason === \"not_found\" ? \"hash_mismatch\" : res.reason,\n };\n if (res.currentHash !== undefined) out.currentHash = res.currentHash;\n if (res.message !== undefined) out.message = res.message;\n if (res.sinkName !== undefined) out.sinkName = res.sinkName;\n if (res.suggestion !== undefined) out.suggestion = res.suggestion;\n if (res.key !== undefined) out.key = res.key;\n if (res.observedValue !== undefined) out.observedValue = res.observedValue;\n return out;\n }\n\n // ADR-008: a write to a ContextFit vault must refresh its search KB so the\n // new/edited content is retrievable. The SQLite note row is already updated\n // inline by writeNote; here we rebuild the ContextFit KB (full re-ingest —\n // CPU-only, fast). Best-effort: a KB-refresh failure does not fail the write\n // (the note is on disk + in SQLite; the next index/catchup reconciles).\n if (vault.config.backend === \"contextfit\") {\n try {\n const { indexVaultWithContextFit } =\n await import(\"../../adapters/retrieval/contextfit/index.js\");\n await indexVaultWithContextFit(vault.config, {});\n } catch {\n // swallow — write succeeded; KB will catch up on next index/restart\n }\n }\n\n // Derive v1 noteId from the DB. The write went through writeNote\n // internally which upserts the note; getByPath returns the row.\n const noteRow = vault.db.notes.getByPath(parsed.path);\n return {\n ok: true,\n newHash: res.newHash,\n noteId: noteRow?.id ?? 0,\n created: res.created,\n };\n}\n\n/**\n * delete_note handler. Routes through `registry.resolveDelivery(handle).delete`\n * (plan 01-04 task 06). Preserves the v1 wire shape `{ok, newHash, noteId,\n * created}` (created=false for delete; newHash echoes the now-gone file's\n * pre-delete hash, matching v1 deleteNote semantics).\n */\nasync function handleDeleteNote(\n registry: AdapterRegistry,\n vault: Vault,\n parsed: {\n vault: string;\n path: string;\n expected_hash: string;\n client_id?: string;\n },\n): Promise<object> {\n // Capture the v1 noteId + existing hash BEFORE we ask the delivery to\n // delete (after success, getByPath returns null).\n const noteRow = vault.db.notes.getByPath(parsed.path);\n const preDeleteHash = noteRow?.hash ?? parsed.expected_hash;\n\n const handle = parseSourceHandle(`obsidian-fs://${parsed.vault}`);\n const delivery = registry.resolveDelivery(handle);\n const docId = formatDocId(\"obsidian-fs\", parsed.vault, parsed.path);\n\n const opts: { expectedHash?: string; clientId?: string } = {\n expectedHash: parsed.expected_hash,\n };\n if (parsed.client_id !== undefined) opts.clientId = parsed.client_id;\n\n const res = await delivery.delete(docId, opts);\n if (!res.ok) {\n const out: Record<string, unknown> = {\n ok: false,\n reason: res.reason === \"not_found\" ? \"hash_mismatch\" : res.reason,\n };\n if (res.currentHash !== undefined) out.currentHash = res.currentHash;\n if (res.message !== undefined) out.message = res.message;\n if (res.sinkName !== undefined) out.sinkName = res.sinkName;\n if (res.suggestion !== undefined) out.suggestion = res.suggestion;\n return out;\n }\n return {\n ok: true,\n newHash: preDeleteHash,\n noteId: noteRow?.id ?? 0,\n created: false,\n };\n}\n\n/**\n * Handler for the v0.10.0 `suggest_frontmatter` tool.\n *\n * Two-mode dispatch:\n * - `path` provided → existing-note inference. Reads stored content +\n * frontmatter + wikilinks from DB. Folder-conventions use the note's\n * own folder.\n * - `content` provided (no path) → draft inference. Folder-conventions\n * use `folder_hint` (or vault root). No backlinks. Forward-link\n * extraction would require a lightweight markdown parse — for v0.10.0\n * we skip it to keep the tool dependency-free and document the\n * limitation in the response.\n */\nfunction handleSuggestFrontmatter(\n manager: VaultManager,\n parsed: {\n vault: string;\n path?: string;\n content?: string;\n title?: string;\n folder_hint?: string;\n },\n): object {\n const vault = manager.require(parsed.vault);\n\n // Mode 1: existing-note path.\n if (parsed.path) {\n const note = vault.db.notes.getByPath(parsed.path);\n if (!note) {\n throw new Error(\n `Note not found: ${parsed.vault}/${parsed.path}. ` +\n `Use draft mode ({content, folder_hint}) for unindexed notes.`,\n );\n }\n const existingFm: Record<string, unknown> | null = note.frontmatter\n ? safeParseFrontmatter(note.frontmatter)\n : null;\n const result = suggestFrontmatter({\n vault,\n path: note.path,\n existingFrontmatter: existingFm,\n content: parsed.content ?? note.content,\n title: parsed.title ?? note.title ?? defaultBasename(note.path),\n excludePath: note.path,\n });\n return {\n mode: \"existing\",\n path: note.path,\n ...result,\n };\n }\n\n // Mode 2: draft.\n const folderHint = normalizeFolderHint(parsed.folder_hint);\n // Synthesize a path under the folder hint so folder-conventions can\n // resolve. The path itself never gets written; it's a probe.\n const probePath = `${folderHint}__draft__${Date.now()}.md`;\n const result = suggestFrontmatter({\n vault,\n path: probePath,\n existingFrontmatter: null,\n content: parsed.content!,\n title: parsed.title ?? \"Draft\",\n // Exclude the synthetic path explicitly — though it won't match any\n // existing note, this future-proofs against collisions.\n excludePath: probePath,\n });\n return {\n mode: \"draft\",\n folder_hint: folderHint,\n note: \"Draft mode: no backlinks contributed. Provide `path` (and index the note first) for richer neighbor-inference.\",\n ...result,\n };\n}\n\nexport function makeNotesHandlers(deps: HandlerDeps): Partial<Record<ToolName, Handler>> {\n const { manager, adapterRegistry, suppression, memorySinkRegistry } = deps;\n return {\n read_note: async (a) => {\n const p = a as { vault: string; path: string };\n return handleReadNote(adapterRegistry, p.vault, p.path);\n },\n query_frontmatter: async (a) => {\n const p = a as { vault: string; where: Record<string, unknown>; limit: number };\n const vault = manager.require(p.vault);\n const hits = queryFrontmatter(vault, {\n where: p.where as Record<string, never>,\n limit: p.limit,\n });\n return {\n notes: hits.map((n) => ({\n path: n.path,\n title: n.title,\n frontmatter: n.frontmatter ? JSON.parse(n.frontmatter) : null,\n mtime: n.mtime,\n })),\n count: hits.length,\n };\n },\n write_note: async (a) => {\n const p = a as {\n vault: string;\n path: string;\n content: string;\n frontmatter?: Record<string, unknown> | null;\n expected_hash?: string;\n client_id?: string;\n };\n const vault = manager.require(p.vault);\n // Suppress the watcher event triggered by our own atomic rename.\n // We call suppression BEFORE delivery.write() so the event is\n // pre-filtered. Worst case (permission_denied / hash_mismatch):\n // we suppress an event that never fires — harmless beyond the\n // ~2s TTL.\n suppression.add(p.path);\n return handleWriteNote(adapterRegistry, vault, p);\n },\n update_frontmatter: async (a) => {\n const p = a as {\n vault: string;\n path: string;\n merge: Record<string, unknown>;\n expected_hash?: string;\n client_id?: string;\n };\n const vault = manager.require(p.vault);\n return updateFrontmatter({\n vault,\n registry: adapterRegistry,\n memorySinkRegistry,\n relativePath: p.path,\n merge: p.merge,\n ...(p.expected_hash !== undefined ? { expectedHash: p.expected_hash } : {}),\n ...(p.client_id !== undefined ? { clientId: p.client_id } : {}),\n onBeforeFsWrite: () => suppression.add(p.path),\n });\n },\n delete_note: async (a) => {\n const p = a as {\n vault: string;\n path: string;\n expected_hash: string;\n client_id?: string;\n };\n const vault = manager.require(p.vault);\n suppression.add(p.path);\n return handleDeleteNote(adapterRegistry, vault, p);\n },\n suggest_frontmatter: async (a) => {\n const p = a as {\n vault: string;\n path?: string;\n content?: string;\n title?: string;\n folder_hint?: string;\n };\n return handleSuggestFrontmatter(manager, p);\n },\n };\n}\n","/**\n * Search-domain MCP handler factory.\n *\n * Tools: search_semantic, search_text, search_hybrid, search (compat),\n * fetch (compat).\n *\n * Extracted verbatim from the inline `handlers` literal + standalone\n * `handle*` functions in `src/server.ts`. Behavior-neutral.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. Display-URL\n * minting flows through the adapter registry seam (`displayUrl`); the\n * search pipeline reads via `vault.db` query namespaces.\n */\n\nimport type { VaultManager } from \"../../vault/index.js\";\nimport type { OllamaClient } from \"../../ollama/index.js\";\nimport type { Reranker } from \"../../rerank/index.js\";\nimport type { AdapterRegistry } from \"../../adapters/registry.js\";\nimport { parseSourceHandle } from \"../../adapters/registry.js\";\nimport { FtsQueries } from \"../../db/index.js\";\nimport { hybridSearch, searchVaults, matchesAnyGlob } from \"../../search/index.js\";\nimport type { ExpandDeps, ExpandDirection } from \"../../graph/index.js\";\nimport type { EdgeType } from \"../../db/queries/edges.js\";\nimport type { SearchHit } from \"../../types.js\";\nimport {\n resolveVaultTargets,\n encodeNoteId,\n decodeNoteId,\n displayUrl,\n truncateSnippet,\n} from \"../utils.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nasync function handleSearchSemantic(\n manager: VaultManager,\n ollama: OllamaClient,\n defaultModel: string,\n activeVault: string | undefined,\n query: string,\n vaultFilter: string[] | undefined,\n topK: number,\n excludePaths: string[] | undefined,\n): Promise<object> {\n const { targets, skipped } = resolveVaultTargets(manager, vaultFilter, activeVault);\n\n if (targets.length === 0) {\n return {\n hits: [],\n note:\n skipped.length > 0\n ? `All eligible vaults are indexing; skipped: ${skipped.join(\", \")}.`\n : \"No vaults configured.\",\n };\n }\n\n // When excluding paths, fan out wider so the filtered topK is well-stocked.\n const hasExclude = excludePaths !== undefined && excludePaths.length > 0;\n const fanK = hasExclude ? topK * 3 : topK;\n\n // Cache query embedding by model name across vaults.\n const embedCache = new Map<string, number[]>();\n const allHits: SearchHit[] = [];\n\n for (const vault of targets) {\n // Phase 7c follow-up (v0.7.2): the active model in the DB is the source\n // of truth — switch_active_model may have promoted a shadow model\n // that doesn't match config.embedding_model. Fall back to the config\n // only when no active model is registered yet.\n const model = vault.db.models.getActive();\n if (!model) continue;\n const modelName = model.name;\n\n let queryVec = embedCache.get(modelName);\n if (!queryVec) {\n const embedResp = await ollama.embed({ model: modelName, texts: [query] });\n queryVec = embedResp.vectors[0];\n if (!queryVec) continue;\n embedCache.set(modelName, queryVec);\n }\n\n const semanticHits = vault.db.embeddings.searchSemantic(model.id, queryVec, fanK);\n\n for (const hit of semanticHits) {\n const chunk = vault.db.chunks.getById(hit.chunkId);\n if (!chunk) continue;\n const note = vault.db.notes.getById(chunk.note_id);\n if (!note) continue;\n if (hasExclude && matchesAnyGlob(note.path, excludePaths!)) continue;\n const score = 1 / (1 + hit.distance);\n\n allHits.push({\n vault: vault.config.name,\n notePath: note.path,\n noteTitle: note.title,\n chunkText: chunk.text,\n chunkIdx: chunk.idx,\n headingPath: chunk.heading_path,\n score,\n scoreBreakdown: { semantic: score },\n });\n }\n }\n\n allHits.sort((a, b) => b.score - a.score);\n const out: Record<string, unknown> = {\n hits: allHits.slice(0, topK),\n count: allHits.length,\n };\n if (skipped.length > 0) {\n out.note = `Skipped vault(s) currently indexing: ${skipped.join(\", \")}.`;\n }\n return out;\n}\n\nfunction handleSearchText(\n manager: VaultManager,\n activeVault: string | undefined,\n query: string,\n vaultFilter: string[] | undefined,\n topK: number,\n excludePaths: string[] | undefined,\n): object {\n const { targets, skipped } = resolveVaultTargets(manager, vaultFilter, activeVault);\n\n if (targets.length === 0) {\n return {\n hits: [],\n note:\n skipped.length > 0\n ? `All eligible vaults are indexing; skipped: ${skipped.join(\", \")}.`\n : \"No vaults configured.\",\n };\n }\n\n const hasExclude = excludePaths !== undefined && excludePaths.length > 0;\n const fanK = hasExclude ? topK * 3 : topK;\n\n const sanitized = FtsQueries.sanitize(query);\n const allHits: SearchHit[] = [];\n const skippedContextFit: string[] = [];\n\n for (const vault of targets) {\n // ADR-008: contextfit-backed vaults have no SQLite FTS table. `search_text`\n // is an Ollama-path BM25 surface; ContextFit users should use search_hybrid\n // / search_semantic (which dispatch to the ContextFit engine). Skip + note.\n if (vault.config.backend === \"contextfit\") {\n skippedContextFit.push(vault.config.name);\n continue;\n }\n const ftsHits = vault.db.fts.search(sanitized, fanK, true);\n for (const hit of ftsHits) {\n const chunk = vault.db.chunks.getById(hit.chunkId);\n if (!chunk) continue;\n const note = vault.db.notes.getById(chunk.note_id);\n if (!note) continue;\n if (hasExclude && matchesAnyGlob(note.path, excludePaths!)) continue;\n\n allHits.push({\n vault: vault.config.name,\n notePath: note.path,\n noteTitle: note.title,\n chunkText: hit.snippet ?? chunk.text,\n chunkIdx: chunk.idx,\n headingPath: chunk.heading_path,\n score: hit.score,\n scoreBreakdown: { text: hit.score },\n });\n }\n }\n\n allHits.sort((a, b) => b.score - a.score);\n const out: Record<string, unknown> = {\n hits: allHits.slice(0, topK),\n count: allHits.length,\n };\n const notes: string[] = [];\n if (skipped.length > 0) notes.push(`Skipped vault(s) currently indexing: ${skipped.join(\", \")}.`);\n if (skippedContextFit.length > 0) {\n notes.push(\n `search_text is not supported for ContextFit vault(s): ${skippedContextFit.join(\", \")} — ` +\n `use search_hybrid or search_semantic instead.`,\n );\n }\n if (notes.length > 0) out.note = notes.join(\" \");\n return out;\n}\n\nexport async function handleSearchHybrid(\n manager: VaultManager,\n ollama: OllamaClient,\n defaultModel: string,\n activeVault: string | undefined,\n query: string,\n vaultFilter: string[] | undefined,\n topK: number,\n rrfK: number,\n excludePaths: string[] | undefined,\n reranker: Reranker | undefined,\n // Phase 3 / 03-05 additive params — D-07/D-08/ASM-07/ASM-08.\n recencyWeight: number = 0,\n authorityWeight: number = 0,\n halfLifeDays: number = 30,\n includeSuperseded: boolean = false,\n // Phase 3 / 03-05: optional display-URL resolver (ADR-002 §I-5b\n // seam-preserving — the URL literal lives in the adapter, not here).\n displayUrlFor?: (vaultName: string, notePath: string) => string,\n // Phase 4 / 04-04 (D-15): optional auto-expansion + its injected deps.\n // When `expand` is undefined, hybridSearch's guard short-circuits;\n // `expandDeps` is forwarded unconditionally so future per-call wiring\n // stays trivial.\n expandOpts?: {\n hops: 1 | 2;\n direction?: ExpandDirection;\n edge_types?: EdgeType[];\n },\n expandDeps?: ExpandDeps,\n): Promise<object> {\n const { targets, skipped } = resolveVaultTargets(manager, vaultFilter, activeVault);\n\n if (targets.length === 0) {\n return {\n hits: [],\n note:\n skipped.length > 0\n ? `All eligible vaults are indexing; skipped: ${skipped.join(\", \")}.`\n : \"No vaults configured.\",\n };\n }\n\n const hasExclude = excludePaths !== undefined && excludePaths.length > 0;\n // Request 3× the final topK when filtering so the post-filter list is\n // well-stocked. hybridSearch internally fans 3× again per ranking, so\n // semantic/BM25 each retrieve ~9×topK chunks — plenty of headroom.\n const innerTopK = hasExclude ? topK * 3 : topK;\n\n // ADR-008: searchVaults routes contextfit-backed vaults to the CPU-only\n // engine and ollama vaults to the embeddings hybrid, then merges. For an\n // all-ollama target set (the common case) it delegates straight to\n // hybridSearch with no behavior change.\n const hits = await searchVaults({\n query,\n embeddingModel: defaultModel,\n ollama,\n vaults: targets,\n topK: innerTopK,\n rrfK,\n includeBreakdown: true,\n reranker,\n recencyWeight,\n authorityWeight,\n halfLifeDays,\n includeSuperseded,\n ...(displayUrlFor ? { displayUrlFor } : {}),\n // Phase 4 / 04-04 (D-15): forward optional expand + deps. When\n // `expandOpts` is undefined, hybridSearch short-circuits the\n // expand block (zero new DB reads — v1-baseline byte-identical).\n ...(expandOpts ? { expand: expandOpts } : {}),\n ...(expandDeps ? { expandDeps } : {}),\n });\n\n const filtered = hasExclude\n ? hits.filter((h) => !matchesAnyGlob(h.notePath, excludePaths!))\n : hits;\n\n const out: Record<string, unknown> = {\n hits: filtered.slice(0, topK),\n count: filtered.length,\n };\n if (skipped.length > 0) {\n out.note = `Skipped vault(s) currently indexing: ${skipped.join(\", \")}.`;\n }\n return out;\n}\n\n// ─── v0.9.0 handlers — Agent-Compatibility & Self-Orientation ───────────────\n\n/**\n * Encode an opaque id for the OB1-compatible `search`/`fetch` API.\n *\n * Format: `<vault>:<vault-relative-path>`\n *\n * Vault names cannot contain `:` per config schema, and Obsidian paths use\n * forward slashes — so the first `:` is an unambiguous separator. We pick\n * this over a base64-encoded blob because the id stays human-readable in\n * connector UIs (ChatGPT shows search results inline) and trivially\n * round-trips through copy/paste.\n */\nasync function handleSearchCompat(\n manager: VaultManager,\n registry: AdapterRegistry,\n ollama: OllamaClient,\n defaultModel: string,\n activeVault: string | undefined,\n query: string,\n limit: number,\n reranker: Reranker | undefined,\n): Promise<object> {\n const { targets, skipped } = resolveVaultTargets(manager, undefined, activeVault);\n\n if (targets.length === 0) {\n return {\n results: [],\n note:\n skipped.length > 0\n ? `All eligible vaults are indexing; skipped: ${skipped.join(\", \")}.`\n : \"No vaults configured.\",\n };\n }\n\n // We delegate to the hybrid pipeline so OB1-style search benefits from\n // both BM25 and vector retrieval — this is the differentiator vs. OB1's\n // pure-embedding implementation. searchVaults additionally routes\n // contextfit-backed vaults to the CPU-only engine (ADR-008).\n const hits = await searchVaults({\n query,\n embeddingModel: defaultModel,\n ollama,\n vaults: targets,\n topK: limit,\n rrfK: 60,\n includeBreakdown: false,\n reranker,\n });\n\n // De-duplicate to one result per note (OB1 spec: one entry per\n // document). Chunks of the same note collapse to the first/best chunk\n // and contribute their snippet.\n const seen = new Set<string>();\n const results: Array<{\n id: string;\n title: string;\n url: string;\n snippet: string;\n }> = [];\n for (const h of hits) {\n const noteKey = `${h.vault}:${h.notePath}`;\n if (seen.has(noteKey)) continue;\n seen.add(noteKey);\n results.push({\n id: encodeNoteId(h.vault, h.notePath),\n title: h.noteTitle ?? h.notePath,\n url: displayUrl(registry, h.vault, h.notePath),\n snippet: truncateSnippet(h.chunkText, 280),\n });\n if (results.length >= limit) break;\n }\n\n const out: Record<string, unknown> = { results };\n if (skipped.length > 0) {\n out.note = `Skipped vault(s) currently indexing: ${skipped.join(\", \")}.`;\n }\n return out;\n}\n\nfunction handleFetchCompat(manager: VaultManager, registry: AdapterRegistry, id: string): object {\n const { vault: vaultName, path } = decodeNoteId(id);\n const vault = manager.require(vaultName);\n const note = vault.db.notes.getByPath(path);\n if (!note) {\n throw new Error(`Note not found: ${vaultName}/${path}`);\n }\n const metadata: Record<string, unknown> = {\n vault: vaultName,\n path: note.path,\n mtime: note.mtime,\n hash: note.hash,\n word_count: note.word_count,\n };\n if (note.frontmatter) {\n try {\n metadata.frontmatter = JSON.parse(note.frontmatter);\n } catch {\n // Stored frontmatter should always be valid JSON; if it isn't, treat\n // as missing rather than failing the fetch.\n }\n }\n return {\n id,\n title: note.title ?? note.path,\n text: note.content,\n url: displayUrl(registry, vaultName, note.path),\n metadata,\n };\n}\n\nexport function makeSearchHandlers(deps: HandlerDeps): Partial<Record<ToolName, Handler>> {\n const { manager, ollama, defaultModel, activeVault, reranker, adapterRegistry } = deps;\n return {\n search_semantic: async (a) => {\n const p = a as {\n query: string;\n vaults?: string[];\n top_k: number;\n exclude_paths?: string[];\n };\n return handleSearchSemantic(\n manager,\n ollama,\n defaultModel,\n activeVault,\n p.query,\n p.vaults,\n p.top_k,\n p.exclude_paths,\n );\n },\n search_text: async (a) => {\n const p = a as {\n query: string;\n vaults?: string[];\n top_k: number;\n exclude_paths?: string[];\n };\n return handleSearchText(manager, activeVault, p.query, p.vaults, p.top_k, p.exclude_paths);\n },\n search_hybrid: async (a) => {\n const p = a as {\n query: string;\n vaults?: string[];\n top_k: number;\n rrf_k: number;\n exclude_paths?: string[];\n rerank: boolean;\n // Phase 3 / 03-05 additive params — Zod fills defaults so these\n // are always present after validation. v1 callers omit them and\n // get the v1-identical default behavior.\n recency_weight: number;\n authority_weight: number;\n half_life_days: number;\n include_superseded: boolean;\n // Phase 4 / 04-04 (D-15): additive optional auto-expansion.\n // When omitted, the downstream hybridSearch guard short-circuits.\n expand?: {\n hops: 1 | 2;\n direction?: ExpandDirection;\n edge_types?: EdgeType[];\n };\n };\n return handleSearchHybrid(\n manager,\n ollama,\n defaultModel,\n activeVault,\n p.query,\n p.vaults,\n p.top_k,\n p.rrf_k,\n p.exclude_paths,\n p.rerank ? reranker : undefined,\n p.recency_weight,\n p.authority_weight,\n p.half_life_days,\n p.include_superseded,\n // 03-05: display-URL resolver — delegates to the obsidian-fs source\n // adapter (or whichever adapter owns the vault) so hybrid.ts never\n // mints adapter URL strings (ADR-002 §I-5b).\n (vaultName, notePath) => displayUrl(adapterRegistry, vaultName, notePath),\n // Phase 4 / 04-04 (D-15): pass the optional expand object + its\n // injected deps (manager + sourceConnectorFor) so hybridSearch\n // can compose Plan 04-03's `expand()` over the rescored top-K.\n p.expand,\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n );\n },\n search: async (a) => {\n const p = a as { query: string; limit: number };\n return handleSearchCompat(\n manager,\n adapterRegistry,\n ollama,\n defaultModel,\n activeVault,\n p.query,\n p.limit,\n reranker,\n );\n },\n fetch: async (a) => {\n const p = a as { id: string };\n return handleFetchCompat(manager, adapterRegistry, p.id);\n },\n };\n}\n","/**\n * Graph-domain MCP handler factory.\n *\n * Tools: list_backlinks, list_forward_links, find_broken_links, expand,\n * cluster.\n *\n * Extracted verbatim from the inline `handlers` literal in `src/server.ts`.\n * Behavior-neutral — each arrow wires args to the same graph-layer call.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. Source\n * resolution flows through the adapter registry seam.\n */\n\nimport { parseDocId, parseSourceHandle } from \"../../adapters/registry.js\";\nimport {\n cluster,\n expand,\n listBacklinks,\n listForwardLinks,\n findBrokenLinks,\n} from \"../../graph/index.js\";\nimport type { ClusterOptions, ExpandDirection, ExpandOptions } from \"../../graph/index.js\";\nimport type { EdgeType } from \"../../db/queries/edges.js\";\nimport { hybridSearch } from \"../../search/index.js\";\nimport { displayUrl } from \"../utils.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nexport function makeGraphHandlers(deps: HandlerDeps): Partial<Record<ToolName, Handler>> {\n const { manager, ollama, defaultModel, reranker, adapterRegistry } = deps;\n return {\n list_backlinks: async (a) => {\n const p = a as { vault: string; path: string };\n const vault = manager.require(p.vault);\n return { backlinks: listBacklinks(vault, p.path) };\n },\n list_forward_links: async (a) => {\n const p = a as { vault: string; path: string; include_broken: boolean };\n const vault = manager.require(p.vault);\n return { links: listForwardLinks(vault, p.path, p.include_broken) };\n },\n find_broken_links: async (a) => {\n const p = a as { vault: string };\n const vault = manager.require(p.vault);\n return { broken: findBrokenLinks(vault) };\n },\n\n // ── Phase 4 graph tools (Plan 04-03 / GRA-01) ─────────────────────────\n expand: async (a) => {\n const p = a as {\n seed_doc_ids: string[];\n hops: 1 | 2;\n direction: ExpandDirection;\n edge_types?: EdgeType[];\n filter_properties?: Record<string, unknown>;\n include_superseded: boolean;\n };\n // Cast incoming validated DocId strings to the branded DocId\n // type via parseDocId; Zod already enforced DOC_ID_PATTERN at\n // the boundary so this is a no-op brand cast at runtime.\n const seeds = p.seed_doc_ids.map((s) => parseDocId(s));\n return expand(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n {\n seed_doc_ids: seeds,\n hops: p.hops,\n direction: p.direction,\n ...(p.edge_types !== undefined ? { edge_types: p.edge_types } : {}),\n ...(p.filter_properties !== undefined ? { filter_properties: p.filter_properties } : {}),\n include_superseded: p.include_superseded,\n } satisfies ExpandOptions,\n );\n },\n\n // ── Phase 4 graph tools (Plan 04-05 / GRA-02) ─────────────────────────\n cluster: async (a) => {\n const p = a as {\n query?: string;\n seed_doc_ids?: string[];\n vault?: string;\n method: \"edge-community\";\n query_top_k?: number;\n force?: boolean;\n };\n // Build a ClusterOptions discriminated value. Zod's mutual-\n // exclusion refinement has already rejected both-present /\n // neither-present inputs by the time we reach this handler, but\n // the runtime cluster() function performs the same validation as\n // a defense-in-depth check for direct (non-MCP) callers.\n let opts: ClusterOptions;\n if (p.query !== undefined) {\n // CR-02: propagate `vault` so cluster()'s query path can scope\n // search_hybrid deterministically on multi-vault setups.\n opts = {\n query: p.query,\n method: \"edge-community\",\n ...(p.vault !== undefined ? { vault: p.vault } : {}),\n ...(p.query_top_k !== undefined ? { query_top_k: p.query_top_k } : {}),\n ...(p.force !== undefined ? { force: p.force } : {}),\n };\n } else {\n const seeds = (p.seed_doc_ids ?? []).map((s) => parseDocId(s));\n opts = {\n seed_doc_ids: seeds,\n method: \"edge-community\",\n ...(p.force !== undefined ? { force: p.force } : {}),\n };\n }\n return cluster(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n // Bind hybridSearch at call time — avoids the\n // src/graph/cluster.ts → src/search/hybrid.ts circular\n // import. The injected callback returns SearchHit[]; the\n // dispatcher already has `ollama` + `defaultModel` in scope.\n hybridSearch: async (vault, query, limit) =>\n hybridSearch({\n query,\n embeddingModel: defaultModel,\n ollama,\n vaults: [vault],\n topK: limit,\n includeBreakdown: false,\n ...(reranker ? { reranker } : {}),\n displayUrlFor: (vaultName, notePath) =>\n displayUrl(adapterRegistry, vaultName, notePath),\n }),\n },\n opts,\n );\n },\n };\n}\n","/**\n * Memory-domain MCP handler factory.\n *\n * Tools: record_observation, supersede, recall.\n *\n * Extracted verbatim from the inline `handlers` literal in `src/server.ts`.\n * Behavior-neutral — each arrow wires args to the same memory-tool call and\n * applies the same post-write suppression bookkeeping.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. Memory writes\n * route through the delivery adapter seam; the `obsidian-fs://` handle\n * literals are adapter handle strings (not display URLs), used to resolve\n * the delivery/source connectors via the registry.\n */\n\nimport { parseSourceHandle } from \"../../adapters/registry.js\";\nimport {\n handleRecall,\n handleRecordObservation,\n handleSupersede,\n} from \"../../memory/tools/index.js\";\nimport { hybridSearch } from \"../../search/index.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nexport function makeMemoryHandlers(deps: HandlerDeps): Partial<Record<ToolName, Handler>> {\n const { manager, ollama, defaultModel, adapterRegistry, suppression, memorySinkRegistry } = deps;\n return {\n // ── Phase 2 memory tools (Plan 02-04) ──────────────────────────────────\n record_observation: async (a) => {\n const p = a as {\n vault: string;\n claim: string;\n evidence: string[];\n confidence: \"direct\" | \"inferred\" | \"uncertain\";\n type: string;\n sink?: string;\n properties?: Record<string, unknown>;\n };\n // Suppress the watcher event for the soon-to-be-written file.\n // We don't know the exact filename yet (controller mints it), so\n // suppress the observations/ folder path prefix; the watcher's\n // suppression set tolerates fuzzy matches via the TTL.\n const result = await handleRecordObservation(\n {\n memorySinkRegistry,\n manager,\n deliveryAdapterFor: (vaultName) =>\n adapterRegistry.resolveDelivery(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n // After the write, suppress the watcher event using the minted\n // DocId so live-indexing doesn't re-fire on our own write.\n if (result.ok) {\n const resource = result.doc_id.replace(`obsidian-fs://${p.vault}/`, \"\");\n suppression.add(resource);\n }\n return result;\n },\n supersede: async (a) => {\n const p = a as {\n doc_id: string;\n replacement_doc_id: string;\n reason: string;\n };\n const result = await handleSupersede(\n {\n memorySinkRegistry,\n manager,\n deliveryAdapterFor: (vaultName) =>\n adapterRegistry.resolveDelivery(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n if (result.ok) {\n const resource = result.doc_id.replace(/^obsidian-fs:\\/\\/[^/]+\\//, \"\");\n suppression.add(resource);\n }\n return result;\n },\n\n // ── Phase 2 memory tools (Plan 02-05) ──────────────────────────────────\n recall: async (a) => {\n const p = a as {\n query: string;\n min_confidence?: \"direct\" | \"inferred\" | \"uncertain\";\n types?: string[];\n max_age_days?: number;\n sink?: string;\n limit?: number;\n vaults?: string[];\n };\n const packets = await handleRecall(\n {\n memorySinkRegistry,\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n searchHybrid: async (input) =>\n hybridSearch({\n query: input.query,\n embeddingModel: defaultModel,\n ollama,\n vaults: input.vaults,\n topK: input.topK,\n rrfK: 60,\n includeBreakdown: false,\n }),\n },\n p,\n );\n return { packets, count: packets.length };\n },\n };\n}\n","/**\n * Brief-domain MCP handler factory.\n *\n * Tools: compile_brief, get_brief.\n *\n * Extracted verbatim from the inline `handlers` literal in `src/server.ts`.\n * Behavior-neutral — same brief-controller calls, same post-write\n * suppression bookkeeping.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. Brief writes\n * route through the delivery adapter seam; `obsidian-fs://` literals are\n * adapter handle strings, not display URLs.\n */\n\nimport { parseSourceHandle } from \"../../adapters/registry.js\";\nimport { handleCompileBrief, handleGetBrief } from \"../../brief/index.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nexport function makeBriefHandlers(deps: HandlerDeps): Partial<Record<ToolName, Handler>> {\n const { manager, ollama, adapterRegistry, suppression, memorySinkRegistry, server, config } =\n deps;\n return {\n // ── Phase 5 brief tools (Plan 05-02 / BRF-03, BRF-04) ──────────────────\n compile_brief: async (a) => {\n const p = a as {\n vault: string;\n target: string;\n source_doc_ids: string[];\n purpose: string;\n max_tokens?: number;\n prepared_text?: string;\n sink?: string;\n };\n const result = await handleCompileBrief(\n {\n memorySinkRegistry,\n manager,\n deliveryAdapterFor: (vaultName) =>\n adapterRegistry.resolveDelivery(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n server,\n ollama,\n briefConfig: config.brief,\n },\n p,\n );\n // Suppress watcher events for the soon-to-be-indexed brief +\n // (when D-12 chain fires) the just-updated prior brief.\n if (result.ok) {\n const resource = result.doc_id.replace(`obsidian-fs://${p.vault}/`, \"\");\n suppression.add(resource);\n if (result.supersededPrior) {\n const oldResource = result.supersededPrior.replace(/^obsidian-fs:\\/\\/[^/]+\\//, \"\");\n suppression.add(oldResource);\n }\n }\n return result;\n },\n get_brief: async (a) => {\n const p = a as {\n vault: string;\n target: string;\n max_age_days?: number;\n allow_stale?: boolean;\n };\n return handleGetBrief(\n {\n memorySinkRegistry,\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n },\n };\n}\n","/**\n * `assembleDossier` — the ASM-04 controller.\n *\n * Resolves a `{type, key}` pair to an anchor `Document` and walks its\n * backlinks to produce a `DossierResult`:\n *\n * - `anchor` — citation packet for the matched document (or `null`\n * when no doc matches the type+key pair).\n * - `linked_documents` — citation packets for every document linking\n * TO the anchor (backlinks), each tagged with `relation: \"wikilink\"`.\n * In v2.0.0 the v1 `wikilinks` table only stores wikilink edges; the\n * `relation` field widens additively in Phase 4 (GRA-04 typed edges).\n * Search for `PHASE-4-WIDEN` to find the one-line change point.\n * - `property_rollups` — `{ linked_count, linked_types, status_distribution }`,\n * aggregated in a single pass over `linked_documents`. Keys missing\n * from a linked doc's properties are bucketed as `\"unknown\"`. Counts\n * are emitted with alphabetically-sorted keys for deterministic\n * JSON serialization.\n * - `error` — structured `{ code: \"no_matching_anchor_document\", type,\n * key }` when no anchor document matches; `null` on success. This\n * replaces the \"silent empty\" anti-pattern (D-04).\n *\n * # Resolution rules\n *\n * - **Strict `properties.type` match (D-03).** Exact string equality\n * against `Document.properties.type`. No tag fallback, no case\n * folding, no synonym expansion.\n * - **Key matches `title` OR any entry in `properties.aliases`\n * (D-04).** `aliases` is a `string[]` from frontmatter. The match\n * is exact-string. Aliases that are not strings are ignored (no\n * coercion).\n * - **Deterministic tiebreak** when multiple docs of `type` match a\n * given key (rare; can happen if two docs share a title): pick the\n * candidate whose `(title, doc_id)` sorts FIRST lexicographically.\n * This guarantees determinism across runs and across adapter\n * implementations.\n *\n * # No status filtering\n *\n * Per the CONTEXT.md §Specifics caveat, dossiers show the WHOLE\n * picture — superseded backlinks DO appear in `linked_documents` with\n * their `status` field populated. Agents that want a status filter\n * apply one client-side over the result; only search applies an\n * implicit `status: \"superseded\"` hide (recall, D-01).\n *\n * # Adapter-seam discipline (ADR-002 I-1..I-7)\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. All vault content access goes through\n * the injected `SourceConnector` (`readDocument`). Frontmatter reads\n * for the anchor-resolution step go through the `vault.db` query\n * namespace, which holds the already-indexed `notes.frontmatter` JSON\n * blob — that's L0 substrate, owned by the existing indexer.\n *\n * # Performance budget\n *\n * Anchor resolution is O(N) over `notes.frontmatter` rows whose\n * `properties.type === args.type`. The Atlas Robotics fixture is ~75\n * notes; query is sub-millisecond. If real-world dossiers exhibit hot\n * type queries (e.g. `type: \"Meeting\"` over a vault with thousands of\n * meeting notes), Phase 5 may add a `notes_type` index. Do not\n * pre-optimize.\n */\n\nimport { formatDocId } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport { queryFrontmatter } from \"../frontmatter/query.js\";\nimport { listBacklinks } from \"../graph/graph.js\";\nimport {\n type CitationPacket,\n displayUrlFor,\n toCitationPacket,\n withPropertyExtras,\n} from \"../memory/citation-packet.js\";\nimport type { Document } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\n\n/**\n * Dossier deps — supplied at server bootstrap. Mirrors the recall\n * controller's seam pattern so the production wiring and unit tests\n * use the same shape.\n */\nexport interface AssembleDossierDeps {\n /** Vault manager — resolves vault names to `Vault` records. */\n manager: VaultManager;\n /** Resolve the `SourceConnector` instance for a vault name. */\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\n/**\n * Dossier arguments — `{type, key, vaults?}`.\n *\n * `type` is matched exactly against `Document.properties.type`; `key`\n * matches against the candidate's `title` OR any entry in\n * `properties.aliases`. `vaults` optionally narrows the search to a\n * subset of registered vaults; omitting it falls back to \"all configured\n * vaults\" (mirrors the recall convention).\n */\nexport interface AssembleDossierArgs {\n type: string;\n key: string;\n vaults?: string[];\n}\n\n/**\n * One linked document — a full `CitationPacket` (8 required fields per\n * D-01, properties always populated) plus dossier-specific extras:\n *\n * - `status` (optional) — denormalized from `properties.status`;\n * surfaced as a top-level field for agent convenience (saves a\n * properties lookup in the common case).\n * - `superseded_by` (optional) — denormalized from\n * `properties.superseded_by` for the same reason.\n * - `relation` — edge type. Always `\"wikilink\"` in v2.0.0 (the v1\n * wikilinks table is the only edge source); Phase 4 widens to the\n * full `Edge.type` enum.\n *\n * Intersection (not redefinition) of `CitationPacket` — `linked.properties`\n * is REQUIRED and always a `Record<string, unknown>`, never `undefined`.\n */\nexport type LinkedDocument = CitationPacket & {\n status?: string;\n superseded_by?: string;\n relation: \"wikilink\";\n};\n\n/**\n * Anchor — citation packet for the resolved document plus the same\n * `status` / `superseded_by` denormalized extras. `null` when no\n * matching anchor was found (see `error`).\n */\nexport type DossierAnchor = CitationPacket & {\n status?: string;\n superseded_by?: string;\n};\n\nexport interface DossierError {\n code: \"no_matching_anchor_document\";\n type: string;\n key: string;\n}\n\n/**\n * Structured dossier result. `anchor === null` iff `error !== null`.\n */\nexport interface DossierResult {\n anchor: DossierAnchor | null;\n linked_documents: LinkedDocument[];\n property_rollups: {\n linked_count: number;\n /** Bucketed by `properties.type` per linked doc. Missing → `\"unknown\"`. */\n linked_types: Record<string, number>;\n /** Bucketed by `properties.status` per linked doc. Missing → `\"unknown\"`. */\n status_distribution: Record<string, number>;\n };\n /** `null` on success; structured error code on no-match (D-04). */\n error: DossierError | null;\n}\n\n// ─── helpers ─────────────────────────────────────────────────────────────────\n\nfunction emptyResult(args: AssembleDossierArgs): DossierResult {\n return {\n anchor: null,\n linked_documents: [],\n property_rollups: {\n linked_count: 0,\n linked_types: {},\n status_distribution: {},\n },\n error: {\n code: \"no_matching_anchor_document\",\n type: args.type,\n key: args.key,\n },\n };\n}\n\n/**\n * Sort a `Record<string, number>` by key (alphabetical) for\n * deterministic JSON serialization. Returns a fresh object; does not\n * mutate the input.\n */\nfunction sortByKey(counts: Record<string, number>): Record<string, number> {\n const keys = Object.keys(counts).sort();\n const out: Record<string, number> = {};\n for (const k of keys) {\n out[k] = counts[k] as number;\n }\n return out;\n}\n\n/**\n * Read aliases from a parsed frontmatter object. Returns the array of\n * string entries (ignoring non-string entries). `null` when the\n * `aliases` key is missing or not an array.\n */\nfunction readAliases(props: Record<string, unknown>): string[] {\n const raw = props.aliases;\n if (!Array.isArray(raw)) return [];\n const out: string[] = [];\n for (const v of raw) {\n if (typeof v === \"string\") out.push(v);\n }\n return out;\n}\n\n/**\n * Sort-key string for the deterministic tiebreak. NOT a real DocId —\n * just a stable, vault-scoped, lex-orderable identifier used inside\n * `findAnchorCandidate` / `findAnchorAcrossVaults`. The scheme prefix\n * is fixed at `\"vault\"` so the sort key is identical across adapters\n * (sort order is the contract, not the prefix). The actual minted\n * DocId for `readDocument` is derived from the resolving adapter's\n * scheme — see `schemeFromSource` and the call sites in\n * `assembleDossier`.\n */\nfunction noteSortKey(vaultName: string, notePath: string): string {\n return `vault://${vaultName}/${notePath}`;\n}\n\n/**\n * Extract the scheme portion of a SourceConnector.handle (e.g.\n * `\"obsidian-fs\"` from `\"obsidian-fs://my-vault\"`). Used to mint\n * adapter-correct DocIds in `assembleDossier` per ASM-12 source-\n * neutrality (Phase 3 / 03-07): the stub adapter publishes\n * `stub://memory` and dossier MUST construct linked-document DocIds\n * with the matching scheme so `StubSource.readDocument(id)` resolves.\n * Pre-03-07 the scheme was hardcoded to `\"obsidian-fs\"` which silently\n * broke non-Obsidian adapters.\n */\nfunction schemeFromSource(source: SourceConnector): string {\n const parts = source.handle.split(\"://\");\n return parts[0] ?? \"obsidian-fs\";\n}\n\n// ─── candidate resolution (anchor) ──────────────────────────────────────────\n\ninterface AnchorCandidate {\n vaultName: string;\n notePath: string;\n title: string;\n /** Lex tiebreak key — `<title>\u0000<doc_id_string>` for total order. */\n sortKey: string;\n}\n\n/**\n * Walk the candidate set returned by `query_frontmatter({type: args.type})`\n * and return the FIRST candidate (per lex tiebreak) whose `title === args.key`\n * OR whose `properties.aliases` contains `args.key`.\n *\n * Returns `null` when no candidate matches. The candidate set is\n * already type-filtered by SQL; this loop is just the key match.\n */\nfunction findAnchorCandidate(vault: Vault, args: AssembleDossierArgs): AnchorCandidate | null {\n // SQL-level type filter via the existing query_frontmatter path.\n // This reads `notes.frontmatter` (JSON column) with JSON1 extract.\n const rows = queryFrontmatter(vault, {\n where: { type: args.type },\n limit: 1000,\n });\n if (rows.length === 0) return null;\n\n const matches: AnchorCandidate[] = [];\n for (const row of rows) {\n // queryFrontmatter only returns rows with non-null frontmatter, but\n // parse defensively — corrupt JSON has bitten us before.\n let props: Record<string, unknown> = {};\n if (row.frontmatter !== null) {\n try {\n const parsed = JSON.parse(row.frontmatter);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n props = parsed as Record<string, unknown>;\n }\n } catch {\n // Corrupt frontmatter — skip this candidate; do not throw.\n continue;\n }\n }\n\n const titleMatch = row.title === args.key;\n const aliasMatch = readAliases(props).includes(args.key);\n if (!titleMatch && !aliasMatch) continue;\n\n matches.push({\n vaultName: vault.config.name,\n notePath: row.path,\n title: row.title,\n sortKey: `${row.title}\u0000${noteSortKey(vault.config.name, row.path)}`,\n });\n }\n\n if (matches.length === 0) return null;\n // Deterministic tiebreak: sort by (title, doc_id) ASC, take first.\n matches.sort((a, b) => (a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey ? 1 : 0));\n return matches[0] ?? null;\n}\n\n/**\n * Across the candidate vault set, find the FIRST anchor by lex\n * tiebreak across all vaults. The cross-vault tiebreak uses the same\n * `(title, doc_id)` rule — the `doc_id` carries the vault name as the\n * authority, so cross-vault ordering is well-defined.\n */\nfunction findAnchorAcrossVaults(\n vaults: Vault[],\n args: AssembleDossierArgs,\n): AnchorCandidate | null {\n const matches: AnchorCandidate[] = [];\n for (const vault of vaults) {\n const c = findAnchorCandidate(vault, args);\n if (c) matches.push(c);\n }\n if (matches.length === 0) return null;\n matches.sort((a, b) => (a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey ? 1 : 0));\n return matches[0] ?? null;\n}\n\n// ─── public entry point ─────────────────────────────────────────────────────\n\n/**\n * Resolve a `{type, key}` pair to a structured dossier. See the file\n * header for the full algorithm.\n */\nexport async function assembleDossier(\n deps: AssembleDossierDeps,\n args: AssembleDossierArgs,\n): Promise<DossierResult> {\n // 1) Build the candidate vault list. Throws on unknown vault names\n // — the server wraps the exception in errorResponse() at the\n // dispatch boundary.\n const vaults: Vault[] = [];\n if (args.vaults && args.vaults.length > 0) {\n for (const name of args.vaults) {\n vaults.push(deps.manager.require(name));\n }\n } else {\n for (const v of deps.manager.list()) {\n vaults.push(v);\n }\n }\n if (vaults.length === 0) return emptyResult(args);\n\n // 2) Resolve the anchor (type-match + key-match, deterministic\n // tiebreak across all candidate vaults).\n const anchorCandidate = findAnchorAcrossVaults(vaults, args);\n if (anchorCandidate === null) return emptyResult(args);\n\n // 3) Hydrate the anchor Document via the SourceConnector seam.\n const anchorVault = vaults.find((v) => v.config.name === anchorCandidate.vaultName);\n if (anchorVault === undefined) return emptyResult(args);\n const anchorSource = deps.sourceConnectorFor(anchorCandidate.vaultName);\n // ASM-12 source-neutrality: derive scheme from the resolving adapter's\n // handle so non-Obsidian connectors (stub, future Notion) produce\n // adapter-correct DocIds rather than always emitting 'obsidian-fs://'.\n const anchorScheme = schemeFromSource(anchorSource);\n const anchorDocId = formatDocId(\n anchorScheme,\n anchorCandidate.vaultName,\n anchorCandidate.notePath,\n );\n let anchorDoc: Document;\n try {\n anchorDoc = await anchorSource.readDocument(anchorDocId);\n } catch {\n // The candidate row was indexed, but the file was deleted between\n // index and assembly. Treat as no-match — the structured error\n // surfaces \"no anchor document\" honestly without exposing the race.\n return emptyResult(args);\n }\n const anchorPacket: DossierAnchor = withPropertyExtras(\n toCitationPacket(anchorDoc, displayUrlFor(anchorDocId, anchorSource)),\n );\n\n // 4) Read backlinks via the Phase 1 graph layer. `listBacklinks`\n // looks the note up by path inside the anchor's vault, then walks\n // the v1 `wikilinks` table for source notes pointing to it. In\n // v2.0.0 every edge in that table is a wikilink; Phase 4 will\n // widen the surface to typed edges.\n let backlinkRows: ReturnType<typeof listBacklinks>;\n try {\n backlinkRows = listBacklinks(anchorVault, anchorCandidate.notePath);\n } catch {\n // listBacklinks throws if the note isn't indexed — same race\n // window as the readDocument try/catch above. Surface no-match.\n return emptyResult(args);\n }\n\n // 5) Hydrate each backlink: read the source Document via the\n // SourceConnector, build a citation packet, attach\n // `relation: \"wikilink\"` plus the denormalized extras.\n const linkedDocuments: LinkedDocument[] = [];\n for (const bl of backlinkRows) {\n const linkedDocId = formatDocId(anchorScheme, anchorCandidate.vaultName, bl.sourcePath);\n let linkedDoc: Document;\n try {\n linkedDoc = await anchorSource.readDocument(linkedDocId);\n } catch {\n // A stale backlink (source file deleted between index and read)\n // is harmless; silently drop. Same defensive posture as recall.\n continue;\n }\n const packet: CitationPacket = toCitationPacket(\n linkedDoc,\n displayUrlFor(linkedDocId, anchorSource),\n );\n const withExtras = withPropertyExtras(packet);\n // PHASE-4-WIDEN: v2.0.0 reads from the v1 wikilinks table, which\n // stores only `\"wikilink\"` edges. When GRA-04 introduces typed\n // edges, this hardcoded literal becomes `edge.type` and the\n // `relation` field in `LinkedDocument` widens to `EdgeType`.\n linkedDocuments.push({\n ...withExtras,\n relation: \"wikilink\" as const,\n });\n }\n\n // 6) Compute rollups in a single pass. Keys are sorted\n // alphabetically before return for deterministic JSON output.\n const linked_types: Record<string, number> = {};\n const status_distribution: Record<string, number> = {};\n for (const linked of linkedDocuments) {\n const type = typeof linked.properties.type === \"string\" ? linked.properties.type : \"unknown\";\n linked_types[type] = (linked_types[type] ?? 0) + 1;\n const status =\n typeof linked.properties.status === \"string\" ? linked.properties.status : \"unknown\";\n status_distribution[status] = (status_distribution[status] ?? 0) + 1;\n }\n\n return {\n anchor: anchorPacket,\n linked_documents: linkedDocuments,\n property_rollups: {\n linked_count: linkedDocuments.length,\n linked_types: sortByKey(linked_types),\n status_distribution: sortByKey(status_distribution),\n },\n error: null,\n };\n}\n","/**\n * `getDocumentBundle` — the ASM-01 controller (Phase 3, Plan 03-04).\n *\n * Returns the document-tree retrieval surface that composes every other\n * Phase 3 read into one response:\n *\n * - `anchor` — citation packet (8 required D-01 fields) for the\n * anchor document, plus optional `status` /\n * `superseded_by` denormalized extras (ASM-06) read\n * from `properties` via the same hydration path\n * extended by Plan 03-05.\n * - `outline` — the section tree from `buildOutlineTree`\n * (re-used from 03-02 — NOT duplicated).\n * - `backlinks` — citation packets + `property_snippet` (≤200 chars\n * of plain-text body from the linking doc) +\n * `relation: \"wikilink\"`. In v2.0.0 the v1\n * `wikilinks` table is the only edge source; Phase\n * 4 widens `relation` additively.\n * - `forward_links` — citation packets + `property_snippet` +\n * `relation: \"wikilink\"`.\n * - `recent_edits` — up to 10 most recent `audit_log` entries for the\n * anchor's CURRENT note path, mapped to\n * `BundleRecentEdit`.\n *\n * # Citation packet contract (M1 fix — single source of truth)\n *\n * Every packet (anchor, backlinks, forward_links) is built via\n * `toCitationPacket()` from `src/memory/citation-packet.ts`. The bundle\n * does NOT redefine the 8-field shape. Bundle-specific extras\n * (`property_snippet`, `relation`, `status?`, `superseded_by?`) are\n * intersected onto `CitationPacket` (`CitationPacket & { ...extras }`).\n *\n * `CitationPacket.properties` is REQUIRED (`Record<string, unknown>`,\n * always populated by the mapper; `{}` when the doc has no frontmatter).\n * Bundle entries therefore never carry `properties: undefined`.\n *\n * # `depth: 1` semantics (only value accepted in v2.0.0)\n *\n * One-hop backlinks / forward links. The Zod schema in `tool-registry.ts`\n * pins `depth` to `z.literal(1).optional().default(1)` — higher values\n * are not accepted today. Phase 4 may widen additively.\n *\n * # Recent-edits rename-history limitation (M3 — documented, no fix)\n *\n * `getAuditLog({notePath})` (see `src/audit/audit.ts:93-97`) looks up\n * entries by CURRENT note path. Pre-rename audit_log rows are keyed on\n * `note_id` internally, so the path-keyed lookup misses them. If a doc\n * was renamed from `foo.md` → `bar.md`, asking\n * `get_document_bundle({doc_id: \"obsidian-fs://vault/bar.md\"})`\n * surfaces only the post-rename edits.\n *\n * Why this is acceptable for v2.0.0:\n * - Phase 3 is read-side; no new write path widens the rename problem.\n * - The audit_log retains pre-rename rows for forensic purposes;\n * they're queryable directly via `audit_log({note_path})` for the\n * OLD path until the note row is purged.\n * - The collaborative-vault domain (\"tolerating collaborators\n * renaming notes\") names this as a design pressure but does not\n * require Phase 3 to surface pre-rename history in `recent_edits`.\n *\n * Phase 4 widens this — the graph layer will centralize\n * `doc_id → note_id` resolution and the bundle can switch to the\n * `note_id`-keyed audit_log lookup.\n *\n * # Adapter-seam discipline (ADR-002 I-1..I-7)\n *\n * The controller is pure: no `node:fs`, no `node:path`, no\n * `gray-matter`, no `chokidar`. All `Document` reads route through the\n * injected `SourceConnector` (`readDocument`). SQLite namespace access\n * (`vault.db.notes`, `vault.db.sections`, `vault.db.chunks`,\n * `vault.db.audit`, `vault.db.wikilinks`) is L0 substrate, owned by the\n * existing query layer — fine.\n */\n\nimport { decomposeDocId, formatDocId, parseDocId } from \"../adapters/registry.js\";\nimport type { SourceConnector } from \"../adapters/source/types.js\";\nimport { getAuditLog } from \"../audit/audit.js\";\nimport { listBacklinks, listForwardLinks } from \"../graph/graph.js\";\nimport type { EdgeType } from \"../graph/graph.js\";\nimport {\n type CitationPacket,\n displayUrlFor,\n toCitationPacket,\n withPropertyExtras,\n} from \"../memory/citation-packet.js\";\nimport { DocNotFoundError } from \"./outline.js\";\nimport { buildOutlineTree } from \"./outline.js\";\nimport type { OutlineNode } from \"./types.js\";\nimport type { BlockNode, ChunkRow, Document, SectionRow } from \"../types.js\";\nimport type { Vault, VaultManager } from \"../vault/index.js\";\n\n/**\n * Maximum number of audit-log rows surfaced in `recent_edits`.\n * Plan §\"Acceptance criteria\" — `recent_edits` length ≤ 10 even when\n * the audit log has more entries.\n */\nconst RECENT_EDITS_LIMIT = 10;\n\n/**\n * Maximum length (in chars) of the body plain-text snippet attached to\n * each backlink / forward-link entry. Plan §\"Property snippet\":\n * \"first 200 chars of plain-text-rendered body.\"\n */\nconst PROPERTY_SNIPPET_MAX = 200;\n\n/**\n * Injected dependencies for `getDocumentBundle`. Mirrors `GetOutlineDeps`\n * / `AssembleDossierDeps` so production wiring + unit tests share one\n * shape.\n */\nexport interface GetDocumentBundleDeps {\n manager: VaultManager;\n sourceConnectorFor: (vaultName: string) => SourceConnector;\n}\n\n/**\n * Validated input shape for `get_document_bundle`. Matches the Zod\n * `GetDocumentBundleArgs` schema in `src/tool-registry.ts`.\n */\nexport interface GetDocumentBundleArgs {\n /** Opaque DocId — `<scheme>://<authority>/<resource>`. */\n doc_id: string;\n /**\n * Depth of the link walk. v2.0.0 accepts ONLY `1`. The Zod schema\n * pins the literal; this field is here for forward compatibility.\n */\n depth?: 1;\n /** Optional vault filter; usually omitted (the DocId names a vault). */\n vaults?: string[];\n}\n\n/**\n * Anchor citation packet — full 8-field `CitationPacket` plus the\n * optional ASM-06 denormalized extras (`status`, `superseded_by`).\n * Read from `Document.properties` via the same hydration path Plan\n * 03-05 extends in `search_hybrid` and `recall`.\n */\nexport type BundleAnchor = CitationPacket & {\n status?: string;\n superseded_by?: string;\n};\n\n/**\n * One backlink entry — full citation packet + bundle-specific extras.\n *\n * - `property_snippet` — first ≤200 chars of the linking doc's\n * plain-text body (frontmatter stripped — the\n * `Document` shape already separates\n * `properties` from `blocks`, so no manual\n * frontmatter strip is needed).\n * - `relation` — `EdgeType` (Phase 4 / 04-01 / D-04). Reads\n * route through `vault.db.edges` (post-04-01\n * backfill) and `bl.type` / `fl.type` carry\n * the actual edge type. Post-backfill every\n * row is `'wikilink'`; Plan 04-02 widens to\n * the other three `Edge.type` literals once\n * the indexer populates them. COMPLETED\n * Phase 4 / 04-01.\n *\n * `heading_path` is inherited from `CitationPacket` and is `[]` for\n * document-level links per `<specifics>` (only outline nodes carry a\n * non-empty heading_path).\n */\nexport type BacklinkEntry = CitationPacket & {\n property_snippet: string;\n // ── Phase 4 / 04-01 / GRA-04 (D-01, D-04): widen `relation` to EdgeType ──\n //\n // Strict widening from the prior `\"wikilink\"` literal. Post-backfill all\n // existing rows still carry `\"wikilink\"`; Plan 04-02 starts populating\n // the other three types in the same column.\n relation: EdgeType;\n};\n\n/**\n * One forward-link entry — same shape as `BacklinkEntry`. Distinct type\n * alias for clarity at call sites.\n */\nexport type ForwardLinkEntry = CitationPacket & {\n property_snippet: string;\n /** Phase 4 / 04-01 (D-04) — widened from `\"wikilink\"` to `EdgeType`. */\n relation: EdgeType;\n};\n\n/**\n * One row from `recent_edits`. Mapped from `AuditLogEntry`:\n *\n * - `at` — epoch ms.\n * - `op` — create | update | delete.\n * - `client_id` — `null` for user-originated writes, real string\n * for agent writes (a sink-route caller plus a\n * `client_id` argument to write tools).\n * - `is_memory_sink_write` — Plan 02-06 (MEM-08) discriminator.\n * Surfaced ONLY when `true` (optional field) so the\n * bundle wire shape stays compact for the common\n * non-memory case.\n *\n * `recent_edits` is keyed by the anchor's CURRENT note path; pre-rename\n * history is not surfaced. See the file header §\"Recent-edits\n * rename-history limitation\".\n */\nexport interface BundleRecentEdit {\n at: number;\n op: \"create\" | \"update\" | \"delete\";\n client_id: string | null;\n is_memory_sink_write?: boolean;\n}\n\n/**\n * Wire shape of the `get_document_bundle({doc_id})` MCP tool response.\n */\nexport interface BundleResult {\n anchor: BundleAnchor;\n outline: OutlineNode[];\n backlinks: BacklinkEntry[];\n forward_links: ForwardLinkEntry[];\n recent_edits: BundleRecentEdit[];\n}\n\n// ─── helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Render a `BlockNode[]` to plain text and truncate to\n * `PROPERTY_SNIPPET_MAX` chars. The `Document` block tree already\n * separates `properties` (frontmatter) from `blocks` (body), so no\n * frontmatter strip is needed — we just project block text.\n *\n * Adapter contract (`bodyShape: \"flat-text\"`, see\n * `src/adapters/capabilities.ts`): the obsidian-fs adapter emits a\n * single `{kind: \"paragraph\", text: body}` block, so the common case is\n * trivial. Other block kinds project their `text` / `items` content;\n * `section` blocks recurse into their nested `blocks`. Unknown kinds\n * project as `\"\"` (defensive — the closed union narrows this away at\n * the type level today).\n */\nfunction bodyPlainText(blocks: BlockNode[]): string {\n const parts: string[] = [];\n for (const b of blocks) {\n switch (b.kind) {\n case \"paragraph\":\n case \"code\":\n parts.push(b.text);\n break;\n case \"heading\":\n parts.push(b.text);\n break;\n case \"list\":\n parts.push(b.items.join(\" \"));\n break;\n case \"section\":\n // Recurse into the section's nested blocks. The section's own\n // heading is NOT projected here — it lives in `heading_path`,\n // which is presentation metadata, not body content.\n parts.push(bodyPlainText(b.blocks));\n break;\n default:\n // TypeScript narrows away the `default` for the closed union;\n // this is dead-code defense for future widenings.\n break;\n }\n }\n const text = parts.join(\" \").trim();\n if (text.length <= PROPERTY_SNIPPET_MAX) return text;\n return text.slice(0, PROPERTY_SNIPPET_MAX);\n}\n\n// ─── public entry point ─────────────────────────────────────────────────────\n\n/**\n * Assemble the document bundle for a `doc_id`. See file header for the\n * full algorithm.\n *\n * Throws `DocNotFoundError` (caught by the server dispatch and wrapped\n * into the `{error: \"doc_not_found\", doc_id}` payload) on:\n * - Malformed `doc_id`.\n * - Unknown vault.\n * - `vaults` filter that excludes the DocId's vault.\n * - Missing note row (note not indexed, OR deleted between catch-up\n * and this call).\n * - SourceConnector read failure on the anchor doc.\n */\nexport async function getDocumentBundle(\n deps: GetDocumentBundleDeps,\n args: GetDocumentBundleArgs,\n): Promise<BundleResult> {\n // 1) Validate-decompose the DocId. `parseDocId` throws on malformed\n // input — surface as `doc_not_found` (callers gave us a bad id).\n let parsed: { scheme: string; authority: string; resource: string };\n try {\n const docId = parseDocId(args.doc_id);\n parsed = decomposeDocId(docId);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n const { scheme: anchorScheme, authority: vaultName, resource: path } = parsed;\n\n // Optional vault-filter narrowing. The DocId already names a vault;\n // the filter exists for callers asserting a known tenant boundary.\n if (args.vaults && args.vaults.length > 0 && !args.vaults.includes(vaultName)) {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 2) Resolve the Vault. `manager.require` throws on unknown — map to\n // DocNotFoundError so the wire response is consistent with\n // get_outline.\n let vault: Vault;\n try {\n vault = deps.manager.require(vaultName);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 3) Look up the note row by path. Missing row → doc_not_found.\n const noteRow = vault.db.notes.getByPath(path);\n if (!noteRow) {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // 4) Read the anchor Document via the SourceConnector seam. We use\n // the canonical packet helper so display-URL resolution matches\n // recall + the rest of Phase 3 byte-for-byte.\n const source = deps.sourceConnectorFor(vaultName);\n const docId = parseDocId(args.doc_id);\n let anchorDoc: Document;\n try {\n anchorDoc = await source.readDocument(docId);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n // `withPropertyExtras` returns `CitationPacket & {status?; superseded_by?}`,\n // structurally identical to `BundleAnchor`; the annotation pins the type.\n const anchorPacket: BundleAnchor = withPropertyExtras(\n toCitationPacket(anchorDoc, displayUrlFor(docId, source)),\n );\n\n // 5) Build the outline tree via 03-02's helper. Re-use, do NOT\n // duplicate. Sections are returned in parent-NULL-first order,\n // and chunks are pre-loaded once for all sections (see\n // outline.ts §\"7-prep\" note).\n const sectionRows: SectionRow[] = vault.db.sections.getByNote(noteRow.id);\n const allChunks: ChunkRow[] = vault.db.chunks.getByNote(noteRow.id);\n const outline = buildOutlineTree(sectionRows, allChunks);\n\n // 6) Read backlinks via the Phase 1 graph layer. In v2.0.0 every\n // edge in the v1 `wikilinks` table is a wikilink; Phase 4 widens\n // to typed edges. `listBacklinks` throws if the anchor note is\n // unindexed — we already verified its existence in step 3, so\n // any throw here is a genuine race we map to `doc_not_found`.\n let backlinkRows: ReturnType<typeof listBacklinks>;\n try {\n backlinkRows = listBacklinks(vault, path);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n\n // Hydrate each backlink — read the source `Document` via the\n // SourceConnector (single adapter-seam read), build a citation\n // packet, attach `property_snippet` (first 200 chars of plain-text\n // body) and `relation: \"wikilink\"`. Stale backlink rows (source\n // file deleted between index and read) are silently dropped — same\n // defensive posture as dossier + recall.\n const backlinks: BacklinkEntry[] = [];\n for (const bl of backlinkRows) {\n const sourceDocId = formatDocId(anchorScheme, vaultName, bl.sourcePath);\n let linkedDoc: Document;\n try {\n linkedDoc = await source.readDocument(sourceDocId);\n } catch {\n continue;\n }\n const packet = toCitationPacket(linkedDoc, displayUrlFor(sourceDocId, source));\n // PHASE-4-WIDEN: v1 wikilinks-only graph reads now route through the\n // typed-edges substrate (`vault.db.edges`, Plan 04-01). `bl.type`\n // sources the actual edge type per row; post-backfill this is\n // `'wikilink'` for every row, and Plan 04-02 starts producing\n // mention / frontmatter-ref / hyperlink. COMPLETED Phase 4 / 04-01.\n backlinks.push({\n ...packet,\n property_snippet: bodyPlainText(linkedDoc.blocks),\n relation: bl.type,\n });\n }\n\n // 7) Read forward links via the symmetric graph helper. We pass\n // `includeBroken: false` because broken links (`resolved: false`)\n // carry no target note row and cannot be hydrated via the\n // SourceConnector — there's no document to cite. The user can\n // still discover broken outbound links via `find_broken_links` /\n // `list_forward_links`. Phase 4 may surface them as a separate\n // `broken_forward_links` array if the use case emerges.\n let forwardLinkRows: ReturnType<typeof listForwardLinks>;\n try {\n forwardLinkRows = listForwardLinks(vault, path, /* includeBroken */ false);\n } catch {\n throw new DocNotFoundError(args.doc_id);\n }\n\n const forward_links: ForwardLinkEntry[] = [];\n for (const fl of forwardLinkRows) {\n const targetDocId = formatDocId(anchorScheme, vaultName, fl.targetPath);\n let linkedDoc: Document;\n try {\n linkedDoc = await source.readDocument(targetDocId);\n } catch {\n continue;\n }\n const packet = toCitationPacket(linkedDoc, displayUrlFor(targetDocId, source));\n forward_links.push({\n ...packet,\n property_snippet: bodyPlainText(linkedDoc.blocks),\n // PHASE-4-WIDEN — see backlinks loop above. COMPLETED Phase 4 / 04-01.\n relation: fl.type,\n });\n }\n\n // 8) Recent edits — capped at RECENT_EDITS_LIMIT (10). `getAuditLog`\n // returns entries in DB-default order (newest first by id DESC;\n // see `src/db/queries/audit.ts` listWrites SQL). Map each entry\n // onto `BundleRecentEdit`, surfacing only the fields the bundle\n // documents — keeps the wire shape stable as the underlying\n // `AuditLogEntry` grows.\n //\n // Rename-history caveat: `getAuditLog({notePath})` is keyed on\n // the current note row; pre-rename entries are not surfaced. See\n // the file header §\"Recent-edits rename-history limitation\".\n const auditEntries = getAuditLog({\n vault,\n notePath: path,\n limit: RECENT_EDITS_LIMIT,\n });\n const recent_edits: BundleRecentEdit[] = auditEntries.map((e) => {\n const out: BundleRecentEdit = {\n at: e.at,\n op: e.op,\n client_id: e.clientId,\n };\n // Only surface the flag when truthy — keeps the bundle wire\n // shape compact for the common non-memory write case.\n if (e.is_memory_sink_write) out.is_memory_sink_write = true;\n return out;\n });\n\n // 9) Assemble. The bundle response does NOT carry a top-level\n // `source_handle` — the anchor citation packet already exposes\n // it as part of its 8-field shape, and every backlink /\n // forward-link entry carries its own (same vault in v2.0.0, but\n // Phase 4 cross-adapter graph walks may surface heterogeneous\n // source handles).\n return {\n anchor: anchorPacket,\n outline,\n backlinks,\n forward_links,\n recent_edits,\n };\n}\n","/**\n * Phase 3 — `src/assembly/` barrel.\n *\n * The assembly layer composes the section-identity substrate (`src/sections/`,\n * landed in 03-01) into higher-level reading tools:\n *\n * - 03-02: `get_outline` — nested section tree.\n * - 03-03: `search_sections` — section-level retrieval.\n * - 03-04: `get_bundle` — section-window assembly.\n * - 03-05: search_hybrid rescore (authority / staleness).\n * - 03-06: `assemble_dossier` — multi-bundle synthesis with property rollups.\n *\n * Adapter-seam discipline (per 03-CONTEXT.md, enforced by\n * `scripts/lint-adapters.sh`): nothing under `src/assembly/` imports\n * `fs`, `gray-matter`, `chokidar`, or `path.*`. Document reads go\n * through the injected `SourceConnector` seam.\n */\n\nexport { assembleDossier } from \"./dossier.js\";\nexport type {\n AssembleDossierArgs,\n AssembleDossierDeps,\n DossierAnchor,\n DossierError,\n DossierResult,\n LinkedDocument,\n} from \"./dossier.js\";\nexport { getDocumentBundle } from \"./bundle.js\";\nexport type {\n BacklinkEntry,\n BundleAnchor,\n BundleRecentEdit,\n BundleResult,\n ForwardLinkEntry,\n GetDocumentBundleArgs,\n GetDocumentBundleDeps,\n} from \"./bundle.js\";\nexport { getOutline, type GetOutlineDeps } from \"./outline.js\";\nexport type { OutlineNode, OutlineResult, GetOutlineArgs } from \"./types.js\";\n","/**\n * Assembly-domain MCP handler factory.\n *\n * Tools: get_outline, search_sections, assemble_dossier, get_document_bundle.\n *\n * Extracted verbatim from the inline `handlers` literal in `src/server.ts`.\n * Behavior-neutral — each arrow wires args to the same assembly-controller\n * call with the same injected seam closures.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports. All document\n * reads + display-URL minting flow through the adapter registry seam.\n */\n\nimport { formatDocId, parseSourceHandle } from \"../../adapters/registry.js\";\nimport { getOutline } from \"../../assembly/outline.js\";\nimport { searchSections } from \"../../assembly/search-sections.js\";\nimport { assembleDossier, getDocumentBundle } from \"../../assembly/index.js\";\nimport { hybridSearch } from \"../../search/index.js\";\nimport type { Vault } from \"../../vault/index.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\nexport function makeAssemblyHandlers(deps: HandlerDeps): Partial<Record<ToolName, Handler>> {\n const { manager, ollama, defaultModel, adapterRegistry } = deps;\n return {\n // ── Phase 3 assembly tools (Plan 03-02 / ASM-02) ───────────────────────\n get_outline: async (a) => {\n const p = a as { doc_id: string; vaults?: string[] };\n return getOutline(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n },\n\n // ── Phase 3 assembly tools (Plan 03-03) ──────────────────────────────────\n search_sections: async (a) => {\n const p = a as {\n query: string;\n limit?: number;\n vaults?: string[];\n recency_weight?: number;\n authority_weight?: number;\n include_superseded?: boolean;\n };\n // Resolve target vaults: callers may scope to a subset; default to\n // all configured vaults (mirrors search_hybrid's behavior).\n const allVaults = manager.list();\n const targetVaults: Vault[] = p.vaults\n ? p.vaults.map((name) => manager.require(name))\n : allVaults;\n\n const results = await searchSections(\n {\n searchHybrid: async (input) =>\n hybridSearch({\n query: input.query,\n embeddingModel: defaultModel,\n ollama,\n vaults: input.vaults\n ? input.vaults.map((name) => manager.require(name))\n : targetVaults,\n topK: input.topK,\n rrfK: 60,\n includeBreakdown: false,\n }),\n sectionForHit: (vaultName, notePath, chunkIdx) => {\n // Look up via the originating vault's DB. The mapping is\n // (notePath → noteId) → (noteId, chunkIdx → chunkId) →\n // findContainingChunk. Returns null on any miss (stale row\n // or pre-migration-010 chunk) so the controller drops it.\n let vault: Vault;\n try {\n vault = manager.require(vaultName);\n } catch {\n return null;\n }\n const note = vault.db.notes.getByPath(notePath);\n if (!note) return null;\n const chunks = vault.db.chunks.getByNote(note.id);\n const chunk = chunks.find((c) => c.idx === chunkIdx);\n if (!chunk) return null;\n const section = vault.db.sections.findContainingChunk(note.id, chunk.id);\n if (!section) return null;\n let headingPath: string[];\n try {\n const parsed = JSON.parse(section.heading_path);\n headingPath = Array.isArray(parsed) ? (parsed as string[]) : [];\n } catch {\n headingPath = [];\n }\n return {\n noteId: note.id,\n anchor: section.anchor,\n headingPath,\n // Sections with a NULL chunk_id_first have been filtered out\n // by findContainingChunk (it requires non-NULL bounds), so\n // chunk_id_first is guaranteed non-null here. Fall back to\n // MAX_SAFE_INTEGER defensively for the tie-break sort.\n chunkIdFirst: section.chunk_id_first ?? Number.MAX_SAFE_INTEGER,\n };\n },\n readDocument: async (vaultName, notePath) => {\n const docId = formatDocId(\"obsidian-fs\", vaultName, notePath);\n return adapterRegistry\n .resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`))\n .readDocument(docId);\n },\n displayUrlFor: (docId, vaultName) => {\n const source = adapterRegistry.resolveSource(\n parseSourceHandle(`obsidian-fs://${vaultName}`),\n );\n return source.formatDisplayUrl?.(docId) ?? docId;\n },\n },\n {\n query: p.query,\n limit: p.limit ?? 10,\n ...(p.vaults !== undefined ? { vaults: p.vaults } : {}),\n ...(p.recency_weight !== undefined ? { recency_weight: p.recency_weight } : {}),\n ...(p.authority_weight !== undefined ? { authority_weight: p.authority_weight } : {}),\n ...(p.include_superseded !== undefined\n ? { include_superseded: p.include_superseded }\n : {}),\n },\n );\n return { results, count: results.length };\n },\n\n // ── Phase 3 assembly tools (Plan 03-06) ────────────────────────────────\n assemble_dossier: async (a) => {\n const p = a as { type: string; key: string; vaults?: string[] };\n return assembleDossier(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n },\n\n // ── Phase 3 assembly tools (Plan 03-04 / ASM-01) ───────────────────────\n get_document_bundle: async (a) => {\n const p = a as { doc_id: string; depth?: 1; vaults?: string[] };\n return getDocumentBundle(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n },\n };\n}\n","/**\n * Contracts-domain MCP handler factory.\n *\n * Tools: register_contracts_as_tools, describe_contract, instantiate_contract.\n *\n * Extracted verbatim from the inline `handlers` literal in `src/server.ts`.\n * Behavior-neutral. Unlike the other domains, these handlers depend on three\n * serve()-local closures (`resolveContractVault`, `instantiateHandler`,\n * `buildInstantiateDeps`) that capture bootstrap state not present on\n * `HandlerDeps`. Those are passed in via the `ContractHelpers` parameter so\n * the closures stay defined in `serve()` and the call shapes are identical.\n *\n * # Adapter-seam discipline\n *\n * No node:path / node:fs / chokidar / gray-matter imports.\n */\n\nimport {\n describeContract,\n instantiateContract,\n syncAutoRegistered,\n} from \"../../contracts/index.js\";\nimport type { InstantiateDeps } from \"../../contracts/index.js\";\nimport type { Vault } from \"../../vault/index.js\";\nimport type { ToolName } from \"../../tool-registry.js\";\nimport type { Handler, HandlerDeps } from \"../deps.js\";\n\n/**\n * Result of resolving the target vault for describe/instantiate. Verbatim\n * from `serve()`'s local `resolveContractVault`.\n */\nexport type ResolveContractVaultResult =\n | { ok: true; vault: Vault }\n | { ok: false; reason: \"ambiguous_vault\"; available_vaults: string[] }\n | { ok: false; reason: \"unknown_vault\"; vault: string };\n\n/**\n * The three serve()-local closures the contract handlers depend on. They\n * capture bootstrap state (`manager`, `adapterRegistry`, `peerMcpRegistry`,\n * per-vault deps, baseline-verb thunks) that is not on `HandlerDeps`, so\n * they are injected rather than reconstructed.\n */\nexport interface ContractHelpers {\n resolveContractVault: (vaultArg: string | undefined) => ResolveContractVaultResult;\n instantiateHandler: (name: string, args: unknown) => Promise<unknown>;\n buildInstantiateDeps: (vault: Vault) => InstantiateDeps;\n}\n\nexport function makeContractsHandlers(\n deps: HandlerDeps,\n helpers: ContractHelpers,\n): Partial<Record<ToolName, Handler>> {\n const { manager, server, config, contractRegistries } = deps;\n const { resolveContractVault, instantiateHandler, buildInstantiateDeps } = helpers;\n return {\n // ── Phase 6 task-contract DSL (Plan 06-02 / D-A1 escape valve) ─────────\n //\n // Scans the per-vault contract registries and forces a sync of the\n // dynamic MCP tool list — regardless of [contracts.auto_register_tools]\n // (which is what makes this the explicit-control escape valve).\n // Returns per-vault diffs so the caller can confirm what landed.\n register_contracts_as_tools: async (a) => {\n const p = a as { vault?: string };\n const targetVaults =\n p.vault !== undefined ? [p.vault] : manager.list().map((v) => v.config.name);\n if (p.vault !== undefined) {\n const v = manager.list().find((vault) => vault.config.name === p.vault);\n if (v === undefined) {\n return { ok: false, reason: \"unknown_vault\", vault: p.vault };\n }\n }\n const results: {\n vault: string;\n registered: string[];\n unregistered: string[];\n }[] = [];\n const prefix = config.contracts.tool_prefix;\n for (const vname of targetVaults) {\n const state = contractRegistries.get(vname);\n if (state === undefined) continue;\n const v = manager.list().find((vault) => vault.config.name === vname);\n if (v === undefined) continue;\n const before = new Set(state.registered.keys());\n // FORCED enabled:true — explicit-control escape valve (D-A1).\n syncAutoRegistered(server, state.started.registry, prefix, state.registered, {\n enabled: true,\n instantiateHandler,\n });\n const after = new Set(state.registered.keys());\n results.push({\n vault: vname,\n registered: Array.from(after).filter((n) => !before.has(n)),\n unregistered: Array.from(before).filter((n) => !after.has(n)),\n });\n }\n if (p.vault !== undefined) {\n const single = results[0] ?? {\n vault: p.vault,\n registered: [],\n unregistered: [],\n };\n return { ok: true, ...single };\n }\n return { ok: true, vaults: results };\n },\n\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-05, Q-DESCRIBE) ────────\n //\n // Pure function over the per-vault ContractRegistry. Returns\n // {ok:true, json_schema, summary} or one of the sealed\n // InstantiateError reasons (`unknown_contract`, `ambiguous_vault`,\n // `unknown_vault`). NO LLM, NO side effects.\n describe_contract: async (a) => {\n const p = a as { name: string; vault?: string };\n const resolved = resolveContractVault(p.vault);\n if (!resolved.ok) return resolved;\n const state = contractRegistries.get(resolved.vault.config.name);\n if (state === undefined) {\n // Defense-in-depth: a vault without a contract registry happens\n // only if `start_contract_registries` skipped it (no change-feed)\n // — surface as unknown_contract for the caller.\n return { ok: false, reason: \"unknown_contract\", name: p.name };\n }\n return describeContract({ registry: state.started.registry }, { name: p.name });\n },\n\n // ── Phase 6 task-contract DSL (Plan 06-03 / CON-06) ────────────────────\n //\n // Replaces the Plan 06-02 stub. Routes through the per-vault deps\n // built by `buildInstantiateDeps`. On multi-vault setups, the caller\n // MUST pass `vault` — otherwise we return the WARNING-6\n // `ambiguous_vault` envelope (12th reason in the closed\n // InstantiateError union).\n instantiate_contract: async (a) => {\n const p = a as {\n name: string;\n inputs: Record<string, unknown>;\n source_overrides?: Record<string, string>;\n sink_overrides?: Record<string, string>;\n vault?: string;\n };\n const resolved = resolveContractVault(p.vault);\n if (!resolved.ok) return resolved;\n return instantiateContract(buildInstantiateDeps(resolved.vault), {\n name: p.name,\n inputs: p.inputs,\n ...(p.source_overrides !== undefined ? { source_overrides: p.source_overrides } : {}),\n ...(p.sink_overrides !== undefined ? { sink_overrides: p.sink_overrides } : {}),\n });\n },\n };\n}\n","{\n \"name\": \"@owrede/vault-memory\",\n \"version\": \"2.3.0\",\n \"description\": \"Local-first semantic memory MCP server for Obsidian vaults\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"workspaces\": [\n \"plugin\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/owrede/vault-memory.git\"\n },\n \"bin\": {\n \"vault-memory\": \"dist/cli.js\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\",\n \"CHANGELOG.md\"\n ],\n \"engines\": {\n \"node\": \">=22 <26\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsx watch src/cli.ts\",\n \"start\": \"node dist/cli.js\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"lint\": \"tsc --noEmit\",\n \"lint:adapters\": \"sh scripts/lint-adapters.sh\",\n \"lint:check\": \"sh scripts/check-fixture-privacy.sh && sh scripts/lint-no-telemetry.sh && sh scripts/lint-adapters.sh && tsc --noEmit && prettier --check \\\"src/**/*.ts\\\"\",\n \"format\": \"prettier --write \\\"src/**/*.ts\\\"\",\n \"eval:baseline\": \"vitest run evals/v1-baseline/baseline.test.ts\",\n \"eval:snapshot\": \"node evals/v1-baseline/dump-tools.mjs > evals/v1-baseline/tools-list.snapshot.json && node evals/v1-baseline/dump-resources.mjs > evals/v1-baseline/resources-list.snapshot.json\",\n \"eval:smoketest\": \"npm run build && node scripts/smoketest-non-claude.mjs\",\n \"release\": \"node scripts/release.mjs\",\n \"sync-marketplace\": \"node scripts/sync-marketplace.mjs\"\n },\n \"dependencies\": {\n \"@huggingface/tokenizers\": \"^0.1.3\",\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"better-sqlite3\": \"^11.7.0\",\n \"chokidar\": \"^4.0.1\",\n \"cross-spawn\": \"^7.0.6\",\n \"graphology\": \"^0.26.0\",\n \"graphology-communities-louvain\": \"^2.0.2\",\n \"gray-matter\": \"^4.0.3\",\n \"onnxruntime-node\": \"^1.26.0\",\n \"seedrandom\": \"^3.0.5\",\n \"smol-toml\": \"^1.3.1\",\n \"sqlite-vec\": \"^0.1.6\",\n \"yaml\": \"^2.9.0\",\n \"zod\": \"^4.4.3\"\n },\n \"devDependencies\": {\n \"@types/better-sqlite3\": \"^7.6.12\",\n \"@types/node\": \"^22.10.0\",\n \"@types/seedrandom\": \"^3.0.8\",\n \"prettier\": \"^3.4.0\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.7.0\",\n \"vitest\": \"^2.1.8\"\n }\n}\n","/**\n * Single source of truth for the vault-memory version (Issue #14 / P2).\n *\n * The version lives in `package.json` and nowhere else. `server.ts` previously\n * hardcoded `const VERSION = \"1.0.0\"` which drifted years behind the published\n * package — the MCP server advertised the wrong version and sink provisioning\n * stamped stale sentinels.\n *\n * tsup inlines this JSON import at build time (resolveJsonModule is on), so the\n * bundled `dist/cli.js` carries the literal string with no runtime file read.\n */\nimport pkg from \"../package.json\" with { type: \"json\" };\n\nexport const VERSION: string = pkg.version;\n","/**\n * MCP server.\n *\n * Phase 1 toolset:\n * - list_vaults, read_note, search_semantic\n *\n * Phase 2 toolset:\n * - search_text, search_hybrid\n * - list_backlinks, list_forward_links, find_broken_links\n * - query_frontmatter\n *\n * Phase 3 will add: write_note, update_frontmatter, audit_log\n */\n\nimport { McpServer, ResourceTemplate } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { loadConfig, configPath } from \"./config/index.js\";\nimport { syncPluginTools, RuntimeConfigStore } from \"./plugin-tools/index.js\";\nimport type { TriggerReindexProgress } from \"./plugin-tools/trigger-reindex.js\";\nimport { VaultManager } from \"./vault/index.js\";\nimport type { Vault } from \"./vault/index.js\";\nimport { OllamaClient } from \"./ollama/index.js\";\nimport { hybridSearch } from \"./search/index.js\";\nimport { OllamaReranker, OnnxReranker } from \"./rerank/index.js\";\nimport type { Reranker } from \"./rerank/index.js\";\nimport { errorMessage } from \"./errors/format.js\";\nimport { ok, errorResponse, errorResponseJson } from \"./server/responses.js\";\nimport { displayUrl } from \"./server/utils.js\";\n// Re-export the five utils that `src/server.test.ts` imports from \"./server.js\".\nexport {\n encodeNoteId,\n decodeNoteId,\n truncateSnippet,\n aggregateTopTags,\n aggregateTopFrontmatterKeys,\n} from \"./server/utils.js\";\nimport { homedir } from \"node:os\";\nimport { join as joinPath } from \"node:path\";\nimport { cluster, expand, listBacklinks } from \"./graph/index.js\";\nimport type { ClusterOptions, ExpandDirection, ExpandOptions } from \"./graph/index.js\";\nimport type { EdgeType } from \"./db/queries/edges.js\";\nimport { queryFrontmatter } from \"./frontmatter/index.js\";\nimport { ObsidianFsDelivery } from \"./adapters/delivery/obsidian-fs/index.js\";\nimport { provisionSink, sentinelExistsAt } from \"./adapters/delivery/obsidian-fs/sentinel.js\";\nimport {\n MemorySinkRegistry,\n readListSinks,\n readMemoryStats,\n RESOURCE_URI_LIST_SINKS,\n RESOURCE_URI_LIST_BRIEFS,\n RESOURCE_URI_MEMORY_STATS,\n RESOURCE_URI_LIST_CONTRACTS,\n RESOURCE_URI_LIST_CONTRACT_VERBS,\n RESOURCE_URI_SOURCES,\n RESOURCE_URI_VAULTS,\n RESOURCE_URI_MODELS,\n RESOURCE_URI_RECENT,\n RESOURCE_URI_STATS,\n RESOURCE_URI_BACKLINKS,\n type MemorySinkConfig,\n} from \"./memory/index.js\";\nimport { RESOURCES } from \"./resource-registry.js\";\nimport { handleRecall } from \"./memory/tools/index.js\";\nimport {\n BriefStalenessDaemon,\n handleCompileBrief,\n handleGetBrief,\n readListBriefs,\n} from \"./brief/index.js\";\nimport { searchSections } from \"./assembly/search-sections.js\";\nimport { DocNotFoundError, getOutline } from \"./assembly/outline.js\";\nimport {\n ObsidianFsChangeFeed,\n SuppressionSet,\n VaultWatcher,\n} from \"./adapters/change-feed/obsidian-fs/index.js\";\nimport { catchupVault, listModels } from \"./indexer/index.js\";\nimport { TOOL_SCHEMAS, TOOLS, buildToolSchema, type ToolName } from \"./tool-registry.js\";\nimport {\n AdapterRegistry,\n formatDocId,\n parseDocId,\n parseSourceHandle,\n} from \"./adapters/registry.js\";\nimport { ObsidianFsSource } from \"./adapters/source/obsidian-fs/index.js\";\nimport {\n startContractRegistry,\n syncAutoRegistered,\n PeerMcpRegistry,\n instantiateContract,\n readListContracts,\n readListContractVerbs,\n readListSources,\n readSourceTools,\n readSourceTool,\n type SourceConfigMeta,\n type StartedContractRegistry,\n type InstantiateDeps,\n} from \"./contracts/index.js\";\nimport type { RegisteredTool } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { Handler, HandlerDeps } from \"./server/deps.js\";\nimport {\n makeVaultHandlers,\n handleListVaults,\n handleVaultStats,\n handleRecentNotes,\n} from \"./server/handlers/vault.js\";\nimport { makeNotesHandlers, handleReadNote } from \"./server/handlers/notes.js\";\nimport { makeSearchHandlers, handleSearchHybrid } from \"./server/handlers/search.js\";\nimport { makeGraphHandlers } from \"./server/handlers/graph.js\";\nimport { makeMemoryHandlers } from \"./server/handlers/memory.js\";\nimport { makeBriefHandlers } from \"./server/handlers/brief.js\";\nimport { makeAssemblyHandlers } from \"./server/handlers/assembly.js\";\nimport { makeContractsHandlers } from \"./server/handlers/contracts.js\";\n// Issue #14 / P2: version comes from package.json via a single source of\n// truth — never hardcode it here (it drifted to \"1.0.0\" for years).\nimport { VERSION } from \"./version.js\";\n\n/**\n * Bootstrap phase names — surfaced via the optional `onPhase` callback on\n * `serve()`. Test-only hook used to assert the bootstrap order invariant\n * (per Plan 02-03b: `register_memory_sinks` MUST fire before\n * `start_catchup`).\n */\nexport type BootstrapPhase =\n | \"load_config\"\n | \"open_vaults\"\n | \"register_memory_sinks\"\n | \"start_contract_registries\"\n | \"start_catchup\"\n | \"connect_transport\";\n\nexport interface ServeOptions {\n /** Test-only hook: called as each bootstrap phase begins. */\n onPhase?: (name: BootstrapPhase) => void;\n}\n\n/**\n * Convention: when no `[[memory_sinks]]` is configured AND a vault root\n * contains `<this-folder>/.memory-sink`, `discoverMemorySinks` synthesizes\n * a default sink named `default` bound to the `default-memory-v1` contract.\n *\n * IN-05 closure: surfaced as an exported constant so the magic isn't\n * buried in a string literal. Users who want a different folder name\n * configure `[[memory_sinks]]` explicitly (which short-circuits auto-\n * discovery — see `discoverMemorySinks` body line 1).\n */\nexport const MEMORY_AUTO_DISCOVERY_FOLDER = \"_memory\";\n\n/**\n * Auto-discover memory sinks per Plan 02-03b. When `config.memory_sinks` is\n * empty AND a vault contains `<MEMORY_AUTO_DISCOVERY_FOLDER>/.memory-sink`,\n * synthesize a default sink config\n * `{name: \"default\", handle: \"obsidian-fs://<vault>/<MEMORY_AUTO_DISCOVERY_FOLDER>/\",\n * contract: \"default-memory-v1\"}`. This preserves the v2 fixture's existing\n * memory docs as a \"default sink\" without requiring config edits.\n *\n * Returns the explicit configs unchanged when `configured` is non-empty.\n */\nexport async function discoverMemorySinks(\n configured: readonly MemorySinkConfig[],\n vaults: readonly { name: string; path: string }[],\n): Promise<MemorySinkConfig[]> {\n if (configured.length > 0) {\n return [...configured];\n }\n const discovered: MemorySinkConfig[] = [];\n for (const v of vaults) {\n if (await sentinelExistsAt(v.path, MEMORY_AUTO_DISCOVERY_FOLDER)) {\n discovered.push({\n name: \"default\",\n handle: `obsidian-fs://${v.name}/${MEMORY_AUTO_DISCOVERY_FOLDER}/`,\n contract: \"default-memory-v1\",\n });\n }\n }\n return discovered;\n}\n\n/**\n * Construct and populate a `MemorySinkRegistry` per Plan 02-03b. Wraps\n * `discoverMemorySinks` + `registry.registerMemorySinks` with the\n * production provisioner (calls `provisionSink` from obsidian-fs/sentinel).\n *\n * Exported for use by `serve()` and by `src/server.test.ts` (MEM-11\n * integration + bootstrap-order assertion).\n */\nexport async function setupMemorySinks(\n config: {\n memory_sinks: MemorySinkConfig[];\n memory?: { default_sink?: string };\n },\n manager: VaultManager,\n): Promise<MemorySinkRegistry> {\n const registry = new MemorySinkRegistry();\n const vaults = manager.list().map((v) => ({\n name: v.config.name,\n path: v.config.path,\n }));\n const sinksConfig = await discoverMemorySinks(config.memory_sinks, vaults);\n await registry.registerMemorySinks(sinksConfig, {\n resolveVaultAbsolutePath: (name) => manager.require(name).config.path,\n ...(config.memory?.default_sink !== undefined\n ? { defaultSinkName: config.memory.default_sink }\n : {}),\n provisioner: async (sink, vaultAbs) => provisionSink(sink, vaultAbs, { version: VERSION }),\n });\n return registry;\n}\n\n// ─── Server bootstrap ────────────────────────────────────────────────────────\n\nexport async function serve(options: ServeOptions = {}): Promise<void> {\n const onPhase = options.onPhase ?? ((): void => undefined);\n\n onPhase(\"load_config\");\n const config = await loadConfig();\n\n onPhase(\"open_vaults\");\n const manager = new VaultManager();\n await manager.loadAll(config.vaults);\n\n // Plan 02-03b — wire the MemorySinkRegistry BEFORE catchup so any\n // sentinel provisioning completes before the catch-up walk touches the\n // _memory/ folder. Registration failures are fatal per ADR-004\n // §Provisioning fail-fast.\n onPhase(\"register_memory_sinks\");\n const memorySinkRegistry = await setupMemorySinks(config, manager);\n\n // ─── Adapter registry (Phase 1, plans 01-03 + 01-04) ──────────────────────\n //\n // One ObsidianFsSource + one ObsidianFsDelivery per vault; registered under\n // the canonical handle `obsidian-fs://<vault-name>`. The read_note handler\n // resolves the source; the write_note / update_frontmatter / delete_note\n // handlers resolve the delivery (plan 01-04 task 06).\n //\n // D-02 (client_info capture): the delivery takes a LAZY clientId getter\n // closure that reads `server.getClientVersion()?.name` on every call. This\n // lets us construct the registry BEFORE `server.connect()` while still\n // surfacing the post-handshake client_info into the audit log.\n // Pre-handshake (or if the client never sent clientInfo per the optional\n // spec field), the fallback is \"unknown\" — explicitly NOT a hardcoded\n // client name (the C-1 leak removed in plan 01-04). RESEARCH Pitfall 4.\n const adapterRegistry = new AdapterRegistry();\n // `serverRef` is assigned below; the closure captures the variable so the\n // delivery can see the post-init clientInfo without a re-registration.\n let serverRef: McpServer | undefined;\n // McpServer wraps an internal low-level `Server`; `getClientVersion()` is\n // on the inner instance.\n const getClientId = (): string => serverRef?.server.getClientVersion()?.name ?? \"unknown\";\n // One SuppressionSet shared by all watchers + the per-vault change-feed.\n // Paths are vault-relative; the chance of a collision across vaults is\n // negligible and a false positive just means one event is dropped —\n // harmless. (Pitfall 6 cross-adapter contract: ObsidianFsDelivery marks\n // a path on this set BEFORE atomicWriteFile; the change-feed +\n // VaultWatcher consume it on the corresponding chokidar event.)\n const suppression = new SuppressionSet({ ttlMs: 2000 });\n const changeFeeds = new Map<string, ObsidianFsChangeFeed>();\n for (const vault of manager.list()) {\n const source = new ObsidianFsSource(vault.config);\n adapterRegistry.registerSource(source.handle, source);\n\n const delivery = new ObsidianFsDelivery(vault, getClientId, memorySinkRegistry);\n adapterRegistry.registerDelivery(delivery.handle, delivery);\n\n // Plan 01-05 task 02: register a ChangeFeed per vault. Coexists with\n // the v1 VaultWatcher (driven from `startCatchupAndWatchers` below)\n // so existing live-indexing behavior is unchanged; a future plan will\n // retire VaultWatcher in favor of an indexer subscribing through this\n // ChangeFeed seam.\n const changeFeed = new ObsidianFsChangeFeed({\n vault,\n suppression,\n log: (m) => process.stderr.write(`[change-feed:${vault.config.name}] ${m}\\n`),\n });\n adapterRegistry.registerChangeFeed(changeFeed.handle, changeFeed);\n changeFeeds.set(vault.config.name, changeFeed);\n }\n\n const ollama = new OllamaClient({\n endpoint: config.server.ollama_endpoint,\n });\n\n const defaultModel = config.server.default_embedding_model ?? \"qwen3-embedding:0.6b\";\n\n // Default search scope. When VAULT_MEMORY_ACTIVE_VAULT is set, search_*\n // tools default to that single vault unless the caller passes an explicit\n // `vaults` array. This makes the common case (\"I'm working in this vault,\n // search this vault\") the default — cross-vault search is opt-in via an\n // explicit `vaults: [\"a\", \"b\"]` filter. If the env var is unset, the\n // legacy behaviour (search all configured vaults) applies.\n const activeVault = process.env.VAULT_MEMORY_ACTIVE_VAULT?.trim() || undefined;\n\n // Optional cross-encoder reranker (Phase 7d). Constructed once;\n // search_hybrid will pass it through only when the caller asks for it.\n // Phase 8: backend selection. Default to \"onnx\" when reranker_model is\n // set but no backend specified — the ONNX cross-encoder is the\n // recommended path; the Ollama L2-norm proxy is retained for\n // backward-compat only.\n const rerankerBackend =\n config.server.reranker_backend ?? (config.server.reranker_model ? \"onnx\" : undefined);\n const reranker: Reranker | undefined = config.server.reranker_model\n ? rerankerBackend === \"ollama\"\n ? new OllamaReranker({ ollama, model: config.server.reranker_model })\n : new OnnxReranker({\n modelDir:\n config.server.reranker_model_dir ??\n joinPath(homedir(), \".vault-memory\", \"models\", \"bge-reranker-v2-m3\"),\n })\n : undefined;\n\n // ─── File watchers (Phase 4) ──────────────────────────────────────────────\n //\n // The shared `suppression` set (hoisted above with the adapter-registry\n // construction so the per-vault ChangeFeed can share it with the v1\n // VaultWatcher) is also wired into each VaultWatcher below.\n const watchers = new Map<string, VaultWatcher>();\n\n // ─── Brief staleness daemons (Phase 5 / BRF-05..BRF-08) ────────────────────\n //\n // One daemon per vault, started after MemorySinkRegistry + catchup. Each\n // daemon subscribes to the same per-vault `ObsidianFsChangeFeed` the\n // VaultWatcher uses; ChangeFeed fan-out is documented (snapshot-then-\n // iterate per change-feed.ts:218), so multiple handlers per feed are\n // safe by contract. Lock contention is a NORMAL multi-MCP-client\n // outcome: the second server logs a structured WARN and serves\n // search/read/write identically.\n const briefDaemons = new Map<string, BriefStalenessDaemon>();\n\n // Codex MEDIUM-3: catch-up reconciliation can take seconds on large vaults\n // (re-embedding modified notes). We defer it until after MCP `connect()` so\n // the tool list responds immediately and the LLM doesn't time out waiting\n // for the handshake. Watchers start per-vault as each catch-up finishes.\n const startCatchupAndWatchers = async (): Promise<void> => {\n for (const vault of manager.list()) {\n // ADR-008: ContextFit vaults have no embedding model by design, but still\n // need catchup + a watcher (they build the SQLite layer + refresh the KB).\n const isContextFit = vault.config.backend === \"contextfit\";\n if (!isContextFit && !vault.config.embedding_model && !vault.db.models.getActive()) continue;\n const modelName = vault.config.embedding_model ?? defaultModel;\n\n try {\n const result = await catchupVault({\n vault,\n embeddingModel: modelName,\n ...(isContextFit ? {} : { ollama }),\n log: (m) => process.stderr.write(`[catchup:${vault.config.name}] ${m}\\n`),\n });\n if (result.reindexed > 0 || result.removed > 0) {\n process.stderr.write(\n `[catchup:${vault.config.name}] scanned ${result.scanned}, ` +\n `reindexed ${result.reindexed}, removed ${result.removed} ` +\n `(${result.durationMs}ms)\\n`,\n );\n }\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(\n `[catchup:${vault.config.name}] failed: ${message} (watcher will still start)\\n`,\n );\n }\n\n const watcher = new VaultWatcher({\n vault,\n embeddingModel: modelName,\n secondaryEmbeddingModel: vault.config.secondary_embedding_model,\n ollama,\n suppression,\n });\n await watcher.start();\n watchers.set(vault.config.name, watcher);\n\n // ── Phase 5 / D-07/D-08: brief staleness daemon ──────────────────\n //\n // Subscribes to the same ObsidianFsChangeFeed as the VaultWatcher.\n // Lock contention is logged as structured WARN to stderr; the\n // server continues to serve search/read/write — only the daemon\n // subscription is gated (D-08 multi-MCP-client norm).\n const feed = changeFeeds.get(vault.config.name);\n if (feed) {\n const daemon = new BriefStalenessDaemon();\n try {\n await daemon.start(vault, feed, {\n memorySinkRegistry,\n deliveryAdapterFor: (vaultName) =>\n adapterRegistry.resolveDelivery(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n log: (m) => process.stderr.write(`[brief-daemon:${vault.config.name}] ${m}\\n`),\n });\n briefDaemons.set(vault.config.name, daemon);\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[brief-daemon:${vault.config.name}] start failed: ${message}\\n`);\n }\n }\n }\n };\n\n const shutdown = async (): Promise<void> => {\n // Phase 6 (Plan 06-02): dispose ContractRegistry feed subscriptions\n // BEFORE the brief daemons + watchers so no contract reload races\n // with mid-shutdown disposal. The dispose() call is synchronous and\n // unsubscribes the per-vault ChangeFeed handler.\n for (const state of contractRegistries.values()) {\n try {\n state.started.dispose();\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[contract-registry] dispose error: ${message}\\n`);\n }\n }\n // Plan 06-03 (Pitfall F4) — kill peer-MCP child processes BEFORE\n // brief daemons + watchers + change-feeds drain. `shutdown()`\n // disposes each `PeerMcpClient`, which invokes `transport.close()`\n // → `child.kill()`. Idempotent; safe to call even when no clients\n // were configured.\n try {\n await peerMcpRegistry.shutdown();\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[peer-mcp-registry] shutdown error: ${message}\\n`);\n }\n // Phase 5 (Plan 05-03): dispose brief staleness daemons FIRST so\n // no in-flight ChangeEvents land mid-shutdown. Then drain + stop\n // watchers; finally close change-feeds (the underlying chokidar\n // watcher). Lock release happens inside daemon.shutdown() — a\n // crashed shutdown that fails here leaves the lock for the\n // PID-liveness stale-detection on next boot.\n for (const d of briefDaemons.values()) {\n try {\n await d.shutdown();\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[brief-daemon] shutdown error: ${message}\\n`);\n }\n }\n for (const w of watchers.values()) {\n await w.drain();\n await w.stop();\n }\n for (const cf of changeFeeds.values()) {\n await cf.close();\n }\n };\n process.on(\"SIGINT\", () => {\n void shutdown().finally(() => process.exit(0));\n });\n process.on(\"SIGTERM\", () => {\n void shutdown().finally(() => process.exit(0));\n });\n\n // Stdin-EOF watchdog. When stdio-MCP parents (Claude, Obsidian plugin)\n // die, they don't always succeed at SIGTERM-ing this child cleanly —\n // the parent may have been killed itself (force-quit Obsidian), the\n // transport.close() may not propagate, or the SIGTERM may race with\n // sustained file-IO and get queued. In all those cases stdin closes,\n // emitting 'end' (FIN received) or 'close' (FD closed). We exit then.\n //\n // Without this watchdog, EVERY plugin reload accumulates a zombie\n // `vault-memory serve` process holding ~22k chokidar FDs. After 10–15\n // reloads the system runs out of file descriptors (`kern.maxfiles`)\n // and Obsidian itself fails to scandir its vault with ENFILE.\n // Discovered the hard way 2026-05-20.\n //\n // Brief grace period: the MCP SDK reads stdin in object-mode chunks;\n // a final `tools/call` may still be processing when stdin closes. The\n // 500 ms timer lets in-flight work complete before exit; shutdown()\n // runs through the watcher/changeFeed drain just like the signal path.\n let stdinClosing = false;\n const onStdinClose = (reason: \"end\" | \"close\") => {\n if (stdinClosing) return;\n stdinClosing = true;\n // eslint-disable-next-line no-console -- direct stderr is intentional;\n // logger may already be draining as part of shutdown.\n process.stderr.write(`[vault-memory] stdin ${reason} — parent process gone; shutting down.\\n`);\n setTimeout(() => {\n void shutdown().finally(() => process.exit(0));\n }, 500);\n };\n process.stdin.on(\"end\", () => onStdinClose(\"end\"));\n process.stdin.on(\"close\", () => onStdinClose(\"close\"));\n\n const server = new McpServer(\n { name: \"vault-memory\", version: VERSION },\n // Plan 02-06 (MEM-09): advertise `resources` capability so MCP clients\n // call `resources/list` + `resources/read` on bootstrap. Polled-only —\n // no `subscribe` / `listChanged` flags asserted.\n { capabilities: { tools: {}, resources: {} } },\n );\n // Make the McpServer visible to the lazy clientId closure (see bootstrap).\n // After `server.connect(transport)` and the MCP initialize handshake,\n // `server.server.getClientVersion()` returns the client's `Implementation`\n // object — the `name` field is what we use for audit-log attribution.\n serverRef = server;\n\n // ─── Phase 6 (Plan 06-02) — per-vault ContractRegistry state ─────────────\n //\n // The map is created BEFORE the TOOLS loop so the `register_contracts_as_tools`\n // handler can capture it via closure. The registries themselves are\n // populated by `startContractRegistry({...})` AFTER all v1+v2 tools are\n // registered (so `syncAutoRegistered` is invoking `server.registerTool`\n // on an already-initialized server instance — RegisteredTool handles\n // are preserved per-vault for later remove() calls).\n //\n // The contractRegistries map carries one StartedContractRegistry per\n // vault plus a mutable RegisteredTool handle map for the dynamic\n // `vm_*` auto-registered tools. The Plan 06-02 stub `instantiateHandler`\n // is replaced (Plan 06-03 Task 5) by a closure over the per-vault\n // `buildInstantiateDeps` helper below.\n const contractRegistries = new Map<\n string,\n {\n started: StartedContractRegistry;\n registered: Map<string, RegisteredTool>;\n }\n >();\n\n // ─── Phase 6 (Plan 06-03) — peer-MCP registry + buildInstantiateDeps ─────\n //\n // ONE PeerMcpRegistry shared across all vaults (peer-MCP servers in\n // `[contracts.mcp_clients]` are vault-independent — a `mcp://gh/list_issues`\n // verb invocation does the same thing regardless of which vault's\n // contract triggered it). The registry boots BEFORE the per-vault\n // contract registries so each `buildInstantiateDeps(vault)` closure\n // captures the same registry instance.\n //\n // Failures during `peerMcpRegistry.start(...)` are NON-FATAL (CONTEXT.md\n // Claude's Discretion + PeerMcpRegistry semantics): individual clients\n // mark themselves unavailable with a stderr WARN. The server keeps booting.\n //\n // SIGTERM/SIGINT cleanup: the existing shutdown() at line ~391 already\n // runs on those signals; we wire `peerMcpRegistry.shutdown()` into it\n // below (Pitfall F4 — kill child processes on parent exit).\n const peerMcpRegistry = new PeerMcpRegistry();\n\n /**\n * Build per-vault `InstantiateDeps` for `instantiate_contract`. Each\n * baseline-verb thunk re-uses the existing Phase 1-5 handler functions;\n * arguments are passed through verbatim post-template-resolution (the\n * contract author is responsible for matching each verb's signature\n * per the JSDoc block in `src/contracts/verbs/index.ts`).\n */\n const buildInstantiateDeps = (vault: Vault): InstantiateDeps => {\n const state = contractRegistries.get(vault.config.name);\n if (state === undefined) {\n throw new Error(`ContractRegistry not initialized for vault \"${vault.config.name}\"`);\n }\n return {\n vault,\n registry: state.started.registry,\n memorySinks: memorySinkRegistry,\n delivery: adapterRegistry.resolveDelivery(\n parseSourceHandle(`obsidian-fs://${vault.config.name}`),\n ),\n contractAudit: vault.db.contractAudit,\n configDefaults: config.contracts.defaults,\n stepTimeoutSeconds: config.contracts.step_timeout_seconds,\n peerMcpRegistry,\n // The baseline verbs use the same args the contract YAML supplied\n // (post-template-resolution). Each thunk forwards to the existing\n // Phase 1-5 handler in the v1+v2 toolset. Contract authors match\n // each verb's signature per RESEARCH §A9.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n hybridSearch: async (args: any) => {\n const p = args as {\n query: string;\n vaults?: string[];\n top_k?: number;\n rrf_k?: number;\n exclude_paths?: string[];\n recency_weight?: number;\n authority_weight?: number;\n half_life_days?: number;\n include_superseded?: boolean;\n };\n return handleSearchHybrid(\n manager,\n ollama,\n defaultModel,\n activeVault,\n p.query,\n p.vaults ?? [vault.config.name],\n p.top_k ?? 10,\n p.rrf_k ?? 60,\n p.exclude_paths,\n reranker,\n p.recency_weight ?? 0,\n p.authority_weight ?? 0,\n p.half_life_days ?? 30,\n p.include_superseded ?? false,\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleExpand: async (args: any) => {\n const p = args as {\n seed_doc_ids: string[];\n hops: 1 | 2;\n direction?: ExpandDirection;\n edge_types?: EdgeType[];\n filter_properties?: Record<string, unknown>;\n include_superseded?: boolean;\n };\n const seeds = p.seed_doc_ids.map((s) => parseDocId(s));\n return expand(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n {\n seed_doc_ids: seeds,\n hops: p.hops,\n direction: p.direction ?? \"both\",\n ...(p.edge_types !== undefined ? { edge_types: p.edge_types } : {}),\n ...(p.filter_properties !== undefined\n ? { filter_properties: p.filter_properties }\n : {}),\n include_superseded: p.include_superseded ?? false,\n } satisfies ExpandOptions,\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleCluster: async (args: any) => {\n const p = args as {\n query?: string;\n seed_doc_ids?: string[];\n vault?: string;\n method?: \"edge-community\";\n query_top_k?: number;\n force?: boolean;\n };\n let opts: ClusterOptions;\n if (p.query !== undefined) {\n opts = {\n query: p.query,\n method: \"edge-community\",\n ...(p.vault !== undefined ? { vault: p.vault } : { vault: vault.config.name }),\n ...(p.query_top_k !== undefined ? { query_top_k: p.query_top_k } : {}),\n ...(p.force !== undefined ? { force: p.force } : {}),\n };\n } else {\n const seeds = (p.seed_doc_ids ?? []).map((s) => parseDocId(s));\n opts = {\n seed_doc_ids: seeds,\n method: \"edge-community\",\n ...(p.force !== undefined ? { force: p.force } : {}),\n };\n }\n return cluster(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n hybridSearch: async (v, query, limit) =>\n hybridSearch({\n query,\n embeddingModel: defaultModel,\n ollama,\n vaults: [v],\n topK: limit,\n includeBreakdown: false,\n ...(reranker ? { reranker } : {}),\n displayUrlFor: (vaultName, notePath) =>\n displayUrl(adapterRegistry, vaultName, notePath),\n }),\n },\n opts,\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleRecall: async (args: any) => {\n const p = args as {\n query: string;\n min_confidence?: \"direct\" | \"inferred\" | \"uncertain\";\n types?: string[];\n max_age_days?: number;\n sink?: string;\n limit?: number;\n vaults?: string[];\n };\n const packets = await handleRecall(\n {\n memorySinkRegistry,\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n searchHybrid: async (input) =>\n hybridSearch({\n query: input.query,\n embeddingModel: defaultModel,\n ollama,\n vaults: input.vaults,\n topK: input.topK,\n rrfK: 60,\n includeBreakdown: false,\n }),\n },\n { ...p, vaults: p.vaults ?? [vault.config.name] },\n );\n return { packets, count: packets.length };\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleCompileBrief: async (args: any) => {\n const p = args as {\n vault?: string;\n target: string;\n source_doc_ids: string[];\n purpose: string;\n max_tokens?: number;\n prepared_text?: string;\n sink?: string;\n };\n return handleCompileBrief(\n {\n memorySinkRegistry,\n manager,\n deliveryAdapterFor: (vaultName) =>\n adapterRegistry.resolveDelivery(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n server,\n ollama,\n briefConfig: config.brief,\n },\n { ...p, vault: p.vault ?? vault.config.name },\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleGetBrief: async (args: any) => {\n const p = args as {\n vault?: string;\n target: string;\n max_age_days?: number;\n allow_stale?: boolean;\n };\n return handleGetBrief(\n {\n memorySinkRegistry,\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n { ...p, vault: p.vault ?? vault.config.name },\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleQueryFrontmatter: async (args: any) => {\n const p = args as {\n vault?: string;\n where: Record<string, unknown>;\n limit?: number;\n };\n const v = p.vault ? manager.require(p.vault) : vault;\n return queryFrontmatter(v, {\n where: p.where as Record<string, never>,\n limit: p.limit ?? 100,\n });\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleListBacklinks: async (args: any) => {\n const p = args as { vault?: string; path: string };\n const v = p.vault ? manager.require(p.vault) : vault;\n return { backlinks: listBacklinks(v, p.path) };\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleGetOutline: async (args: any) => {\n const p = args as { doc_id: string; vaults?: string[] };\n return getOutline(\n {\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n p,\n );\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleSearchSections: async (args: any) => {\n const p = args as {\n query: string;\n limit?: number;\n vaults?: string[];\n recency_weight?: number;\n authority_weight?: number;\n include_superseded?: boolean;\n };\n // Default scope: caller's vault (the one the contract is bound to).\n const targetVaults: Vault[] = p.vaults\n ? p.vaults.map((name) => manager.require(name))\n : [vault];\n const results = await searchSections(\n {\n searchHybrid: async (input) =>\n hybridSearch({\n query: input.query,\n embeddingModel: defaultModel,\n ollama,\n vaults: input.vaults\n ? input.vaults.map((name) => manager.require(name))\n : targetVaults,\n topK: input.topK,\n rrfK: 60,\n includeBreakdown: false,\n }),\n sectionForHit: (vaultName, notePath, chunkIdx) => {\n let v: Vault;\n try {\n v = manager.require(vaultName);\n } catch {\n return null;\n }\n const note = v.db.notes.getByPath(notePath);\n if (!note) return null;\n const chunks = v.db.chunks.getByNote(note.id);\n const chunk = chunks.find((c) => c.idx === chunkIdx);\n if (!chunk) return null;\n const section = v.db.sections.findContainingChunk(note.id, chunk.id);\n if (!section) return null;\n let headingPath: string[];\n try {\n const parsed = JSON.parse(section.heading_path);\n headingPath = Array.isArray(parsed) ? (parsed as string[]) : [];\n } catch {\n headingPath = [];\n }\n return {\n noteId: note.id,\n anchor: section.anchor,\n headingPath,\n chunkIdFirst: section.chunk_id_first ?? Number.MAX_SAFE_INTEGER,\n };\n },\n readDocument: async (vaultName, notePath) => {\n const docId = formatDocId(\"obsidian-fs\", vaultName, notePath);\n return adapterRegistry\n .resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`))\n .readDocument(docId);\n },\n displayUrlFor: (docId, vaultName) => {\n const source = adapterRegistry.resolveSource(\n parseSourceHandle(`obsidian-fs://${vaultName}`),\n );\n return source.formatDisplayUrl?.(docId) ?? docId;\n },\n },\n {\n query: p.query,\n limit: p.limit ?? 10,\n ...(p.vaults !== undefined ? { vaults: p.vaults } : {}),\n ...(p.recency_weight !== undefined ? { recency_weight: p.recency_weight } : {}),\n ...(p.authority_weight !== undefined ? { authority_weight: p.authority_weight } : {}),\n ...(p.include_superseded !== undefined\n ? { include_superseded: p.include_superseded }\n : {}),\n },\n );\n return { results, count: results.length };\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handleReadNote: async (args: any) => {\n const p = args as { vault?: string; path: string };\n return handleReadNote(adapterRegistry, p.vault ?? vault.config.name, p.path);\n },\n };\n };\n\n /**\n * Resolve the target vault for `describe_contract` / `instantiate_contract`.\n * Single-vault setups: use the only configured vault. Multi-vault setups:\n * the caller MUST pass `vault`; otherwise return the WARNING-6\n * `ambiguous_vault` envelope (12th reason in InstantiateError).\n */\n const resolveContractVault = (\n vaultArg: string | undefined,\n ):\n | { ok: true; vault: Vault }\n | { ok: false; reason: \"ambiguous_vault\"; available_vaults: string[] }\n | { ok: false; reason: \"unknown_vault\"; vault: string } => {\n const list = manager.list();\n if (vaultArg !== undefined) {\n const v = list.find((x) => x.config.name === vaultArg);\n if (v === undefined) {\n return { ok: false, reason: \"unknown_vault\", vault: vaultArg };\n }\n return { ok: true, vault: v };\n }\n if (list.length === 1) {\n const only = list[0];\n if (only === undefined) {\n return { ok: false, reason: \"ambiguous_vault\", available_vaults: [] };\n }\n return { ok: true, vault: only };\n }\n return {\n ok: false,\n reason: \"ambiguous_vault\",\n available_vaults: list.map((v) => v.config.name),\n };\n };\n\n /**\n * Bound to auto-registered `vm_*` tools. Each auto-registered tool's\n * callback (see `syncAutoRegistered` in `src/contracts/auto-register.ts`)\n * passes the contract name + the caller args through this closure. We\n * route to `instantiateContract` using the per-vault deps captured at\n * register time — single-vault deployments are the v2.0.0 norm; multi-\n * vault setups will surface `ambiguous_vault` until per-vault\n * tool-prefixing lands in a future slice.\n */\n const instantiateHandler = async (name: string, args: unknown): Promise<unknown> => {\n const resolved = resolveContractVault(undefined);\n if (!resolved.ok) return resolved;\n const inputs = ((args as { inputs?: Record<string, unknown> })?.inputs ?? {}) as Record<\n string,\n unknown\n >;\n return instantiateContract(buildInstantiateDeps(resolved.vault), {\n name,\n inputs,\n });\n };\n\n // ─── registerTool × 23 (SDK 1.29, plan 01-05 task 07) ─────────────────────\n //\n // Each handler receives ALREADY-VALIDATED args (the SDK runs the Zod\n // schema before invoking us). We layer a try/catch to convert thrown\n // errors into MCP error responses, preserving the v1 error shape.\n\n // Bundle the serve()-scope closure state once; per-domain handler\n // factories (server/handlers/*.ts) close over `deps.*` instead of the\n // bare serve() locals.\n const deps: HandlerDeps = {\n manager,\n ollama,\n defaultModel,\n reranker,\n adapterRegistry,\n suppression,\n memorySinkRegistry,\n server,\n contractRegistries,\n peerMcpRegistry,\n config,\n activeVault,\n };\n\n // Assembled from per-domain factories (spread) plus the not-yet-extracted\n // inline entries. Typed as Partial during assembly; completeness over the\n // ToolName union is re-asserted by `assertCompleteHandlers` below.\n const handlers: Partial<Record<ToolName, Handler>> = {\n ...makeVaultHandlers(deps),\n ...makeNotesHandlers(deps),\n ...makeSearchHandlers(deps),\n ...makeGraphHandlers(deps),\n ...makeMemoryHandlers(deps),\n\n ...makeBriefHandlers(deps),\n\n ...makeAssemblyHandlers(deps),\n ...makeContractsHandlers(deps, {\n resolveContractVault,\n instantiateHandler,\n buildInstantiateDeps,\n }),\n };\n\n // Completeness gate: every ToolName must have a handler. The per-domain\n // factories return `Partial<Record<ToolName, Handler>>`, so we re-assert\n // the union is fully covered (a missing key would be a wiring bug, caught\n // here at boot rather than on first tool call).\n for (const tool of TOOLS) {\n const name = tool.name as ToolName;\n if (handlers[name] === undefined) {\n throw new Error(`Internal error: no handler registered for tool \"${name}\".`);\n }\n }\n const completeHandlers = handlers as Record<ToolName, Handler>;\n\n // Wire each TOOLS entry through registerTool. The SDK runs the Zod\n // schema (built via buildToolSchema from tool-registry.ts) against the\n // incoming arguments BEFORE invoking our handler — so each handler\n // receives args matching the declared shape. Thrown errors are caught\n // and converted to MCP error responses (isError:true) per the v1\n // error-wrapping contract.\n for (const tool of TOOLS) {\n const name = tool.name as ToolName;\n const handler = completeHandlers[name];\n const schema = TOOL_SCHEMAS[name];\n // suggest_frontmatter layers an extra refinement on top of its raw\n // shape; the SDK only accepts a raw shape here, so we register the\n // shape directly and let the handler re-validate with the refined\n // schema (`buildToolSchema`) for the cross-field check. The same\n // pattern applies to `cluster` (D-15a mutual exclusion between\n // `query` and `seed_doc_ids`).\n const needsRefinementCheck = name === \"suggest_frontmatter\" || name === \"cluster\";\n server.registerTool(\n name,\n { description: tool.description, inputSchema: schema },\n async (args: unknown) => {\n try {\n let validated: unknown = args;\n if (needsRefinementCheck) {\n validated = buildToolSchema(name).parse(args);\n }\n const data = await handler(validated);\n return ok(data);\n } catch (err) {\n // Phase 3 ASM-02: a `DocNotFoundError` carries a structured\n // payload (`{error: \"doc_not_found\", doc_id}`) per the plan's\n // error contract. Other tools that resolve documents by id\n // (forthcoming get_bundle, dossier) will throw the same shape.\n if (err instanceof DocNotFoundError) {\n return errorResponseJson({ error: \"doc_not_found\", doc_id: err.doc_id });\n }\n const message = errorMessage(err);\n return errorResponse(message);\n }\n },\n );\n }\n\n // ─── MCP Resources (Plan 02-06 / MEM-09) ─────────────────────────────────\n //\n // Polled-only — no `notifyResourceUpdated` integration in v2.0.0\n // (CONTEXT D-Q4). URIs are FLAT per RESEARCH §Q4: one resource per\n // capability, not per sink. The registry is already populated above\n // (via `setupMemorySinks(...)`); the read callbacks just project from\n // it (list_sinks) or query the per-vault SQLite DB (memory_stats).\n server.registerResource(\n \"memory-sinks\",\n RESOURCE_URI_LIST_SINKS,\n {\n title: \"Memory sinks\",\n description:\n \"Configured + auto-discovered MemorySinks (name, handle, vault, contract, default). \" +\n \"Read to discover where memory documents (record_observation, supersede) land.\",\n mimeType: \"application/json\",\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(readListSinks(memorySinkRegistry), null, 2),\n },\n ],\n }),\n );\n server.registerResource(\n \"memory-stats\",\n RESOURCE_URI_MEMORY_STATS,\n {\n title: \"Memory sink stats\",\n description:\n \"Per-sink document counts, by_type / by_status breakdowns, and last memory-write timestamp. \" +\n \"Polled — re-read to refresh.\",\n mimeType: \"application/json\",\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(readMemoryStats(memorySinkRegistry, manager), null, 2),\n },\n ],\n }),\n );\n\n // ─── Plan 05-04 (BRF-09) — list_briefs MCP Resource ───────────────────────\n //\n // Discovery surface for compiled briefs. Filtered by optional `?target=`\n // query parameter (substring match on `properties.target`). The read\n // handler is a pure function over `MemorySinkRegistry + VaultManager +\n // SourceConnector` — see `src/brief/resources.ts`.\n server.registerResource(\n \"briefs\",\n RESOURCE_URI_LIST_BRIEFS,\n {\n title: \"Compiled briefs\",\n description:\n \"Discovery of compiled briefs by target. Supports optional `?target=<pattern>` \" +\n \"substring filter on `properties.target`. Includes `active`, `stale`, and \" +\n \"`superseded` entries so callers can build their own filter / inspect the \" +\n \"supersede chain. BRF-09.\",\n mimeType: \"application/json\",\n },\n async (uri) => {\n const target = uri.searchParams.get(\"target\") ?? undefined;\n const payload = await readListBriefs(\n {\n registry: memorySinkRegistry,\n manager,\n sourceConnectorFor: (vaultName) =>\n adapterRegistry.resolveSource(parseSourceHandle(`obsidian-fs://${vaultName}`)),\n },\n target !== undefined ? { target } : {},\n );\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n },\n ],\n };\n },\n );\n\n // ─── Phase 6 (Plan 06-04) — contract MCP Resources ───────────────────────\n //\n // Two Resources expose contract metadata for discovery (CON-04) and\n // verb-usage promotion signals (D-A2b). Both use the SDK 1.29\n // `ResourceTemplate` pattern with a `{vault}` URI variable so each\n // per-vault contract registry surfaces as its own readable URI.\n //\n // Resources do NOT count toward the REL-08 tool budget per Phase 5\n // BRF-09 precedent. They are listed under `resources/list` in the\n // MCP protocol, not `tools/list`.\n server.registerResource(\n \"contracts\",\n new ResourceTemplate(`${RESOURCE_URI_LIST_CONTRACTS}/{vault}`, {\n list: undefined,\n }),\n {\n title: \"Task contracts\",\n description:\n \"Discovery of task contracts available in a vault (CON-04). Each entry \" +\n \"carries name, description, source/sink counts, and write_back boolean. \" +\n \"Optional `?source=<prefix>` filters to contracts declaring a source \" +\n \"whose handle starts with the given prefix.\",\n mimeType: \"application/json\",\n },\n async (uri, variables) => {\n const vault = String(variables.vault ?? \"\");\n const state = contractRegistries.get(vault);\n if (state === undefined) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: `unknown vault: ${vault}` }),\n },\n ],\n };\n }\n const source = uri.searchParams.get(\"source\") ?? undefined;\n const payload = readListContracts(\n { registry: state.started.registry, vaultName: vault },\n source !== undefined ? { source } : {},\n );\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n },\n ],\n };\n },\n );\n\n server.registerResource(\n \"contract-verbs\",\n new ResourceTemplate(`${RESOURCE_URI_LIST_CONTRACT_VERBS}/{vault}`, {\n list: undefined,\n }),\n {\n title: \"Contract verbs\",\n description:\n \"List baseline assembly verbs + custom (mcp://) verbs in use, with \" +\n \"invocation_count + last_seen aggregated from contract_audit (D-A2b). \" +\n \"Baseline verbs are constant per ADR-006 §Decision 3.\",\n mimeType: \"application/json\",\n },\n async (uri, variables) => {\n const vault = String(variables.vault ?? \"\");\n // Look up the per-vault contractAudit directly through the manager\n // rather than via the contractRegistries map — the audit table is\n // populated regardless of whether the registry boot scan succeeded.\n const vaultRef = manager.list().find((vt) => vt.config.name === vault);\n if (vaultRef === undefined) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: `unknown vault: ${vault}` }),\n },\n ],\n };\n }\n const payload = readListContractVerbs({\n contractAudit: vaultRef.db.contractAudit,\n vaultName: vault,\n });\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n },\n ],\n };\n },\n );\n\n // ─── SOURCES-REGISTRY.md §5 (Stage 2) — peer-MCP source discovery ────────\n //\n // Three vault-independent resources over the live PeerMcpRegistry. The\n // command/args come from `config.contracts.mcp_clients`; runtime-added\n // sources (set_mcp_client without restart) appear with empty meta until\n // the next boot, which is acceptable for discovery.\n const sourceConfigMeta = (): Record<string, SourceConfigMeta> => {\n const out: Record<string, SourceConfigMeta> = {};\n for (const [name, cfg] of Object.entries(config.contracts.mcp_clients)) {\n out[name] = { command: cfg.command, args: cfg.args ?? [] };\n }\n return out;\n };\n\n server.registerResource(\n \"sources\",\n RESOURCE_URI_SOURCES,\n {\n title: \"Peer MCP sources\",\n description:\n \"List peer MCP servers vault-memory connects to, with per-source \" +\n \"status (connected/unavailable/unreachable), tool_count, and \" +\n \"last_refreshed. vault-memory itself is not included. SOURCES-REGISTRY §5.1.\",\n mimeType: \"application/json\",\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(readListSources(peerMcpRegistry, sourceConfigMeta()), null, 2),\n },\n ],\n }),\n );\n\n server.registerResource(\n \"source-tools\",\n new ResourceTemplate(`${RESOURCE_URI_SOURCES}/{name}/tools`, {\n list: undefined,\n }),\n {\n title: \"Peer MCP source tools\",\n description:\n \"List the cached tools/list for one peer MCP source. Empty when the \" +\n \"source is not connected. SOURCES-REGISTRY §5.2.\",\n mimeType: \"application/json\",\n },\n async (uri, variables) => {\n const name = String(variables.name ?? \"\");\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(readSourceTools(peerMcpRegistry, name), null, 2),\n },\n ],\n };\n },\n );\n\n server.registerResource(\n \"source-tool\",\n new ResourceTemplate(`${RESOURCE_URI_SOURCES}/{name}/tools/{tool}`, {\n list: undefined,\n }),\n {\n title: \"Peer MCP source tool\",\n description:\n \"Read a single tool's schema from one peer MCP source, inlined from \" +\n \"the cached tools/list (no extra peer call). SOURCES-REGISTRY §5.3.\",\n mimeType: \"application/json\",\n },\n async (uri, variables) => {\n const name = String(variables.name ?? \"\");\n const tool = String(variables.tool ?? \"\");\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(readSourceTool(peerMcpRegistry, name, tool), null, 2),\n },\n ],\n };\n },\n );\n\n // ─── Phase 8 (Plan 08-05 / REL-08) — promote 5 list-style v1 tools ───────\n //\n // Each Resource delegates to the existing internal tool handler (GAT-01\n // seam preservation: no logic duplication). The v1 tool handlers remain\n // wired in `tools/call` — only their descriptions get a DEPRECATED notice\n // (see src/tool-registry.ts).\n //\n // `vault-memory://vaults` is static (no per-vault variable). The other\n // four use ResourceTemplate with a `{vault}` variable; `backlinks`\n // additionally uses RFC 6570 reserved expansion `{+docId}` so multi-segment\n // paths (e.g. `notes/sub/file.md`) parse as a single value.\n const rel08Vaults = RESOURCES.find((r) => r.name === \"vaults\");\n const rel08Models = RESOURCES.find((r) => r.name === \"models\");\n const rel08Recent = RESOURCES.find((r) => r.name === \"recent\");\n const rel08Stats = RESOURCES.find((r) => r.name === \"stats\");\n const rel08Backlinks = RESOURCES.find((r) => r.name === \"backlinks\");\n if (\n rel08Vaults === undefined ||\n rel08Models === undefined ||\n rel08Recent === undefined ||\n rel08Stats === undefined ||\n rel08Backlinks === undefined\n ) {\n throw new Error(\n \"REL-08 Resources missing from RESOURCES registry — check src/resource-registry.ts\",\n );\n }\n\n server.registerResource(\n rel08Vaults.name,\n RESOURCE_URI_VAULTS,\n {\n title: \"Vaults\",\n description: rel08Vaults.description,\n mimeType: rel08Vaults.mimeType,\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(handleListVaults(manager), null, 2),\n },\n ],\n }),\n );\n\n server.registerResource(\n rel08Models.name,\n new ResourceTemplate(`${RESOURCE_URI_MODELS}/{vault}`, { list: undefined }),\n {\n title: \"Embedding models\",\n description: rel08Models.description,\n mimeType: rel08Models.mimeType,\n },\n async (uri, variables) => {\n const vaultName = String(variables.vault ?? \"\");\n try {\n const vault = manager.require(vaultName);\n const models = listModels(vault);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ models, count: models.length }, null, 2),\n },\n ],\n };\n } catch (err) {\n const message = errorMessage(err);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: message }),\n },\n ],\n };\n }\n },\n );\n\n server.registerResource(\n rel08Recent.name,\n new ResourceTemplate(`${RESOURCE_URI_RECENT}/{vault}`, { list: undefined }),\n {\n title: \"Recent notes\",\n description: rel08Recent.description,\n mimeType: rel08Recent.mimeType,\n },\n async (uri, variables) => {\n const vaultName = String(variables.vault ?? \"\");\n try {\n manager.require(vaultName);\n // Default limit matches the recent_notes tool's schema default (20).\n const limitParam = uri.searchParams.get(\"limit\");\n const sinceParam = uri.searchParams.get(\"since\");\n const limit = limitParam !== null ? Number(limitParam) : 20;\n const since = sinceParam !== null ? Number(sinceParam) : undefined;\n const payload = handleRecentNotes(manager, vaultName, limit, since);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n },\n ],\n };\n } catch (err) {\n const message = errorMessage(err);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: message }),\n },\n ],\n };\n }\n },\n );\n\n server.registerResource(\n rel08Stats.name,\n new ResourceTemplate(`${RESOURCE_URI_STATS}/{vault}`, { list: undefined }),\n {\n title: \"Vault stats\",\n description: rel08Stats.description,\n mimeType: rel08Stats.mimeType,\n },\n async (uri, variables) => {\n const vaultName = String(variables.vault ?? \"\");\n try {\n manager.require(vaultName);\n const payload = handleVaultStats(manager, vaultName);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n },\n ],\n };\n } catch (err) {\n const message = errorMessage(err);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: message }),\n },\n ],\n };\n }\n },\n );\n\n server.registerResource(\n rel08Backlinks.name,\n new ResourceTemplate(`${RESOURCE_URI_BACKLINKS}/{vault}/{+docId}`, {\n list: undefined,\n }),\n {\n title: \"Backlinks\",\n description: rel08Backlinks.description,\n mimeType: rel08Backlinks.mimeType,\n },\n async (uri, variables) => {\n const vaultName = String(variables.vault ?? \"\");\n const rawDocId = variables.docId;\n // RFC 6570 reserved expansion: when the URI contains percent-encoded\n // characters (e.g. spaces or unicode in path segments), the SDK\n // already decodes them. The variable arrives as the raw path string.\n const docId = Array.isArray(rawDocId) ? rawDocId.join(\"/\") : String(rawDocId ?? \"\");\n try {\n const vault = manager.require(vaultName);\n const backlinks = listBacklinks(vault, docId);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ backlinks }, null, 2),\n },\n ],\n };\n } catch (err) {\n const message = errorMessage(err);\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify({ error: message }),\n },\n ],\n };\n }\n },\n );\n\n // ─── Phase 6 (Plan 06-02) — start per-vault ContractRegistry ─────────────\n //\n // Boot scan + ChangeFeed hot-reload subscriber per vault. The boot scan\n // is light (yaml@2.9 parse over a handful of contract YAMLs) and runs\n // synchronously here so the registry is populated before any client\n // request lands. The ChangeFeed subscription is the third concurrent\n // subscriber on the per-vault ObsidianFsChangeFeed (alongside the\n // VaultWatcher from Phase 1 and the BriefStalenessDaemon from Phase 5).\n // Lock contention is N/A — the ContractRegistry holds no lockfile.\n //\n // When [contracts.auto_register_tools] is true, an initial sync runs\n // after the boot scan completes, registering one MCP tool per parsed\n // contract (prefix from `config.contracts.tool_prefix`). Subsequent\n // ChangeFeed events trigger another sync via the onRegistryChange hook.\n onPhase(\"start_contract_registries\");\n // Plan 06-03 — boot the peer-MCP registry BEFORE per-vault contract\n // registries so each vault's instantiate deps share the same registry.\n // Failures inside `start()` are non-fatal — individual clients mark\n // themselves unavailable + log to stderr (Pitfall F4 mitigation).\n try {\n await peerMcpRegistry.start(config.contracts.mcp_clients);\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[peer-mcp-registry] start failed: ${message}\\n`);\n }\n for (const vault of manager.list()) {\n const feed = changeFeeds.get(vault.config.name);\n if (feed === undefined) continue;\n const source = adapterRegistry.resolveSource(\n parseSourceHandle(`obsidian-fs://${vault.config.name}`),\n );\n const registeredHandles = new Map<string, RegisteredTool>();\n let started: StartedContractRegistry;\n try {\n // eslint-disable-next-line prefer-const\n started = await startContractRegistry({\n vault,\n feed,\n source,\n auditDeps: { contractAudit: vault.db.contractAudit },\n // Phase 7 / Plan 07-07 / CAN-08 — hash-keyed echo suppression\n // for the plugin's `.yaml` companion writes. Shared with the\n // change-feed watcher above so a single set sees both write\n // pathways (writer, indexer, plugin).\n suppression,\n // CAN-08 D-WATCH-SERVER-NOTIFY — emit the external-edit MCP\n // Resource notification when (and only when) the gate is on.\n // The plugin's `ReloadNotifier` (plan 07-07 task 3) subscribes\n // via `notifications/resources/updated` for this URI and\n // prompts the user with a Modal.\n onExternalReload: config.plugin.enabled\n ? (file) => {\n try {\n server.server.notification({\n method: \"notifications/resources/updated\",\n params: {\n uri: \"vault-memory://contracts/reloaded\",\n // Body is non-standard for resources/updated but\n // MCP clients ignore unknown params. Carrying the\n // file path here saves the plugin a follow-up\n // resource read in the common case.\n _meta: { path: file, reason: \"external_edit\" },\n },\n });\n } catch (err) {\n const msg = errorMessage(err);\n process.stderr.write(`[contracts-reloaded-notify] ${vault.config.name}: ${msg}\\n`);\n }\n }\n : undefined,\n onRegistryChange: () => {\n if (config.contracts.auto_register_tools) {\n syncAutoRegistered(\n server,\n started.registry,\n config.contracts.tool_prefix,\n registeredHandles,\n { enabled: true, instantiateHandler },\n );\n }\n },\n });\n } catch (err) {\n const message = errorMessage(err);\n process.stderr.write(`[contract-registry:${vault.config.name}] start failed: ${message}\\n`);\n continue;\n }\n if (config.contracts.auto_register_tools) {\n syncAutoRegistered(\n server,\n started.registry,\n config.contracts.tool_prefix,\n registeredHandles,\n { enabled: true, instantiateHandler },\n );\n }\n contractRegistries.set(vault.config.name, {\n started,\n registered: registeredHandles,\n });\n }\n\n // ─── Phase 7 (Plan 07-04) — plugin-control MCP tools ─────────────────────\n //\n // Gated by `config.plugin.enabled` (default OFF). When false, zero plugin\n // tools register and `tools/list` is byte-equivalent to the v1-baseline\n // snapshot for non-plugin deployments (REL-08 ≤32-tool budget).\n //\n // The runtime-config store is owned at the serve() lifetime and threaded\n // into the `set_runtime_config` handler. Hot-swap mutations are NOT\n // persisted — `~/.vault-memory/config.toml` remains authoritative across\n // restarts (PLG-01 §\"Hot-swap semantics\").\n const runtimeConfigStore = new RuntimeConfigStore({});\n const pluginToolsRegistered = new Map<string, RegisteredTool>();\n // `reindexVault` shim — wraps the existing `indexVault` entry point so the\n // trigger_reindex tool is decoupled from the full indexer surface.\n const reindexVault = async (\n vaultName: string,\n onProgress?: (p: TriggerReindexProgress) => void,\n ): Promise<void> => {\n const v = manager.list().find((vt) => vt.config.name === vaultName);\n if (v === undefined) throw new Error(`unknown vault: ${vaultName}`);\n const embeddingModel =\n v.config.embedding_model ?? config.server.default_embedding_model ?? \"qwen3-embedding\";\n // Use a dynamic import to keep the indexer module out of the startup\n // critical path when the plugin gate is OFF.\n const { indexVault } = await import(\"./indexer/index.js\");\n let lastReported = 0;\n await indexVault(v, {\n mode: \"full\",\n embeddingModel,\n ollama,\n onProgress: (_msg: string) => {\n // The current indexer onProgress signal is a free-text status line;\n // we increment a per-call counter as a coarse progress proxy. The\n // chrome can render that as \"reindex in progress\" until the call\n // resolves. A future indexer enhancement (out of scope for 07-04)\n // can replace this with structured progress events.\n lastReported += 1;\n onProgress?.({ progress: lastReported });\n },\n });\n };\n // Snapshot peer-MCP availability for get_runtime_stats.\n const peerMcpStatus = (): Array<{ name: string; available: boolean }> => {\n const out: Array<{ name: string; available: boolean }> = [];\n for (const name of Object.keys(config.contracts.mcp_clients)) {\n const client = peerMcpRegistry.get(name);\n out.push({ name, available: client?.available ?? false });\n }\n return out;\n };\n // Contract count per vault — reads from the live registry map populated above.\n const contractCountFor = (vaultName: string): number => {\n const state = contractRegistries.get(vaultName);\n if (state === undefined) return 0;\n let count = 0;\n for (const _ of state.started.registry.entries()) count += 1;\n return count;\n };\n syncPluginTools(server, pluginToolsRegistered, {\n enabled: config.plugin.enabled,\n runtimeConfig: runtimeConfigStore,\n configPath: configPath(),\n listVaults: () => manager.list() as never,\n peerMcpStatus,\n contractCountFor,\n reindexVault,\n // Plan 07-07 / CAN-08 — same shared instance the contract loader\n // sees, so the plugin's `suppress_contract_write` call and the\n // change-feed handler observe the same entries.\n suppression,\n // SOURCES-REGISTRY.md §6 (Stage 2) — live registry for refresh_source\n // + unset_mcp_client. The singleton booted above.\n sourceRegistry: peerMcpRegistry,\n notifier: (notification) => {\n // Forward to the underlying MCP server transport. The McpServer\n // wraps a low-level Server with `server.server`; the notification\n // method is exposed there.\n server.server.notification(notification);\n },\n });\n\n onPhase(\"connect_transport\");\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n // Fire-and-forget — the MCP handshake is complete, tools are usable, and\n // catch-up runs in the background. Errors are already logged inside the\n // function; we still catch here to satisfy the linter and surface anything\n // unexpected on stderr.\n //\n // Plan 02-03b: `start_catchup` fires AFTER `register_memory_sinks` (the\n // sentinel-provisioning step above has already completed). The phase\n // hook fires synchronously so the bootstrap-order assertion in tests\n // can observe the invariant `register_memory_sinks` < `start_catchup`.\n onPhase(\"start_catchup\");\n startCatchupAndWatchers().catch((err) => {\n const message = errorMessage(err);\n process.stderr.write(`[catchup] unexpected failure: ${message}\\n`);\n });\n}\n\n// ─── Tool handlers ───────────────────────────────────────────────────────────\n","/**\n * vault-memory CLI entrypoint.\n */\n\nexport {};\n\nconst args = process.argv.slice(2);\nconst command = args[0] ?? \"serve\";\n\nswitch (command) {\n case \"serve\":\n await import(\"./server.js\").then((m) => m.serve());\n break;\n\n case \"index\":\n await runIndex(args.slice(1));\n break;\n\n case \"add-vault\":\n await runAddVault(args.slice(1));\n break;\n\n case \"--help\":\n case \"-h\":\n case \"help\":\n printHelp();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n printHelp();\n process.exit(2);\n}\n\nasync function runIndex(rest: string[]): Promise<void> {\n const { loadConfig } = await import(\"./config/index.js\");\n const { VaultManager } = await import(\"./vault/index.js\");\n const { OllamaClient } = await import(\"./ollama/index.js\");\n const { indexVault } = await import(\"./indexer/index.js\");\n\n // Parse flags\n let vaultName: string | null = null;\n let mode: \"full\" | \"incremental\" = \"incremental\";\n\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n if (arg === \"--full\") mode = \"full\";\n else if (arg === \"--vault\") {\n vaultName = rest[i + 1] ?? null;\n i++;\n } else if (arg && !arg.startsWith(\"--\") && vaultName === null) {\n vaultName = arg;\n }\n }\n\n const config = await loadConfig();\n if (config.vaults.length === 0) {\n console.error(\"No vaults configured. Edit ~/.vault-memory/config.toml.\");\n process.exit(2);\n }\n\n const manager = new VaultManager();\n await manager.loadAll(config.vaults);\n\n const ollama = new OllamaClient({\n endpoint: config.server.ollama_endpoint,\n });\n\n const targets = vaultName ? [manager.require(vaultName)] : manager.list();\n\n for (const vault of targets) {\n // ADR-008: ContextFit-backed vaults use the CPU-only token-native engine.\n // Two-part index: (1) build the full SQLite content layer WITHOUT embeddings\n // (powers graph/sections/frontmatter/stats tools, the watcher, catchup, and\n // write re-index) and (2) build the ContextFit search KB. No Ollama, no GPU.\n if (vault.config.backend === \"contextfit\") {\n const { indexVaultWithContextFit } = await import(\"./adapters/retrieval/contextfit/index.js\");\n console.error(\n `\\n→ Indexing \"${vault.config.name}\" with ContextFit (CPU-only, no embeddings)`,\n );\n // (1) SQLite content layer — embeddings:\"none\" skips Ollama entirely.\n const sqlite = await indexVault(vault, {\n mode,\n embeddingModel: \"contextfit\",\n embeddings: \"none\",\n onProgress: (msg) => console.error(` ${msg}`),\n });\n if (sqlite.status !== \"completed\") {\n console.error(`✗ ${vault.config.name}: SQLite layer failed — ${sqlite.error}`);\n process.exitCode = 1;\n continue;\n }\n // (2) ContextFit search KB.\n const cfResult = await indexVaultWithContextFit(vault.config, {\n onProgress: (msg) => console.error(` ${msg}`),\n });\n if (cfResult.status === \"completed\") {\n console.error(\n `✓ ${vault.config.name}: ${sqlite.notesIndexed} notes (SQLite) + ContextFit KB · ${sqlite.durationMs + cfResult.durationMs}ms`,\n );\n } else if (cfResult.status === \"skipped\") {\n // Issue #17: another process held the ingest lock. Not an error — the\n // holder will do a trailing re-ingest that captures our changes.\n console.error(\n `↷ ${vault.config.name}: ${sqlite.notesIndexed} notes (SQLite); ContextFit KB re-ingest already in progress in another process — flagged for retry, skipping`,\n );\n } else {\n console.error(`✗ ${vault.config.name}: ContextFit KB failed — ${cfResult.error}`);\n process.exitCode = 1;\n }\n continue;\n }\n\n const model =\n vault.config.embedding_model ?? config.server.default_embedding_model ?? \"qwen3-embedding\";\n\n console.error(`\\n→ Indexing \"${vault.config.name}\" (${mode}) with ${model}`);\n const result = await indexVault(vault, {\n mode,\n embeddingModel: model,\n ollama,\n onProgress: (msg) => console.error(` ${msg}`),\n });\n\n if (result.status === \"completed\") {\n const skipSuffix = result.notesSkipped > 0 ? `, ${result.notesSkipped} skipped` : \"\";\n console.error(\n `✓ ${vault.config.name}: ${result.notesIndexed} new, ` +\n `${result.notesUpdated} updated, ${result.notesDeleted} deleted${skipSuffix}, ` +\n `${result.chunksCreated} chunks · ${result.durationMs}ms`,\n );\n } else {\n console.error(`✗ ${vault.config.name}: ${result.error}`);\n process.exitCode = 1;\n }\n }\n\n manager.closeAll();\n}\n\n/**\n * add-vault: onboard a new Obsidian vault end-to-end.\n * 1. append a [[vaults]] block to ~/.vault-memory/config.toml\n * 2. write/merge .mcp.json in the vault root (so an MCP-aware client\n * can auto-spawn the MCP server when that vault is opened)\n * 3. build an initial index (unless --no-index is passed)\n *\n * Idempotent: re-running with a known path skips config mutation\n * and only refreshes the .mcp.json + delta-indexes.\n */\nasync function runAddVault(rest: string[]): Promise<void> {\n const { addVault } = await import(\"./config/index.js\");\n\n // Parse positional path + flags.\n let path: string | null = null;\n let name: string | undefined;\n let writeEnabled = false;\n let skipIndex = false;\n let backend: \"ollama\" | \"contextfit\" | undefined;\n\n const USAGE =\n \"Usage: vault-memory add-vault <path> [--name <name>] [--write] \" +\n \"[--backend ollama|contextfit] [--no-index]\";\n\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n if (arg === \"--name\") {\n name = rest[i + 1];\n i++;\n } else if (arg === \"--write\" || arg === \"--write-enabled\") {\n writeEnabled = true;\n } else if (arg === \"--backend\") {\n const v = rest[i + 1];\n i++;\n if (v !== \"ollama\" && v !== \"contextfit\") {\n console.error(`--backend must be \"ollama\" or \"contextfit\" (got: ${v ?? \"<missing>\"})`);\n process.exit(2);\n }\n backend = v;\n } else if (arg === \"--no-index\") {\n skipIndex = true;\n } else if (arg === \"--help\" || arg === \"-h\") {\n console.error(`${USAGE}\n\nRegisters a vault in ~/.vault-memory/config.toml, writes a .mcp.json\ninto the vault root, and runs an initial index. Idempotent.\n\n--backend contextfit Use the CPU-only, token-native ContextFit engine\n (no Ollama / embeddings / GPU). Requires the\n \\`contextfit\\` CLI (pipx install contextfit). Ideal for\n resource-limited / non-GPU hosts (e.g. a Synology NAS).`);\n return;\n } else if (arg && !arg.startsWith(\"--\") && path === null) {\n path = arg;\n }\n }\n\n if (path === null) {\n console.error(USAGE);\n process.exit(2);\n }\n\n console.error(`→ Registering vault: ${path}${backend ? ` (backend: ${backend})` : \"\"}`);\n const result = await addVault({ path, name, writeEnabled, ...(backend ? { backend } : {}) });\n\n // Render the per-step transcript so users see exactly what changed.\n for (const step of result.steps) {\n switch (step.kind) {\n case \"config-added\":\n console.error(` ✓ config.toml: added [[vaults]] \"${step.name}\"`);\n break;\n case \"config-already-registered\":\n console.error(\n ` • config.toml: already registered as \"${step.name}\" (${step.existingPath})`,\n );\n break;\n case \"mcp-json-created\":\n console.error(` ✓ ${step.mcpPath}: created`);\n break;\n case \"mcp-json-merged\":\n console.error(` ✓ ${step.mcpPath}: merged vault-memory entry`);\n break;\n case \"mcp-json-unchanged\":\n console.error(` • ${step.mcpPath}: already up to date`);\n break;\n }\n }\n\n if (skipIndex) {\n console.error(`\\nSkipped indexing (--no-index). Run later:`);\n console.error(` vault-memory index ${result.name}`);\n } else {\n console.error(`\\n→ Building initial index for \"${result.name}\"…`);\n // Reuse the existing index flow. Pass the vault name as positional arg.\n await runIndex([result.name]);\n }\n\n console.error(\n `\\nDone. Open ${result.resolvedPath} in your MCP-aware client — the vault-memory MCP server will be available.`,\n );\n}\n\nfunction printHelp(): void {\n console.error(`vault-memory — local-first semantic memory MCP server\n\nUSAGE:\n vault-memory [COMMAND] [OPTIONS]\n\nCOMMANDS:\n serve Start MCP server on stdio (default)\n index [VAULT] Build/refresh index for a vault (or all if omitted)\n --full Wipe derived layer and re-embed everything\n --vault NAME Alternative flag form\n add-vault <path> Register a new vault end-to-end (config + .mcp.json + index)\n --name NAME Override the auto-slugified name\n --write Allow MCP write operations (default: read-only)\n --no-index Skip the initial index (you can run it later)\n init Interactive config wizard (Phase 5 — not yet)\n help, --help Show this message\n\nCONFIG:\n ~/.vault-memory/config.toml`);\n}\n"],"mappings":"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","names":["path","join","homedir","stat","path","z","args","z","handler","args","z","parseToml","readFile","configPath","handler","args","root","clients","entry","z","handler","args","z","handler","args","z","handler","args","path","z","args","args","topIdx","existing","createHash","rows","path","rows","homedir","join","resolve","z","displayUrl","init_graph","init_graph","args","resolve","readFile","mkdir","writeFile","homedir","join","path","homedir","join","searchVaultWithContextFit","path","readFile","join","ok","errorResponse","path","fs","path","init_wikilinks","open","createHash","fs","path","stat","countWords","toPosix","init_wikilinks","fs","path","stat","posix","init_chunker","lineOf","init_sections","computeSectionOffsetRanges","init_chunker","init_sections","path","upsert","insertWikilinks","writeAllEdges","init_chunker","isContextFit","relative","randomUUID","init_indexer","fs","isAbsolute","resolve","sep","fs","basename","matter","extractTitle","countWords","stat","init_indexer","z","z","requiredKeys","baseShape","path","readFile","z","init_schema","z","init_loader","init_schema","init_loader","fs","SENTINEL_FILENAME","args","obsidian_fs_exports","fs","matter","SCHEME","init_obsidian_fs","ok","path","isPlainObject","stripWikilinks","docId","ObsidianFsSource","ObsidianFsDelivery","init_registry","init_registry","createHash","randomBytes","args","args","args","init_chunk_id","init_chunk_id","WIKILINK_RE","args","findBriefByTarget","args","DEFAULT_BRIEF_SINK_NAME","open","readFile","unlink","mkdir","homedir","join","lockDir","lockPath","isProcessAlive","readOwnerPid","path","DEFAULT_BRIEF_SINK_NAME","DEFAULT_BRIEF_SINK_NAME","init_resources","init_chunk_id","init_resources","args","args","docId","path","displayUrl","path","init_indexer","path","resolve","indexVaultWithContextFit","isContextFit","result","path","chokidar","nativeSep","SCHEME","handler","resolve","init_obsidian_fs","z","DOC_ID_PATTERN","CONTRACT_PATH_REGEX","z","init_registry","slugify","args","init_audit","z","init_schema","CONTRACT_PATH_REGEX","ok","init_loader","init_schema","init_registry","init_audit","slugify","args","path","args","args","MCP_VERB_RE","args","z","args","init_audit","args","BASELINE_VERBS","init_resources","init_registry","init_audit","init_schema","init_loader","init_resources","init_audit","init_audit","init_vault","init_indexer","init_audit","row","aggregateEntries","safeParse","path","args","init_schema","path","indexVaultWithContextFit","result","init_notes","init_schema","displayUrlFor","path","init_search","init_graph","init_memory","init_brief","emptyResult","args","args","docId","path","init_audit","init_assembly","init_contracts","homedir","isContextFit","args","handler","ok","errorResponse","indexVault","init_graph","init_obsidian_fs","init_indexer","init_vault","init_notes","init_search","init_memory","init_brief","init_assembly","init_contracts","loadConfig","VaultManager","OllamaClient","indexVault","indexVaultWithContextFit","addVault","path"]} \ No newline at end of file