@owrede/vault-memory 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -90
- package/dist/cli.js +708 -2
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +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/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/audit.ts","../src/db/queries/models.ts","../src/db/queries/fts.ts","../src/db/queries/aliases.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/search/hybrid.ts","../src/search/glob.ts","../src/search/index.ts","../src/rerank/reranker.ts","../src/rerank/onnx-reranker.ts","../src/rerank/index.ts","../src/graph/graph.ts","../src/graph/index.ts","../src/frontmatter/query.ts","../src/reader/hash.ts","../src/reader/scanner.ts","../src/reader/wikilinks.ts","../src/reader/parser.ts","../src/reader/index.ts","../src/chunker/tokens.ts","../src/chunker/headings.ts","../src/chunker/chunker.ts","../src/chunker/index.ts","../src/indexer/resolver.ts","../src/indexer/indexer.ts","../src/write/fs.ts","../src/frontmatter/update.ts","../src/frontmatter/index.ts","../src/indexer/single.ts","../src/indexer/catchup.ts","../src/indexer/shadow.ts","../src/indexer/vacuum.ts","../src/indexer/index.ts","../src/write/write.ts","../src/write/index.ts","../src/audit/audit.ts","../src/audit/index.ts","../src/watcher/queue.ts","../src/watcher/watcher.ts","../src/watcher/suppression.ts","../src/watcher/index.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\nconst VaultConfigSchema = z.object({\n name: z.string().min(1),\n path: z.string().min(1),\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\nconst AppConfigSchema = z.object({\n server: ServerConfigSchema.optional().default({}),\n vaults: z.array(VaultConfigSchema).optional().default([]),\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};\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(\n `Failed to parse TOML at ${path}: ${(err as Error).message}`,\n );\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 };\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 Claude Code can spawn\n * the MCP server when the user opens 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` Claude Code skill.\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 /** 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/**\",\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(\n (v) => resolve(v.path) === resolvedPath,\n );\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 });\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}\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 `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(\n `Failed to read existing .mcp.json at ${mcpPath}: ${(err as Error).message}`,\n );\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 {\n AddVaultOptions,\n AddVaultResult,\n AddVaultStep,\n} from \"./add-vault.js\";\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\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) => 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): 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<[], { name: string }>(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'embeddings\\\\_%' ESCAPE '\\\\'\",\n )\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<[], { chunk_id: number; model_id: number; vector: Buffer }>(\n `SELECT chunk_id, model_id, vector FROM ${name}`,\n )\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(\n `INSERT INTO ${newName} (chunk_id, vector) VALUES (?, ?)`,\n );\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\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:\n \"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","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { NoteRow } from \"../../types.js\";\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\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\n constructor(private readonly db: BetterSqlite3.Database) {\n this._selectByPath = db.prepare<[string], NoteRow>(\n \"SELECT * FROM notes WHERE path = ?\",\n );\n this._selectById = db.prepare<[number], NoteRow>(\n \"SELECT * FROM notes WHERE id = ?\",\n );\n this._insert = db.prepare(`\n INSERT INTO notes (path, content, frontmatter, title, hash, body_hash, mtime, word_count, created_at, updated_at)\n VALUES (@path, @content, @frontmatter, @title, @hash, @body_hash, @mtime, @word_count, @now, @now)\n `);\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 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 }>(\n \"SELECT COUNT(*) AS c FROM notes\",\n );\n }\n\n upsertByPath(input: UpsertNoteInput): { id: number; isNew: boolean } {\n const existing = this._selectByPath.get(input.path);\n const now = Date.now();\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 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 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","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { ChunkRow } from \"../../types.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\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)\n VALUES (@note_id, @idx, @text, @heading_path, @start_offset, @end_offset, @token_count)\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>(\n \"SELECT * FROM chunks WHERE id = ?\",\n );\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 });\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<\n [string, number],\n { chunk_id: number; distance: number }\n >;\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(\n `EmbeddingsQueries: model_id ${modelId} not found in models table`,\n );\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(\n `INSERT INTO ${table} (chunk_id, vector) VALUES (?, ?)`,\n ),\n deleteByChunk: this.db.prepare(\n `DELETE FROM ${table} WHERE chunk_id = ?`,\n ),\n deleteAll: this.db.prepare(`DELETE FROM ${table}`),\n search: this.db.prepare<\n [string, number],\n { chunk_id: number; distance: number }\n >(\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(\n modelId: number,\n queryVector: number[],\n topK: number,\n ): 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(\n \"DELETE FROM wikilinks WHERE source_note = ?\",\n );\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","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\nexport interface ListWritesFilter {\n noteId?: number;\n op?: string;\n since?: number;\n limit?: number;\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)\n VALUES (@note_id, @op, @previous_hash, @new_hash, @expected_hash, @client_id, @diff_summary, @at)\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 });\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 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","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>(\n \"SELECT * FROM models WHERE name = ?\",\n );\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>(\n \"SELECT * FROM models WHERE id = ?\",\n );\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]>(\n \"UPDATE models SET active = 1 WHERE id = ?\",\n );\n this._listAll = db.prepare<[], ModelRow>(\n \"SELECT * FROM models ORDER BY id\",\n );\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 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 }\n\n search(query: string, topK: number, withSnippet = false): BM25Hit[] {\n const sanitized = FtsQueries.sanitize(query);\n if (sanitized.length === 0) return [];\n\n if (withSnippet) {\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 rows = this._search.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 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\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(\n `DELETE FROM note_aliases WHERE note_id = ?`,\n );\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 }\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 BetterSqlite3 from \"better-sqlite3\";\nimport * as sqliteVec from \"sqlite-vec\";\n\nimport { MIGRATIONS } 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 { AuditQueries } from \"./queries/audit.js\";\nimport { ModelsQueries } from \"./queries/models.js\";\nimport { FtsQueries } from \"./queries/fts.js\";\nimport { AliasesQueries } from \"./queries/aliases.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 readonly audit: AuditQueries;\n readonly models: ModelsQueries;\n readonly fts: FtsQueries;\n readonly aliases: AliasesQueries;\n\n constructor(dbPath: string) {\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 this.audit = new AuditQueries(this.handle);\n this.fts = new FtsQueries(this.handle);\n this.aliases = new AliasesQueries(this.handle);\n }\n\n static async open(dbPath: string): Promise<Database> {\n return new Database(dbPath);\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 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);\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\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 const db = new Database(dbPath);\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(\n `Unknown vault: \"${name}\". Configured vaults: ${known}`,\n );\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>(\n fn: () => Promise<T>,\n options: RetryOptions,\n): 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 {\n EmbedRequest,\n EmbedResponse,\n OllamaClientOptions,\n} from \"../types.js\";\nimport { withRetry } from \"./retry.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 * 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(\n batches.map((batch) => this.embedBatch(model, batch)),\n );\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(\n `${this.endpoint}/api/embed`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n },\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 * 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(\n `${this.endpoint}/api/tags`,\n { method: \"GET\" },\n );\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 = err instanceof Error ? err.message : String(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(\n url: string,\n init: RequestInit,\n ): 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 { withRetry } from \"./retry.js\";\nexport type { RetryOptions } from \"./retry.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 { SearchHit } from \"../types.js\";\nimport type { Reranker } from \"../rerank/index.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}\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(\n undefined,\n );\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(\n opts: HybridSearchOptions,\n): 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 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 ),\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 // 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 try {\n const scores = await opts.reranker.score(query, texts);\n if (scores.length !== indexed.length) {\n throw new Error(\n `reranker returned ${scores.length} scores for ${indexed.length} chunks`,\n );\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 hits.push(hit);\n }\n\n return hits;\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): 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(\n activeModel.id,\n vec,\n fanK,\n );\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 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);\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 * 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(\n path: string,\n patterns: readonly string[],\n): 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 {\n HybridSearchOptions,\n RankedList,\n RrfMergeResult,\n} from \"./hybrid.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(\n `Reranker: expected ${chunks.length} vectors, got ${res.vectors.length}`,\n );\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 =\n 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 * Graph operations — high-level wikilink queries for MCP tool handlers.\n *\n * Thin layer above `vault.db.wikilinks`. Returns enriched results with\n * source/target paths and titles so callers don't need to re-query notes.\n */\n\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\nexport interface ForwardLinkResult {\n targetPath: string;\n resolved: boolean;\n targetTitle: string | null;\n anchor: string | null;\n linkText: string | null;\n}\n\nexport interface BrokenLinkResult {\n sourcePath: string;\n sourceTitle: string;\n targetPath: string;\n lineNumber: number | null;\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(\n vault: Vault,\n notePath: string,\n): BacklinkResult[] {\n const note = vault.db.notes.getByPath(notePath);\n if (!note) {\n throw new Error(`Note not found: ${notePath}`);\n }\n\n const rows = vault.db.wikilinks.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 });\n }\n return results;\n}\n\n/**\n * Get all wikilinks 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 const rows = vault.db.wikilinks.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 targetPath: row.targetPath,\n resolved,\n targetTitle,\n anchor: row.anchor,\n linkText: row.linkText,\n });\n }\n return results;\n}\n\n/**\n * List all broken wikilinks in the vault (where target_note IS NULL).\n *\n * Note: `lineNumber` is currently always `null` — the DB-layer\n * `resolveBrokenLinks()` query doesn't return it, and `getForwardLinks()`\n * doesn't expose `lineNumber` in its row type. Enriching here would require\n * either changing the DB layer (out of scope for this module) or a separate\n * raw query. Acceptable per spec; documented for future enhancement.\n */\nexport function findBrokenLinks(vault: Vault): BrokenLinkResult[] {\n const rows = vault.db.wikilinks.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 targetPath: row.targetPath,\n lineNumber: null,\n });\n }\n return results;\n}\n","export { listBacklinks, listForwardLinks, findBrokenLinks } from \"./graph.js\";\nexport type {\n BacklinkResult,\n ForwardLinkResult,\n BrokenLinkResult,\n} from \"./graph.js\";\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 =\n | Scalar\n | { $in: Scalar[] }\n | { $exists: boolean }\n | { $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(\n vault: Vault,\n input: QueryFrontmatterInput,\n): 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 { 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(\n (k) => JSON.stringify(k) + \":\" + canonicalJsonStringify(obj[k]),\n );\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\";\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(\n rootPath: string,\n options?: ScanOptions,\n): 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\nasync function walk(\n root: string,\n dir: string,\n matchers: RegExp[],\n out: string[],\n): 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 (\n m !== null &&\n m[1] !== undefined &&\n m[1][0] === fenceMarker\n ) {\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","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 { 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(\n absolutePath: string,\n vaultRoot: string,\n): 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(\n path.relative(path.resolve(vaultRoot), path.resolve(absolutePath)),\n );\n\n return {\n relativePath,\n content,\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","export { scanVault } from \"./scanner.js\";\nexport type { ScanOptions } from \"./scanner.js\";\nexport { parseNote } from \"./parser.js\";\nexport { extractWikilinks } from \"./wikilinks.js\";\nexport {\n sha256,\n canonicalJsonStringify,\n computeNoteHash,\n computeBodyHash,\n} from \"./hash.js\";\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 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(\n headings: HeadingRef[],\n offset: number,\n): 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 * 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(\n content: string,\n headings: HeadingRef[],\n _maxChars: number,\n): 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(\n content: string,\n span: Span,\n maxChars: number,\n): 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(\n content: string,\n span: Span,\n maxChars: number,\n): 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 * 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, parseNote } from \"../reader/index.js\";\nimport { chunkNote } from \"../chunker/index.js\";\nimport { OllamaClient } from \"../ollama/index.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { ParsedNote, ParsedWikilink } from \"../types.js\";\nimport { WikilinkResolver } from \"./resolver.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: OllamaClient;\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(\n vault: Vault,\n options: IndexerOptions,\n): Promise<IndexRunResult> {\n const startedAt = Date.now();\n const runId = randomUUID();\n const mode = options.mode ?? \"incremental\";\n const log = options.onProgress ?? (() => {});\n\n // 1. Resolve / upsert model in DB\n log(`Probing Ollama model: ${options.embeddingModel}`);\n const health = await options.ollama.healthCheck();\n if (!health.ok) {\n throw new Error(`Ollama unreachable: ${health.error ?? \"unknown error\"}`);\n }\n const modelExists = await options.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 options.ollama.embed({\n model: options.embeddingModel,\n texts: [\"probe\"],\n });\n const dim = probe.dim;\n const 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 let secondaryModelRow: { id: number; dim: number } | null = null;\n if (options.secondaryEmbeddingModel) {\n const secName = options.secondaryEmbeddingModel;\n log(`Probing secondary (shadow) model: ${secName}`);\n const secExists = await options.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 options.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 vault.db.audit.startRun({\n runId,\n vaultName: vault.config.name,\n modelId: modelRow.id,\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 }\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 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 // 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 const noteExisted = !upsert.isNew;\n const existing = noteExisted ? vault.db.notes.getById(upsert.id) : null;\n // After upsert the DB row reflects the new state; we need to know if the hash\n // actually changed. The NotesQueries.upsertByPath returns isNew, but we also\n // need \"isModified\". Workaround: check chunks count — if a note has no chunks,\n // it needs (re-)indexing.\n const chunkCount = vault.db.chunks.getByNote(upsert.id).length;\n const needsReindex =\n mode === \"full\" || upsert.isNew || chunkCount === 0;\n\n if (upsert.isNew) notesIndexed++;\n else if (needsReindex) notesUpdated++;\n\n if (needsReindex) {\n parsedNotes.push({ parsed, noteId: upsert.id, needsReindex: true });\n }\n\n // Suppress unused-var warning for `existing` — kept for clarity above\n void existing;\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 old chunks (handles re-index)\n vault.db.chunks.deleteByNote(noteId);\n vault.db.wikilinks.deleteByNote(noteId);\n\n const chunks = chunkNote(parsed.content);\n\n if (chunks.length === 0) {\n // empty note — record wikilinks anyway, but no chunks/embeddings\n insertWikilinks(vault, noteId, parsed.wikilinks);\n continue;\n }\n\n // Insert chunks first to get IDs\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 }));\n const chunkIds = vault.db.chunks.insertBatch(noteId, chunkInputs);\n\n // Embed\n const embedResult = await options.ollama.embed({\n model: options.embeddingModel,\n texts: chunks.map((c) => c.text),\n });\n if (embedResult.dim !== dim) {\n throw new Error(\n `Embedding dimension mismatch: expected ${dim}, got ${embedResult.dim}`,\n );\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 options.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 // Wikilinks\n insertWikilinks(vault, noteId, parsed.wikilinks, 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 = err instanceof Error ? err.message : String(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}\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\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 * 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(\n absPath: string,\n content: string,\n): 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 (\n realTarget !== realRoot &&\n !realTarget.startsWith(realRootWithSep)\n ) {\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\n ? real\n : 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 * 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\nimport { promises as fs } from \"node:fs\";\nimport matter from \"gray-matter\";\nimport type { Vault } from \"../vault/index.js\";\nimport { computeNoteHash, computeBodyHash } from \"../reader/hash.js\";\nimport { extractAliases } from \"../indexer/indexer.js\";\nimport { atomicWriteFile, safeJoinInsideVault } from \"../write/fs.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public API\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface UpdateFrontmatterInput {\n vault: Vault;\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\";\n currentHash?: string;\n message: 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\nfunction computeHash(content: string, data: Record<string, unknown>): string {\n // Mirror reader/parser: empty frontmatter → canonicalJson({}). Delegates to\n // computeNoteHash so canonical (key-sorted) JSON is used everywhere.\n const fmForHash = Object.keys(data).length > 0 ? data : {};\n return computeNoteHash(content, fmForHash);\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 extractTitle(content: string, fallback: 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 fallback;\n}\n\nfunction basenameNoMd(relativePath: string): string {\n const base = relativePath.split(\"/\").pop() ?? relativePath;\n return base.endsWith(\".md\") ? base.slice(0, -3) : base;\n}\n\nexport async function updateFrontmatter(\n input: UpdateFrontmatterInput,\n): Promise<UpdateResult> {\n const { vault, relativePath, merge, expectedHash, clientId } = input;\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 const absPath = await safeJoinInsideVault(vault.config.path, relativePath);\n\n let raw: string;\n try {\n raw = await fs.readFile(absPath, \"utf8\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n ok: false,\n reason: \"note_not_found\",\n message: `Failed to read file: ${msg}`,\n };\n }\n\n const parsed = matter(raw);\n const content = parsed.content;\n const data = (parsed.data ?? {}) as Record<string, unknown>;\n\n const currentHash = computeHash(content, data);\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(data, 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 // Build the new file. gray-matter.stringify writes frontmatter even when\n // empty — we explicitly handle the all-deleted case by emitting just body.\n const fullText =\n Object.keys(next).length === 0 ? content : matter.stringify(content, next);\n\n input.onBeforeFsWrite?.();\n await atomicWriteFile(absPath, fullText);\n\n const stat = await fs.stat(absPath);\n const newHash = computeHash(content, next);\n const title = extractTitle(content, basenameNoMd(relativePath));\n const wordCount = countWords(content);\n const fmJson = Object.keys(next).length > 0 ? JSON.stringify(next) : null;\n const aliasKeyTouched = \"aliases\" in merge || \"alias\" in merge;\n\n // Codex MEDIUM-1: atomic DB updates + FS rollback on failure. The note\n // already exists on disk (we read `raw` above), so rollback restores it.\n let upsertId: number;\n try {\n upsertId = vault.db.transaction(() => {\n const up = vault.db.notes.upsertByPath({\n path: relativePath,\n content,\n frontmatter: fmJson,\n title,\n hash: newHash,\n bodyHash: computeBodyHash(content),\n mtime: Math.floor(stat.mtimeMs),\n wordCount,\n });\n if (aliasKeyTouched) {\n vault.db.aliases.setForNote(up.id, extractAliases(next));\n }\n vault.db.audit.recordWrite({\n noteId: up.id,\n op: \"update\",\n previousHash: currentHash,\n newHash,\n expectedHash: expectedHash ?? null,\n clientId: clientId ?? null,\n diffSummary: JSON.stringify(diff),\n });\n return up.id;\n });\n } catch (dbErr) {\n input.onBeforeFsWrite?.();\n try {\n await atomicWriteFile(absPath, raw);\n } catch {\n // Rollback failed — re-throw original; catch-up will reconcile.\n }\n throw dbErr;\n }\n\n return {\n ok: true,\n newHash,\n noteId: upsertId,\n diff,\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 * 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 \"../reader/index.js\";\nimport { chunkNote } from \"../chunker/index.js\";\nimport {\n extractAliases,\n resolveWikilinkTarget,\n} from \"./indexer.js\";\nimport type { 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 ollama: OllamaClient;\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(\n options: IndexNoteOptions,\n): 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(\n existing.id,\n extractAliases(parsed.frontmatter),\n );\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\n ? JSON.stringify(parsed.frontmatter)\n : 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(\n upsert.id,\n extractAliases(parsed.frontmatter),\n );\n vault.db.wikilinks.deleteByNote(upsert.id);\n insertWikilinks(vault, upsert.id, parsed.wikilinks);\n return {\n status: \"indexed\",\n notePath: parsed.relativePath,\n noteId: upsert.id,\n chunksCreated: 0,\n isNew: false,\n };\n }\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 activeModel = vault.db.models.getActive();\n if (!activeModel) {\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 (activeModel.name !== embeddingModel) {\n throw new Error(\n `single-indexer: active model \"${activeModel.name}\" does not match ` +\n `requested \"${embeddingModel}\". Run a full re-index to switch models.`,\n );\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\n ? JSON.stringify(parsed.frontmatter)\n : 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(\n upsert.id,\n extractAliases(parsed.frontmatter),\n );\n\n // 7. Wipe derived layer for this note.\n vault.db.chunks.deleteByNote(upsert.id);\n vault.db.wikilinks.deleteByNote(upsert.id);\n\n // 8. Chunk + embed + persist.\n const chunks = chunkNote(parsed.content);\n\n if (chunks.length === 0) {\n insertWikilinks(vault, upsert.id, parsed.wikilinks);\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 })),\n );\n\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 insertWikilinks(vault, upsert.id, parsed.wikilinks);\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(\n status: \"outside_vault\" | \"missing\" | \"parse_error\",\n): 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 */\nfunction insertWikilinks(\n vault: Vault,\n sourceNoteId: number,\n wikilinks: ParsedWikilink[],\n): void {\n if (wikilinks.length === 0) return;\n\n const inputs = wikilinks.map((wl) => {\n const target = resolveWikilinkTarget(vault, 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 * 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, parseNote } from \"../reader/index.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 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(\n options: CatchupOptions,\n): 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\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 ollama: options.ollama,\n });\n if (result.status === \"indexed\") {\n reindexed++;\n log(\n `catch-up indexed ${parsed.relativePath} (${result.isNew ? \"new\" : \"updated\"})`,\n );\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 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\";\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(\n options: ShadowIndexOptions,\n): 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(\n `Shadow model \"${model}\" not found in Ollama. ` +\n `Run: ollama pull ${model}`,\n );\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\n .prepare<[], PendingChunkRow>(pendingSql)\n .all();\n const totalRow = vault.db.handle\n .prepare<[], { c: number }>(totalSql)\n .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 = err instanceof Error ? err.message : String(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 }>(\n `SELECT COUNT(*) AS c FROM embeddings_m${m.id}_d${m.dim}`,\n )\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(\n vault: Vault,\n targetModelName: string,\n): 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(\n `DELETE FROM ${table} WHERE chunk_id = ?`,\n );\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 {\n startShadowIndex,\n listModels,\n switchActiveModel,\n} 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 * 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 \"../reader/index.js\";\nimport { extractAliases } from \"../indexer/index.js\";\nimport { atomicWriteFile, safeJoinInsideVault } from \"./fs.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\";\n currentHash?: string;\n currentContent?: string;\n message: 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 /** For audit_log entry. Defaults to \"claude-code\". */\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\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}\n\nconst DEFAULT_CLIENT_ID = \"claude-code\";\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(\n content: string,\n frontmatter: Record<string, unknown> | null,\n): 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(\n absPath: string,\n): Promise<{ raw: string; content: string; frontmatter: Record<string, unknown> | null; hash: string } | 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 } = input;\n const frontmatter = input.frontmatter ?? null;\n const clientId = input.clientId ?? DEFAULT_CLIENT_ID;\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 const fileText =\n frontmatter !== null && Object.keys(frontmatter).length > 0\n ? matter.stringify(content, frontmatter)\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(\n `Internal error: file disappeared after write: ${relativePath}`,\n );\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 });\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 } = input;\n const clientId = input.clientId ?? DEFAULT_CLIENT_ID;\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 });\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","export { 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 * 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\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\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\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 };\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 =\n 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 {\n AuditLogEntry,\n IndexRunEntry,\n GetAuditLogInput,\n GetIndexRunsInput,\n} from \"./audit.js\";\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(\n `[DebouncedQueue] onFlush failed for ${event.path} (${event.kind}):`,\n err,\n );\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 * 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 { posix } from \"node:path\";\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\";\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\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 = err instanceof Error ? err.message : String(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, {\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: 200,\n pollInterval: 50,\n },\n followSymlinks: false,\n });\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 = err instanceof Error ? err.message : String(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.fsWatcher) {\n await this.fsWatcher.close();\n this.fsWatcher = null;\n }\n }\n\n // ─── internal ──────────────────────────────────────────────────────────\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 if (event.kind === \"delete\") {\n const result = removeNote(this.opts.vault, event.path);\n if (result.removed) {\n this.opts.log(`removed ${relativePath}`);\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 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 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\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\ninterface Entry {\n expiresAt: number;\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 /** Mark a path as \"expect a filesystem event for this — please ignore it\". */\n add(path: string, ttlMs?: number): void {\n this.prune();\n const ttl = ttlMs ?? this.defaultTtlMs;\n this.entries.set(path, { expiresAt: this.now() + ttl });\n }\n\n /**\n * If path is suppressed, remove the entry and return true (skip event).\n * Otherwise return false.\n */\n consume(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 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","export { 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\";\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 { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type BetterSqlite3 from \"better-sqlite3\";\nimport { z } from \"zod\";\nimport { loadConfig } from \"./config/index.js\";\nimport { VaultManager } from \"./vault/index.js\";\nimport { OllamaClient } from \"./ollama/index.js\";\nimport { FtsQueries } from \"./db/index.js\";\nimport { hybridSearch, matchesAnyGlob } from \"./search/index.js\";\nimport { OllamaReranker, OnnxReranker } from \"./rerank/index.js\";\nimport type { Reranker } from \"./rerank/index.js\";\nimport { homedir } from \"node:os\";\nimport { join as joinPath } from \"node:path\";\nimport {\n listBacklinks,\n listForwardLinks,\n findBrokenLinks,\n} from \"./graph/index.js\";\nimport { queryFrontmatter, updateFrontmatter } from \"./frontmatter/index.js\";\nimport { writeNote, deleteNote } from \"./write/index.js\";\nimport { getAuditLog, getIndexRuns } from \"./audit/index.js\";\nimport { SuppressionSet, VaultWatcher } from \"./watcher/index.js\";\nimport {\n catchupVault,\n listModels,\n startShadowIndex,\n switchActiveModel,\n vacuumEmbeddings,\n} from \"./indexer/index.js\";\nimport type { SearchHit } from \"./types.js\";\n\nconst VERSION = \"0.9.1\";\n\n// ─── Tool Input Schemas ──────────────────────────────────────────────────────\n\nconst ReadNoteArgs = z.object({\n vault: z.string(),\n path: z.string(),\n});\n\nconst SearchArgs = z.object({\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 /** Glob patterns of vault-relative paths to exclude from results. Useful\n * for filtering self-referential notes (e.g. an eval note that lists\n * the same keywords it's testing) or auxiliary indices. */\n exclude_paths: z.array(z.string()).optional(),\n});\n\nconst HybridSearchArgs = SearchArgs.extend({\n rrf_k: z.number().int().positive().max(1000).optional().default(60),\n /** When true AND a `reranker_model` is configured, runs a cross-encoder\n * rerank pass over the top candidates. Silently ignored otherwise. */\n rerank: z.boolean().optional().default(false),\n});\n\nconst VaultPathArgs = z.object({\n vault: z.string(),\n path: z.string(),\n});\n\nconst ForwardLinksArgs = VaultPathArgs.extend({\n include_broken: z.boolean().optional().default(true),\n});\n\nconst FindBrokenLinksArgs = z.object({\n vault: z.string(),\n});\n\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\nconst QueryFrontmatterArgs = z.object({\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// ─── Phase 3 input schemas ──────────────────────────────────────────────────\n\nconst WriteNoteArgs = z.object({\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\nconst UpdateFrontmatterArgs = z.object({\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\nconst DeleteNoteArgs = z.object({\n vault: z.string(),\n path: z.string(),\n expected_hash: z.string(),\n client_id: z.string().optional(),\n});\n\nconst AuditLogArgs = z.object({\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});\n\nconst IndexRunsArgs = z.object({\n vault: z.string(),\n limit: z.number().int().positive().max(200).optional().default(20),\n});\n\n// ─── Phase 7c — shadow-indexing / model switch ──────────────────────────────\n\nconst ListModelsArgs = z.object({\n vault: z.string(),\n});\n\nconst StartShadowIndexArgs = z.object({\n vault: z.string(),\n model: z.string().min(1),\n batch_size: z.number().int().positive().max(256).optional(),\n});\n\nconst SwitchActiveModelArgs = z.object({\n vault: z.string(),\n model_name: z.string().min(1),\n});\n\nconst VacuumEmbeddingsArgs = z.object({\n vault: z.string(),\n});\n\n// ─── v0.9.0 — Agent-Compatibility & Self-Orientation ────────────────────────\n//\n// `search` / `fetch` follow the OB1 / ChatGPT-Connector / Claude.ai\n// Deep-Research tool spec: a flat shape with opaque `id`s. The `id` here\n// encodes `<vault>:<notePath>` so `fetch(id)` can deterministically resolve\n// back to the underlying note without round-tripping a separate vault arg.\n\nconst SearchCompatArgs = z.object({\n query: z.string().min(1),\n limit: z.number().int().positive().max(50).optional().default(10),\n});\n\nconst FetchCompatArgs = z.object({\n id: z.string().min(1),\n});\n\nconst VaultStatsArgs = z.object({\n vault: z.string().optional(),\n});\n\nconst RecentNotesArgs = z.object({\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// ─── Server bootstrap ────────────────────────────────────────────────────────\n\nexport async function serve(): Promise<void> {\n const config = await loadConfig();\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 defaultModel =\n 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 ??\n (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 // One SuppressionSet shared by all vaults (paths are vault-relative; the\n // chance of a collision across vaults is negligible and a false positive\n // just means one event is dropped — harmless).\n const suppression = new SuppressionSet({ ttlMs: 2000 });\n const watchers = new Map<string, VaultWatcher>();\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 if (!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 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 = err instanceof Error ? err.message : String(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 };\n\n const shutdown = async (): Promise<void> => {\n for (const w of watchers.values()) {\n await w.drain();\n await w.stop();\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 const server = new Server(\n { name: \"vault-memory\", version: VERSION },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: \"list_vaults\",\n description:\n \"List configured vaults with their status (note count, last indexed run).\",\n inputSchema: { type: \"object\", properties: {} },\n },\n {\n name: \"read_note\",\n description:\n \"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:\n \"Vault-relative path with forward slashes, ending in .md\",\n },\n },\n },\n },\n {\n name: \"search_semantic\",\n description:\n \"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:\n \"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:\n \"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.\",\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:\n \"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 },\n },\n },\n {\n name: \"list_backlinks\",\n description: \"Find all notes that link TO a given note.\",\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:\n \"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:\n \"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:\n \"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 },\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 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:\n \"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 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.\",\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 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 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 }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n switch (name) {\n case \"list_vaults\":\n return ok(handleListVaults(manager));\n\n case \"read_note\": {\n const parsed = ReadNoteArgs.parse(args ?? {});\n return ok(handleReadNote(manager, parsed.vault, parsed.path));\n }\n\n case \"search_semantic\": {\n const parsed = SearchArgs.parse(args ?? {});\n return ok(\n await handleSearchSemantic(\n manager,\n ollama,\n defaultModel,\n activeVault,\n parsed.query,\n parsed.vaults,\n parsed.top_k,\n parsed.exclude_paths,\n ),\n );\n }\n\n case \"search_text\": {\n const parsed = SearchArgs.parse(args ?? {});\n return ok(\n handleSearchText(\n manager,\n activeVault,\n parsed.query,\n parsed.vaults,\n parsed.top_k,\n parsed.exclude_paths,\n ),\n );\n }\n\n case \"search_hybrid\": {\n const parsed = HybridSearchArgs.parse(args ?? {});\n return ok(\n await handleSearchHybrid(\n manager,\n ollama,\n defaultModel,\n activeVault,\n parsed.query,\n parsed.vaults,\n parsed.top_k,\n parsed.rrf_k,\n parsed.exclude_paths,\n parsed.rerank ? reranker : undefined,\n ),\n );\n }\n\n case \"list_backlinks\": {\n const parsed = VaultPathArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n return ok({ backlinks: listBacklinks(vault, parsed.path) });\n }\n\n case \"list_forward_links\": {\n const parsed = ForwardLinksArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n return ok({\n links: listForwardLinks(vault, parsed.path, parsed.include_broken),\n });\n }\n\n case \"find_broken_links\": {\n const parsed = FindBrokenLinksArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n return ok({ broken: findBrokenLinks(vault) });\n }\n\n case \"query_frontmatter\": {\n const parsed = QueryFrontmatterArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const hits = queryFrontmatter(vault, {\n where: parsed.where as Record<string, never>,\n limit: parsed.limit,\n });\n return ok({\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\n case \"write_note\": {\n const parsed = WriteNoteArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n // Suppress the watcher event triggered by our own atomic rename.\n // The hook fires inside writeNote() ONLY if the write actually\n // happens (no permission/hash conflict), so a failed write never\n // accidentally masks a real external edit shortly after.\n const result = await writeNote({\n vault,\n relativePath: parsed.path,\n content: parsed.content,\n frontmatter: parsed.frontmatter ?? null,\n expectedHash: parsed.expected_hash,\n clientId: parsed.client_id,\n onBeforeFsWrite: () => suppression.add(parsed.path),\n });\n return ok(result);\n }\n\n case \"update_frontmatter\": {\n const parsed = UpdateFrontmatterArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const result = await updateFrontmatter({\n vault,\n relativePath: parsed.path,\n merge: parsed.merge,\n expectedHash: parsed.expected_hash,\n clientId: parsed.client_id,\n onBeforeFsWrite: () => suppression.add(parsed.path),\n });\n return ok(result);\n }\n\n case \"delete_note\": {\n const parsed = DeleteNoteArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const result = await deleteNote({\n vault,\n relativePath: parsed.path,\n expectedHash: parsed.expected_hash,\n clientId: parsed.client_id,\n onBeforeFsWrite: () => suppression.add(parsed.path),\n });\n return ok(result);\n }\n\n case \"audit_log\": {\n const parsed = AuditLogArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const entries = getAuditLog({\n vault,\n notePath: parsed.note_path,\n op: parsed.op,\n since: parsed.since,\n limit: parsed.limit,\n });\n return ok({ entries, count: entries.length });\n }\n\n case \"list_models\": {\n const parsed = ListModelsArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const models = listModels(vault);\n return ok({ models, count: models.length });\n }\n\n case \"start_shadow_index\": {\n const parsed = StartShadowIndexArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const result = await startShadowIndex({\n vault,\n model: parsed.model,\n ollama,\n batchSize: parsed.batch_size,\n log: (m) =>\n process.stderr.write(`[shadow:${vault.config.name}] ${m}\\n`),\n });\n return ok(result);\n }\n\n case \"switch_active_model\": {\n const parsed = SwitchActiveModelArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const result = switchActiveModel(vault, parsed.model_name);\n return ok(result);\n }\n\n case \"vacuum_embeddings\": {\n const parsed = VacuumEmbeddingsArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const result = vacuumEmbeddings(vault);\n return ok(result);\n }\n\n case \"index_runs\": {\n const parsed = IndexRunsArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const runs = getIndexRuns({ vault, limit: parsed.limit });\n return ok({ runs, count: runs.length });\n }\n\n case \"search\": {\n const parsed = SearchCompatArgs.parse(args ?? {});\n return ok(\n await handleSearchCompat(\n manager,\n ollama,\n defaultModel,\n activeVault,\n parsed.query,\n parsed.limit,\n reranker,\n ),\n );\n }\n\n case \"fetch\": {\n const parsed = FetchCompatArgs.parse(args ?? {});\n return ok(handleFetchCompat(manager, parsed.id));\n }\n\n case \"vault_stats\": {\n const parsed = VaultStatsArgs.parse(args ?? {});\n return ok(handleVaultStats(manager, parsed.vault));\n }\n\n case \"recent_notes\": {\n const parsed = RecentNotesArgs.parse(args ?? {});\n return ok(\n handleRecentNotes(\n manager,\n parsed.vault,\n parsed.limit,\n parsed.since,\n ),\n );\n }\n\n default:\n return errorResponse(`Unknown tool: ${name}`);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return errorResponse(message);\n }\n });\n\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 startCatchupAndWatchers().catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[catchup] unexpected failure: ${message}\\n`);\n });\n}\n\n// ─── Tool handlers ───────────────────────────────────────────────────────────\n\nfunction 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\nfunction handleReadNote(\n manager: VaultManager,\n vaultName: string,\n path: string,\n): object {\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 return {\n path: note.path,\n title: note.title,\n content: note.content,\n frontmatter: note.frontmatter ? JSON.parse(note.frontmatter) : null,\n hash: note.hash,\n mtime: note.mtime,\n word_count: note.word_count,\n };\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 */\nfunction 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\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(\n model.id,\n queryVec,\n fanK,\n );\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\n for (const vault of targets) {\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 if (skipped.length > 0) {\n out.note = `Skipped vault(s) currently indexing: ${skipped.join(\", \")}.`;\n }\n return out;\n}\n\nasync 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): 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 const hits = await hybridSearch({\n query,\n embeddingModel: defaultModel,\n ollama,\n vaults: targets,\n topK: innerTopK,\n rrfK,\n includeBreakdown: true,\n reranker,\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 */\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(\n `Invalid id: ${id}. Expected format <vault>:<vault-relative-path>.`,\n );\n }\n return { vault: id.slice(0, idx), path: id.slice(idx + 1) };\n}\n\n/**\n * Build an `obsidian://` URL pointing at a note in the vault. Mirrors the\n * pattern documented in our Linear-backlink convention (memory:\n * feedback_linear_obsidian_backlinks): clickable from any connector UI,\n * opens the actual note locally.\n */\nexport function obsidianUrl(vaultName: string, notePath: string): string {\n return `obsidian://open?vault=${encodeURIComponent(vaultName)}&file=${encodeURIComponent(notePath)}`;\n}\n\nasync function handleSearchCompat(\n manager: VaultManager,\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.\n const hits = await hybridSearch({\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: obsidianUrl(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\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\nfunction handleFetchCompat(manager: VaultManager, 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: obsidianUrl(vaultName, note.path),\n metadata,\n };\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\nfunction handleVaultStats(\n manager: VaultManager,\n vaultFilter: string | undefined,\n): object {\n const targets = vaultFilter\n ? [manager.require(vaultFilter)]\n : 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 }>(\n \"SELECT SUM(word_count) AS total FROM notes\",\n )\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\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\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\nfunction handleRecentNotes(\n manager: VaultManager,\n vaultFilter: string | undefined,\n limit: number,\n since: number | undefined,\n): object {\n const targets = vaultFilter\n ? [manager.require(vaultFilter)]\n : manager.list();\n\n const all: RecentNoteRow[] = [];\n for (const v of targets) {\n const rows = since !== undefined\n ? v.db.handle\n .prepare<\n [number, number],\n { path: string; title: string | null; mtime: number; word_count: number | null; frontmatter: string | null }\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 { path: string; title: string | null; mtime: number; word_count: number | null; frontmatter: string | null }\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\n// ─── Response helpers ────────────────────────────────────────────────────────\n\nfunction ok(data: object): { content: Array<{ type: \"text\"; text: string }> } {\n return {\n content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }],\n };\n}\n\nfunction 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 * 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\n ? [manager.require(vaultName)]\n : manager.list();\n\n for (const vault of targets) {\n const model =\n vault.config.embedding_model ??\n config.server.default_embedding_model ??\n \"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 =\n 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 Claude Code can\n * 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\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 === \"--no-index\") {\n skipIndex = true;\n } else if (arg === \"--help\" || arg === \"-h\") {\n console.error(`Usage: vault-memory add-vault <path> [--name <name>] [--write] [--no-index]\n\nRegisters a vault in ~/.vault-memory/config.toml, writes a .mcp.json\ninto the vault root, and runs an initial index. Idempotent.`);\n return;\n } else if (arg && !arg.startsWith(\"--\") && path === null) {\n path = arg;\n }\n }\n\n if (path === null) {\n console.error(\n \"Usage: vault-memory add-vault <path> [--name <name>] [--write] [--no-index]\",\n );\n process.exit(2);\n }\n\n console.error(`→ Registering vault: ${path}`);\n const result = await addVault({ path, name, writeEnabled });\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 Claude Code — 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","rows","path","rows","homedir","join","resolve","z","path","readFile","join","init_graph","fs","path","init_wikilinks","fs","path","toPosix","init_wikilinks","init_wikilinks","init_chunker","init_chunker","fs","resolve","sep","fs","matter","isPlainObject","countWords","extractTitle","path","upsert","insertWikilinks","init_chunker","relative","randomUUID","init_indexer","fs","basename","matter","computeHash","extractTitle","countWords","init_indexer","init_write","init_audit","init_audit","path","init_indexer","path","resolve","result","path","init_watcher","z","homedir","args","path","init_graph","init_write","init_audit","init_watcher","init_indexer","loadConfig","VaultManager","OllamaClient","indexVault","addVault","path"]}
|
|
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/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/audit.ts","../src/db/queries/models.ts","../src/db/queries/fts.ts","../src/db/queries/aliases.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/search/hybrid.ts","../src/search/glob.ts","../src/search/index.ts","../src/rerank/reranker.ts","../src/rerank/onnx-reranker.ts","../src/rerank/index.ts","../src/graph/graph.ts","../src/graph/index.ts","../src/frontmatter/query.ts","../src/reader/hash.ts","../src/reader/scanner.ts","../src/reader/wikilinks.ts","../src/reader/parser.ts","../src/reader/index.ts","../src/chunker/tokens.ts","../src/chunker/headings.ts","../src/chunker/chunker.ts","../src/chunker/index.ts","../src/indexer/resolver.ts","../src/indexer/indexer.ts","../src/write/fs.ts","../src/frontmatter/update.ts","../src/frontmatter/index.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/indexer/single.ts","../src/indexer/catchup.ts","../src/indexer/shadow.ts","../src/indexer/vacuum.ts","../src/indexer/index.ts","../src/write/write.ts","../src/write/index.ts","../src/audit/audit.ts","../src/audit/index.ts","../src/watcher/queue.ts","../src/watcher/watcher.ts","../src/watcher/suppression.ts","../src/watcher/index.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\nconst VaultConfigSchema = z.object({\n name: z.string().min(1),\n path: z.string().min(1),\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\nconst AppConfigSchema = z.object({\n server: ServerConfigSchema.optional().default({}),\n vaults: z.array(VaultConfigSchema).optional().default([]),\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};\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(\n `Failed to parse TOML at ${path}: ${(err as Error).message}`,\n );\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 };\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 Claude Code can spawn\n * the MCP server when the user opens 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` Claude Code skill.\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 /** 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/**\",\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(\n (v) => resolve(v.path) === resolvedPath,\n );\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 });\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}\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 `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(\n `Failed to read existing .mcp.json at ${mcpPath}: ${(err as Error).message}`,\n );\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 {\n AddVaultOptions,\n AddVaultResult,\n AddVaultStep,\n} from \"./add-vault.js\";\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\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) => 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): 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<[], { name: string }>(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'embeddings\\\\_%' ESCAPE '\\\\'\",\n )\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<[], { chunk_id: number; model_id: number; vector: Buffer }>(\n `SELECT chunk_id, model_id, vector FROM ${name}`,\n )\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(\n `INSERT INTO ${newName} (chunk_id, vector) VALUES (?, ?)`,\n );\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\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:\n \"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","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { NoteRow } from \"../../types.js\";\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\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\n constructor(private readonly db: BetterSqlite3.Database) {\n this._selectByPath = db.prepare<[string], NoteRow>(\n \"SELECT * FROM notes WHERE path = ?\",\n );\n this._selectById = db.prepare<[number], NoteRow>(\n \"SELECT * FROM notes WHERE id = ?\",\n );\n this._insert = db.prepare(`\n INSERT INTO notes (path, content, frontmatter, title, hash, body_hash, mtime, word_count, created_at, updated_at)\n VALUES (@path, @content, @frontmatter, @title, @hash, @body_hash, @mtime, @word_count, @now, @now)\n `);\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 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 }>(\n \"SELECT COUNT(*) AS c FROM notes\",\n );\n }\n\n upsertByPath(input: UpsertNoteInput): { id: number; isNew: boolean } {\n const existing = this._selectByPath.get(input.path);\n const now = Date.now();\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 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 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","import type BetterSqlite3 from \"better-sqlite3\";\nimport type { ChunkRow } from \"../../types.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\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)\n VALUES (@note_id, @idx, @text, @heading_path, @start_offset, @end_offset, @token_count)\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>(\n \"SELECT * FROM chunks WHERE id = ?\",\n );\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 });\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<\n [string, number],\n { chunk_id: number; distance: number }\n >;\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(\n `EmbeddingsQueries: model_id ${modelId} not found in models table`,\n );\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(\n `INSERT INTO ${table} (chunk_id, vector) VALUES (?, ?)`,\n ),\n deleteByChunk: this.db.prepare(\n `DELETE FROM ${table} WHERE chunk_id = ?`,\n ),\n deleteAll: this.db.prepare(`DELETE FROM ${table}`),\n search: this.db.prepare<\n [string, number],\n { chunk_id: number; distance: number }\n >(\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(\n modelId: number,\n queryVector: number[],\n topK: number,\n ): 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(\n \"DELETE FROM wikilinks WHERE source_note = ?\",\n );\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","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\nexport interface ListWritesFilter {\n noteId?: number;\n op?: string;\n since?: number;\n limit?: number;\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)\n VALUES (@note_id, @op, @previous_hash, @new_hash, @expected_hash, @client_id, @diff_summary, @at)\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 });\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 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","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>(\n \"SELECT * FROM models WHERE name = ?\",\n );\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>(\n \"SELECT * FROM models WHERE id = ?\",\n );\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]>(\n \"UPDATE models SET active = 1 WHERE id = ?\",\n );\n this._listAll = db.prepare<[], ModelRow>(\n \"SELECT * FROM models ORDER BY id\",\n );\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 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 }\n\n search(query: string, topK: number, withSnippet = false): BM25Hit[] {\n const sanitized = FtsQueries.sanitize(query);\n if (sanitized.length === 0) return [];\n\n if (withSnippet) {\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 rows = this._search.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 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\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(\n `DELETE FROM note_aliases WHERE note_id = ?`,\n );\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 }\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 BetterSqlite3 from \"better-sqlite3\";\nimport * as sqliteVec from \"sqlite-vec\";\n\nimport { MIGRATIONS } 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 { AuditQueries } from \"./queries/audit.js\";\nimport { ModelsQueries } from \"./queries/models.js\";\nimport { FtsQueries } from \"./queries/fts.js\";\nimport { AliasesQueries } from \"./queries/aliases.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 readonly audit: AuditQueries;\n readonly models: ModelsQueries;\n readonly fts: FtsQueries;\n readonly aliases: AliasesQueries;\n\n constructor(dbPath: string) {\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 this.audit = new AuditQueries(this.handle);\n this.fts = new FtsQueries(this.handle);\n this.aliases = new AliasesQueries(this.handle);\n }\n\n static async open(dbPath: string): Promise<Database> {\n return new Database(dbPath);\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 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);\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\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 const db = new Database(dbPath);\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(\n `Unknown vault: \"${name}\". Configured vaults: ${known}`,\n );\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>(\n fn: () => Promise<T>,\n options: RetryOptions,\n): 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 {\n EmbedRequest,\n EmbedResponse,\n OllamaClientOptions,\n} from \"../types.js\";\nimport { withRetry } from \"./retry.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 * 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(\n batches.map((batch) => this.embedBatch(model, batch)),\n );\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(\n `${this.endpoint}/api/embed`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n },\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 * 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(\n `${this.endpoint}/api/tags`,\n { method: \"GET\" },\n );\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 = err instanceof Error ? err.message : String(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(\n url: string,\n init: RequestInit,\n ): 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 { withRetry } from \"./retry.js\";\nexport type { RetryOptions } from \"./retry.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 { SearchHit } from \"../types.js\";\nimport type { Reranker } from \"../rerank/index.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}\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(\n undefined,\n );\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(\n opts: HybridSearchOptions,\n): 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 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 ),\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 // 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 try {\n const scores = await opts.reranker.score(query, texts);\n if (scores.length !== indexed.length) {\n throw new Error(\n `reranker returned ${scores.length} scores for ${indexed.length} chunks`,\n );\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 hits.push(hit);\n }\n\n return hits;\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): 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(\n activeModel.id,\n vec,\n fanK,\n );\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 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);\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 * 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(\n path: string,\n patterns: readonly string[],\n): 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 {\n HybridSearchOptions,\n RankedList,\n RrfMergeResult,\n} from \"./hybrid.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(\n `Reranker: expected ${chunks.length} vectors, got ${res.vectors.length}`,\n );\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 =\n 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 * Graph operations — high-level wikilink queries for MCP tool handlers.\n *\n * Thin layer above `vault.db.wikilinks`. Returns enriched results with\n * source/target paths and titles so callers don't need to re-query notes.\n */\n\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\nexport interface ForwardLinkResult {\n targetPath: string;\n resolved: boolean;\n targetTitle: string | null;\n anchor: string | null;\n linkText: string | null;\n}\n\nexport interface BrokenLinkResult {\n sourcePath: string;\n sourceTitle: string;\n targetPath: string;\n lineNumber: number | null;\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(\n vault: Vault,\n notePath: string,\n): BacklinkResult[] {\n const note = vault.db.notes.getByPath(notePath);\n if (!note) {\n throw new Error(`Note not found: ${notePath}`);\n }\n\n const rows = vault.db.wikilinks.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 });\n }\n return results;\n}\n\n/**\n * Get all wikilinks 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 const rows = vault.db.wikilinks.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 targetPath: row.targetPath,\n resolved,\n targetTitle,\n anchor: row.anchor,\n linkText: row.linkText,\n });\n }\n return results;\n}\n\n/**\n * List all broken wikilinks in the vault (where target_note IS NULL).\n *\n * Note: `lineNumber` is currently always `null` — the DB-layer\n * `resolveBrokenLinks()` query doesn't return it, and `getForwardLinks()`\n * doesn't expose `lineNumber` in its row type. Enriching here would require\n * either changing the DB layer (out of scope for this module) or a separate\n * raw query. Acceptable per spec; documented for future enhancement.\n */\nexport function findBrokenLinks(vault: Vault): BrokenLinkResult[] {\n const rows = vault.db.wikilinks.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 targetPath: row.targetPath,\n lineNumber: null,\n });\n }\n return results;\n}\n","export { listBacklinks, listForwardLinks, findBrokenLinks } from \"./graph.js\";\nexport type {\n BacklinkResult,\n ForwardLinkResult,\n BrokenLinkResult,\n} from \"./graph.js\";\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 =\n | Scalar\n | { $in: Scalar[] }\n | { $exists: boolean }\n | { $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(\n vault: Vault,\n input: QueryFrontmatterInput,\n): 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 { 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(\n (k) => JSON.stringify(k) + \":\" + canonicalJsonStringify(obj[k]),\n );\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\";\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(\n rootPath: string,\n options?: ScanOptions,\n): 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\nasync function walk(\n root: string,\n dir: string,\n matchers: RegExp[],\n out: string[],\n): 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 (\n m !== null &&\n m[1] !== undefined &&\n m[1][0] === fenceMarker\n ) {\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","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 { 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(\n absolutePath: string,\n vaultRoot: string,\n): 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(\n path.relative(path.resolve(vaultRoot), path.resolve(absolutePath)),\n );\n\n return {\n relativePath,\n content,\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","export { scanVault } from \"./scanner.js\";\nexport type { ScanOptions } from \"./scanner.js\";\nexport { parseNote } from \"./parser.js\";\nexport { extractWikilinks } from \"./wikilinks.js\";\nexport {\n sha256,\n canonicalJsonStringify,\n computeNoteHash,\n computeBodyHash,\n} from \"./hash.js\";\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 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(\n headings: HeadingRef[],\n offset: number,\n): 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 * 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(\n content: string,\n headings: HeadingRef[],\n _maxChars: number,\n): 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(\n content: string,\n span: Span,\n maxChars: number,\n): 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(\n content: string,\n span: Span,\n maxChars: number,\n): 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 * 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, parseNote } from \"../reader/index.js\";\nimport { chunkNote } from \"../chunker/index.js\";\nimport { OllamaClient } from \"../ollama/index.js\";\nimport type { Vault } from \"../vault/index.js\";\nimport type { ParsedNote, ParsedWikilink } from \"../types.js\";\nimport { WikilinkResolver } from \"./resolver.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: OllamaClient;\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(\n vault: Vault,\n options: IndexerOptions,\n): Promise<IndexRunResult> {\n const startedAt = Date.now();\n const runId = randomUUID();\n const mode = options.mode ?? \"incremental\";\n const log = options.onProgress ?? (() => {});\n\n // 1. Resolve / upsert model in DB\n log(`Probing Ollama model: ${options.embeddingModel}`);\n const health = await options.ollama.healthCheck();\n if (!health.ok) {\n throw new Error(`Ollama unreachable: ${health.error ?? \"unknown error\"}`);\n }\n const modelExists = await options.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 options.ollama.embed({\n model: options.embeddingModel,\n texts: [\"probe\"],\n });\n const dim = probe.dim;\n const 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 let secondaryModelRow: { id: number; dim: number } | null = null;\n if (options.secondaryEmbeddingModel) {\n const secName = options.secondaryEmbeddingModel;\n log(`Probing secondary (shadow) model: ${secName}`);\n const secExists = await options.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 options.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 vault.db.audit.startRun({\n runId,\n vaultName: vault.config.name,\n modelId: modelRow.id,\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 }\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 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 // 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 const noteExisted = !upsert.isNew;\n const existing = noteExisted ? vault.db.notes.getById(upsert.id) : null;\n // After upsert the DB row reflects the new state; we need to know if the hash\n // actually changed. The NotesQueries.upsertByPath returns isNew, but we also\n // need \"isModified\". Workaround: check chunks count — if a note has no chunks,\n // it needs (re-)indexing.\n const chunkCount = vault.db.chunks.getByNote(upsert.id).length;\n const needsReindex =\n mode === \"full\" || upsert.isNew || chunkCount === 0;\n\n if (upsert.isNew) notesIndexed++;\n else if (needsReindex) notesUpdated++;\n\n if (needsReindex) {\n parsedNotes.push({ parsed, noteId: upsert.id, needsReindex: true });\n }\n\n // Suppress unused-var warning for `existing` — kept for clarity above\n void existing;\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 old chunks (handles re-index)\n vault.db.chunks.deleteByNote(noteId);\n vault.db.wikilinks.deleteByNote(noteId);\n\n const chunks = chunkNote(parsed.content);\n\n if (chunks.length === 0) {\n // empty note — record wikilinks anyway, but no chunks/embeddings\n insertWikilinks(vault, noteId, parsed.wikilinks);\n continue;\n }\n\n // Insert chunks first to get IDs\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 }));\n const chunkIds = vault.db.chunks.insertBatch(noteId, chunkInputs);\n\n // Embed\n const embedResult = await options.ollama.embed({\n model: options.embeddingModel,\n texts: chunks.map((c) => c.text),\n });\n if (embedResult.dim !== dim) {\n throw new Error(\n `Embedding dimension mismatch: expected ${dim}, got ${embedResult.dim}`,\n );\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 options.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 // Wikilinks\n insertWikilinks(vault, noteId, parsed.wikilinks, 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 = err instanceof Error ? err.message : String(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}\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\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 * 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(\n absPath: string,\n content: string,\n): 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 (\n realTarget !== realRoot &&\n !realTarget.startsWith(realRootWithSep)\n ) {\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\n ? real\n : 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 * 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\nimport { promises as fs } from \"node:fs\";\nimport matter from \"gray-matter\";\nimport type { Vault } from \"../vault/index.js\";\nimport { computeNoteHash, computeBodyHash } from \"../reader/hash.js\";\nimport { extractAliases } from \"../indexer/indexer.js\";\nimport { atomicWriteFile, safeJoinInsideVault } from \"../write/fs.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public API\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface UpdateFrontmatterInput {\n vault: Vault;\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\";\n currentHash?: string;\n message: 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\nfunction computeHash(content: string, data: Record<string, unknown>): string {\n // Mirror reader/parser: empty frontmatter → canonicalJson({}). Delegates to\n // computeNoteHash so canonical (key-sorted) JSON is used everywhere.\n const fmForHash = Object.keys(data).length > 0 ? data : {};\n return computeNoteHash(content, fmForHash);\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 extractTitle(content: string, fallback: 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 fallback;\n}\n\nfunction basenameNoMd(relativePath: string): string {\n const base = relativePath.split(\"/\").pop() ?? relativePath;\n return base.endsWith(\".md\") ? base.slice(0, -3) : base;\n}\n\nexport async function updateFrontmatter(\n input: UpdateFrontmatterInput,\n): Promise<UpdateResult> {\n const { vault, relativePath, merge, expectedHash, clientId } = input;\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 const absPath = await safeJoinInsideVault(vault.config.path, relativePath);\n\n let raw: string;\n try {\n raw = await fs.readFile(absPath, \"utf8\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n ok: false,\n reason: \"note_not_found\",\n message: `Failed to read file: ${msg}`,\n };\n }\n\n const parsed = matter(raw);\n const content = parsed.content;\n const data = (parsed.data ?? {}) as Record<string, unknown>;\n\n const currentHash = computeHash(content, data);\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(data, 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 // Build the new file. gray-matter.stringify writes frontmatter even when\n // empty — we explicitly handle the all-deleted case by emitting just body.\n const fullText =\n Object.keys(next).length === 0 ? content : matter.stringify(content, next);\n\n input.onBeforeFsWrite?.();\n await atomicWriteFile(absPath, fullText);\n\n const stat = await fs.stat(absPath);\n const newHash = computeHash(content, next);\n const title = extractTitle(content, basenameNoMd(relativePath));\n const wordCount = countWords(content);\n const fmJson = Object.keys(next).length > 0 ? JSON.stringify(next) : null;\n const aliasKeyTouched = \"aliases\" in merge || \"alias\" in merge;\n\n // Codex MEDIUM-1: atomic DB updates + FS rollback on failure. The note\n // already exists on disk (we read `raw` above), so rollback restores it.\n let upsertId: number;\n try {\n upsertId = vault.db.transaction(() => {\n const up = vault.db.notes.upsertByPath({\n path: relativePath,\n content,\n frontmatter: fmJson,\n title,\n hash: newHash,\n bodyHash: computeBodyHash(content),\n mtime: Math.floor(stat.mtimeMs),\n wordCount,\n });\n if (aliasKeyTouched) {\n vault.db.aliases.setForNote(up.id, extractAliases(next));\n }\n vault.db.audit.recordWrite({\n noteId: up.id,\n op: \"update\",\n previousHash: currentHash,\n newHash,\n expectedHash: expectedHash ?? null,\n clientId: clientId ?? null,\n diffSummary: JSON.stringify(diff),\n });\n return up.id;\n });\n } catch (dbErr) {\n input.onBeforeFsWrite?.();\n try {\n await atomicWriteFile(absPath, raw);\n } catch {\n // Rollback failed — re-throw original; catch-up will reconcile.\n }\n throw dbErr;\n }\n\n return {\n ok: true,\n newHash,\n noteId: upsertId,\n diff,\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 * 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(\n vault: Vault,\n folder: string,\n excludePath: string | null,\n): 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<[string | null], { c: number }>(\n \"SELECT COUNT(*) AS c FROM notes WHERE instr(path, '/') = 0 AND path != COALESCE(?, '')\",\n )\n .get(excludePath);\n return row?.c ?? 0;\n }\n const row = handle\n .prepare<[string, string | null], { c: number }>(\n \"SELECT COUNT(*) AS c FROM notes WHERE path LIKE ? || '%' AND path != COALESCE(?, '')\",\n )\n .get(folder, excludePath);\n return row?.c ?? 0;\n}\n\nfunction fetchSiblings(\n vault: Vault,\n folder: string,\n excludePath: string | null,\n): SiblingRow[] {\n const handle = vault.db.handle;\n if (folder === \"\") {\n return handle\n .prepare<[string | null], SiblingRow>(\n \"SELECT path, frontmatter FROM notes WHERE instr(path, '/') = 0 AND path != COALESCE(?, '')\",\n )\n .all(excludePath);\n }\n return handle\n .prepare<[string, string | null], SiblingRow>(\n \"SELECT path, frontmatter FROM notes WHERE path LIKE ? || '%' AND path != COALESCE(?, '')\",\n )\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(\n siblings: SiblingRow[],\n): 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 =\n 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`)\n ?? 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(\n neighbors: NeighborRow[],\n): 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 =\n 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(\n vault,\n notePath,\n additionalForwardTargets,\n );\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) ||\n /^(Re|Fwd|AW|WG):\\s/i.test(title);\n const headerMatch =\n /^(From|Von):\\s+\\S+/im.test(bodyHead) &&\n /^(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 =\n /^(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 =\n /^[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 [\n { key: \"class\", value: \"Fact\", confidence: WEAK_CONFIDENCE },\n ];\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 [\n { key: \"created\", value: iso, confidence: STRONG_CONFIDENCE },\n ];\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: {\n title: string;\n body: string;\n}): 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 {\n inferFromFolder,\n type FolderConventionResult,\n} from \"./folder-conventions.js\";\nimport {\n inferFromNeighbors,\n type NeighborInferenceResult,\n} from \"./neighbor-inference.js\";\nimport {\n inferFromContent,\n type ContentHeuristicResult,\n} 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(\n input: SuggestFrontmatterInput,\n): 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(\n input.vault,\n input.path,\n input.draftWikilinkTargets ?? [],\n );\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>([\n ...candidates.keys(),\n ...existingKeys,\n ]);\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(\n (k) => k !== \"__keyonly__\",\n ).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(\n ([k]) => k !== \"__keyonly__\",\n );\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 {\n FolderConventionEntry,\n FolderConventionResult,\n} from \"./folder-conventions.js\";\n\nexport { inferFromNeighbors } from \"./neighbor-inference.js\";\nexport type {\n NeighborInferenceEntry,\n NeighborInferenceResult,\n} from \"./neighbor-inference.js\";\n\nexport { inferFromContent } from \"./content-heuristics.js\";\nexport type {\n ContentHeuristicEntry,\n ContentHeuristicResult,\n} 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 * 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 \"../reader/index.js\";\nimport { chunkNote } from \"../chunker/index.js\";\nimport {\n extractAliases,\n resolveWikilinkTarget,\n} from \"./indexer.js\";\nimport type { 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 ollama: OllamaClient;\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(\n options: IndexNoteOptions,\n): 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(\n existing.id,\n extractAliases(parsed.frontmatter),\n );\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\n ? JSON.stringify(parsed.frontmatter)\n : 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(\n upsert.id,\n extractAliases(parsed.frontmatter),\n );\n vault.db.wikilinks.deleteByNote(upsert.id);\n insertWikilinks(vault, upsert.id, parsed.wikilinks);\n return {\n status: \"indexed\",\n notePath: parsed.relativePath,\n noteId: upsert.id,\n chunksCreated: 0,\n isNew: false,\n };\n }\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 activeModel = vault.db.models.getActive();\n if (!activeModel) {\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 (activeModel.name !== embeddingModel) {\n throw new Error(\n `single-indexer: active model \"${activeModel.name}\" does not match ` +\n `requested \"${embeddingModel}\". Run a full re-index to switch models.`,\n );\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\n ? JSON.stringify(parsed.frontmatter)\n : 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(\n upsert.id,\n extractAliases(parsed.frontmatter),\n );\n\n // 7. Wipe derived layer for this note.\n vault.db.chunks.deleteByNote(upsert.id);\n vault.db.wikilinks.deleteByNote(upsert.id);\n\n // 8. Chunk + embed + persist.\n const chunks = chunkNote(parsed.content);\n\n if (chunks.length === 0) {\n insertWikilinks(vault, upsert.id, parsed.wikilinks);\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 })),\n );\n\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 insertWikilinks(vault, upsert.id, parsed.wikilinks);\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(\n status: \"outside_vault\" | \"missing\" | \"parse_error\",\n): 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 */\nfunction insertWikilinks(\n vault: Vault,\n sourceNoteId: number,\n wikilinks: ParsedWikilink[],\n): void {\n if (wikilinks.length === 0) return;\n\n const inputs = wikilinks.map((wl) => {\n const target = resolveWikilinkTarget(vault, 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 * 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, parseNote } from \"../reader/index.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 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(\n options: CatchupOptions,\n): 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\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 ollama: options.ollama,\n });\n if (result.status === \"indexed\") {\n reindexed++;\n log(\n `catch-up indexed ${parsed.relativePath} (${result.isNew ? \"new\" : \"updated\"})`,\n );\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 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\";\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(\n options: ShadowIndexOptions,\n): 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(\n `Shadow model \"${model}\" not found in Ollama. ` +\n `Run: ollama pull ${model}`,\n );\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\n .prepare<[], PendingChunkRow>(pendingSql)\n .all();\n const totalRow = vault.db.handle\n .prepare<[], { c: number }>(totalSql)\n .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 = err instanceof Error ? err.message : String(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 }>(\n `SELECT COUNT(*) AS c FROM embeddings_m${m.id}_d${m.dim}`,\n )\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(\n vault: Vault,\n targetModelName: string,\n): 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(\n `DELETE FROM ${table} WHERE chunk_id = ?`,\n );\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 {\n startShadowIndex,\n listModels,\n switchActiveModel,\n} 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 * 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 \"../reader/index.js\";\nimport { extractAliases } from \"../indexer/index.js\";\nimport { atomicWriteFile, safeJoinInsideVault } from \"./fs.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\";\n currentHash?: string;\n currentContent?: string;\n message: 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 /** For audit_log entry. Defaults to \"claude-code\". */\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\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}\n\nconst DEFAULT_CLIENT_ID = \"claude-code\";\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(\n content: string,\n frontmatter: Record<string, unknown> | null,\n): 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(\n absPath: string,\n): Promise<{ raw: string; content: string; frontmatter: Record<string, unknown> | null; hash: string } | 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 } = input;\n const frontmatter = input.frontmatter ?? null;\n const clientId = input.clientId ?? DEFAULT_CLIENT_ID;\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 const fileText =\n frontmatter !== null && Object.keys(frontmatter).length > 0\n ? matter.stringify(content, frontmatter)\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(\n `Internal error: file disappeared after write: ${relativePath}`,\n );\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 });\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 } = input;\n const clientId = input.clientId ?? DEFAULT_CLIENT_ID;\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 });\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","export { 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 * 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\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\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\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 };\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 =\n 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 {\n AuditLogEntry,\n IndexRunEntry,\n GetAuditLogInput,\n GetIndexRunsInput,\n} from \"./audit.js\";\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(\n `[DebouncedQueue] onFlush failed for ${event.path} (${event.kind}):`,\n err,\n );\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 * 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 { posix } from \"node:path\";\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\";\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\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 = err instanceof Error ? err.message : String(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, {\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: 200,\n pollInterval: 50,\n },\n followSymlinks: false,\n });\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 = err instanceof Error ? err.message : String(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.fsWatcher) {\n await this.fsWatcher.close();\n this.fsWatcher = null;\n }\n }\n\n // ─── internal ──────────────────────────────────────────────────────────\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 if (event.kind === \"delete\") {\n const result = removeNote(this.opts.vault, event.path);\n if (result.removed) {\n this.opts.log(`removed ${relativePath}`);\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 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 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\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\ninterface Entry {\n expiresAt: number;\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 /** Mark a path as \"expect a filesystem event for this — please ignore it\". */\n add(path: string, ttlMs?: number): void {\n this.prune();\n const ttl = ttlMs ?? this.defaultTtlMs;\n this.entries.set(path, { expiresAt: this.now() + ttl });\n }\n\n /**\n * If path is suppressed, remove the entry and return true (skip event).\n * Otherwise return false.\n */\n consume(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 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","export { 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\";\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 { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type BetterSqlite3 from \"better-sqlite3\";\nimport { z } from \"zod\";\nimport { loadConfig } from \"./config/index.js\";\nimport { VaultManager } from \"./vault/index.js\";\nimport { OllamaClient } from \"./ollama/index.js\";\nimport { FtsQueries } from \"./db/index.js\";\nimport { hybridSearch, matchesAnyGlob } from \"./search/index.js\";\nimport { OllamaReranker, OnnxReranker } from \"./rerank/index.js\";\nimport type { Reranker } from \"./rerank/index.js\";\nimport { homedir } from \"node:os\";\nimport { join as joinPath } from \"node:path\";\nimport {\n listBacklinks,\n listForwardLinks,\n findBrokenLinks,\n} from \"./graph/index.js\";\nimport { queryFrontmatter, updateFrontmatter } from \"./frontmatter/index.js\";\nimport { suggestFrontmatter } from \"./schema/index.js\";\nimport { writeNote, deleteNote } from \"./write/index.js\";\nimport { getAuditLog, getIndexRuns } from \"./audit/index.js\";\nimport { SuppressionSet, VaultWatcher } from \"./watcher/index.js\";\nimport {\n catchupVault,\n listModels,\n startShadowIndex,\n switchActiveModel,\n vacuumEmbeddings,\n} from \"./indexer/index.js\";\nimport type { SearchHit } from \"./types.js\";\n\nconst VERSION = \"0.10.0\";\n\n// ─── Tool Input Schemas ──────────────────────────────────────────────────────\n\nconst ReadNoteArgs = z.object({\n vault: z.string(),\n path: z.string(),\n});\n\nconst SearchArgs = z.object({\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 /** Glob patterns of vault-relative paths to exclude from results. Useful\n * for filtering self-referential notes (e.g. an eval note that lists\n * the same keywords it's testing) or auxiliary indices. */\n exclude_paths: z.array(z.string()).optional(),\n});\n\nconst HybridSearchArgs = SearchArgs.extend({\n rrf_k: z.number().int().positive().max(1000).optional().default(60),\n /** When true AND a `reranker_model` is configured, runs a cross-encoder\n * rerank pass over the top candidates. Silently ignored otherwise. */\n rerank: z.boolean().optional().default(false),\n});\n\nconst VaultPathArgs = z.object({\n vault: z.string(),\n path: z.string(),\n});\n\nconst ForwardLinksArgs = VaultPathArgs.extend({\n include_broken: z.boolean().optional().default(true),\n});\n\nconst FindBrokenLinksArgs = z.object({\n vault: z.string(),\n});\n\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\nconst QueryFrontmatterArgs = z.object({\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// ─── Phase 3 input schemas ──────────────────────────────────────────────────\n\nconst WriteNoteArgs = z.object({\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\nconst UpdateFrontmatterArgs = z.object({\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\nconst DeleteNoteArgs = z.object({\n vault: z.string(),\n path: z.string(),\n expected_hash: z.string(),\n client_id: z.string().optional(),\n});\n\nconst AuditLogArgs = z.object({\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});\n\nconst IndexRunsArgs = z.object({\n vault: z.string(),\n limit: z.number().int().positive().max(200).optional().default(20),\n});\n\n// ─── Phase 7c — shadow-indexing / model switch ──────────────────────────────\n\nconst ListModelsArgs = z.object({\n vault: z.string(),\n});\n\nconst StartShadowIndexArgs = z.object({\n vault: z.string(),\n model: z.string().min(1),\n batch_size: z.number().int().positive().max(256).optional(),\n});\n\nconst SwitchActiveModelArgs = z.object({\n vault: z.string(),\n model_name: z.string().min(1),\n});\n\nconst VacuumEmbeddingsArgs = z.object({\n vault: z.string(),\n});\n\n// ─── v0.9.0 — Agent-Compatibility & Self-Orientation ────────────────────────\n//\n// `search` / `fetch` follow the OB1 / ChatGPT-Connector / Claude.ai\n// Deep-Research tool spec: a flat shape with opaque `id`s. The `id` here\n// encodes `<vault>:<notePath>` so `fetch(id)` can deterministically resolve\n// back to the underlying note without round-tripping a separate vault arg.\n\nconst SearchCompatArgs = z.object({\n query: z.string().min(1),\n limit: z.number().int().positive().max(50).optional().default(10),\n});\n\nconst FetchCompatArgs = z.object({\n id: z.string().min(1),\n});\n\nconst VaultStatsArgs = z.object({\n vault: z.string().optional(),\n});\n\nconst RecentNotesArgs = z.object({\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// ─── v0.10.0 — suggest_frontmatter ───────────────────────────────────────────\n//\n// Two input modes:\n// 1. Existing note: {vault, path} — tool reads the note from DB, runs\n// folder + neighbor + (optional) content inference.\n// 2. Draft note: {vault, content, folder_hint?} — tool uses folder_hint\n// (or extracts from title) for folder-inference and the parsed\n// content for heuristics. Useful for /import-person, /log-fact-style\n// flows where the note isn't written yet.\n//\n// At least one of `path` or `content` must be present.\n\nconst SuggestFrontmatterArgs = z\n .object({\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 .refine((v) => v.path !== undefined || v.content !== undefined, {\n message: \"suggest_frontmatter requires either `path` or `content`\",\n });\n\n// ─── Server bootstrap ────────────────────────────────────────────────────────\n\nexport async function serve(): Promise<void> {\n const config = await loadConfig();\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 defaultModel =\n 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 ??\n (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 // One SuppressionSet shared by all vaults (paths are vault-relative; the\n // chance of a collision across vaults is negligible and a false positive\n // just means one event is dropped — harmless).\n const suppression = new SuppressionSet({ ttlMs: 2000 });\n const watchers = new Map<string, VaultWatcher>();\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 if (!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 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 = err instanceof Error ? err.message : String(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 };\n\n const shutdown = async (): Promise<void> => {\n for (const w of watchers.values()) {\n await w.drain();\n await w.stop();\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 const server = new Server(\n { name: \"vault-memory\", version: VERSION },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: \"list_vaults\",\n description:\n \"List configured vaults with their status (note count, last indexed run).\",\n inputSchema: { type: \"object\", properties: {} },\n },\n {\n name: \"read_note\",\n description:\n \"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:\n \"Vault-relative path with forward slashes, ending in .md\",\n },\n },\n },\n },\n {\n name: \"search_semantic\",\n description:\n \"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:\n \"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:\n \"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.\",\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:\n \"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 },\n },\n },\n {\n name: \"list_backlinks\",\n description: \"Find all notes that link TO a given note.\",\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:\n \"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:\n \"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:\n \"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 },\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 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:\n \"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 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.\",\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 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 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 ],\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n switch (name) {\n case \"list_vaults\":\n return ok(handleListVaults(manager));\n\n case \"read_note\": {\n const parsed = ReadNoteArgs.parse(args ?? {});\n return ok(handleReadNote(manager, parsed.vault, parsed.path));\n }\n\n case \"search_semantic\": {\n const parsed = SearchArgs.parse(args ?? {});\n return ok(\n await handleSearchSemantic(\n manager,\n ollama,\n defaultModel,\n activeVault,\n parsed.query,\n parsed.vaults,\n parsed.top_k,\n parsed.exclude_paths,\n ),\n );\n }\n\n case \"search_text\": {\n const parsed = SearchArgs.parse(args ?? {});\n return ok(\n handleSearchText(\n manager,\n activeVault,\n parsed.query,\n parsed.vaults,\n parsed.top_k,\n parsed.exclude_paths,\n ),\n );\n }\n\n case \"search_hybrid\": {\n const parsed = HybridSearchArgs.parse(args ?? {});\n return ok(\n await handleSearchHybrid(\n manager,\n ollama,\n defaultModel,\n activeVault,\n parsed.query,\n parsed.vaults,\n parsed.top_k,\n parsed.rrf_k,\n parsed.exclude_paths,\n parsed.rerank ? reranker : undefined,\n ),\n );\n }\n\n case \"list_backlinks\": {\n const parsed = VaultPathArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n return ok({ backlinks: listBacklinks(vault, parsed.path) });\n }\n\n case \"list_forward_links\": {\n const parsed = ForwardLinksArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n return ok({\n links: listForwardLinks(vault, parsed.path, parsed.include_broken),\n });\n }\n\n case \"find_broken_links\": {\n const parsed = FindBrokenLinksArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n return ok({ broken: findBrokenLinks(vault) });\n }\n\n case \"query_frontmatter\": {\n const parsed = QueryFrontmatterArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const hits = queryFrontmatter(vault, {\n where: parsed.where as Record<string, never>,\n limit: parsed.limit,\n });\n return ok({\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\n case \"write_note\": {\n const parsed = WriteNoteArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n // Suppress the watcher event triggered by our own atomic rename.\n // The hook fires inside writeNote() ONLY if the write actually\n // happens (no permission/hash conflict), so a failed write never\n // accidentally masks a real external edit shortly after.\n const result = await writeNote({\n vault,\n relativePath: parsed.path,\n content: parsed.content,\n frontmatter: parsed.frontmatter ?? null,\n expectedHash: parsed.expected_hash,\n clientId: parsed.client_id,\n onBeforeFsWrite: () => suppression.add(parsed.path),\n });\n return ok(result);\n }\n\n case \"update_frontmatter\": {\n const parsed = UpdateFrontmatterArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const result = await updateFrontmatter({\n vault,\n relativePath: parsed.path,\n merge: parsed.merge,\n expectedHash: parsed.expected_hash,\n clientId: parsed.client_id,\n onBeforeFsWrite: () => suppression.add(parsed.path),\n });\n return ok(result);\n }\n\n case \"delete_note\": {\n const parsed = DeleteNoteArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const result = await deleteNote({\n vault,\n relativePath: parsed.path,\n expectedHash: parsed.expected_hash,\n clientId: parsed.client_id,\n onBeforeFsWrite: () => suppression.add(parsed.path),\n });\n return ok(result);\n }\n\n case \"audit_log\": {\n const parsed = AuditLogArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const entries = getAuditLog({\n vault,\n notePath: parsed.note_path,\n op: parsed.op,\n since: parsed.since,\n limit: parsed.limit,\n });\n return ok({ entries, count: entries.length });\n }\n\n case \"list_models\": {\n const parsed = ListModelsArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const models = listModels(vault);\n return ok({ models, count: models.length });\n }\n\n case \"start_shadow_index\": {\n const parsed = StartShadowIndexArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const result = await startShadowIndex({\n vault,\n model: parsed.model,\n ollama,\n batchSize: parsed.batch_size,\n log: (m) =>\n process.stderr.write(`[shadow:${vault.config.name}] ${m}\\n`),\n });\n return ok(result);\n }\n\n case \"switch_active_model\": {\n const parsed = SwitchActiveModelArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const result = switchActiveModel(vault, parsed.model_name);\n return ok(result);\n }\n\n case \"vacuum_embeddings\": {\n const parsed = VacuumEmbeddingsArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const result = vacuumEmbeddings(vault);\n return ok(result);\n }\n\n case \"index_runs\": {\n const parsed = IndexRunsArgs.parse(args ?? {});\n const vault = manager.require(parsed.vault);\n const runs = getIndexRuns({ vault, limit: parsed.limit });\n return ok({ runs, count: runs.length });\n }\n\n case \"search\": {\n const parsed = SearchCompatArgs.parse(args ?? {});\n return ok(\n await handleSearchCompat(\n manager,\n ollama,\n defaultModel,\n activeVault,\n parsed.query,\n parsed.limit,\n reranker,\n ),\n );\n }\n\n case \"fetch\": {\n const parsed = FetchCompatArgs.parse(args ?? {});\n return ok(handleFetchCompat(manager, parsed.id));\n }\n\n case \"vault_stats\": {\n const parsed = VaultStatsArgs.parse(args ?? {});\n return ok(handleVaultStats(manager, parsed.vault));\n }\n\n case \"recent_notes\": {\n const parsed = RecentNotesArgs.parse(args ?? {});\n return ok(\n handleRecentNotes(\n manager,\n parsed.vault,\n parsed.limit,\n parsed.since,\n ),\n );\n }\n\n case \"suggest_frontmatter\": {\n const parsed = SuggestFrontmatterArgs.parse(args ?? {});\n return ok(handleSuggestFrontmatter(manager, parsed));\n }\n\n default:\n return errorResponse(`Unknown tool: ${name}`);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return errorResponse(message);\n }\n });\n\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 startCatchupAndWatchers().catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[catchup] unexpected failure: ${message}\\n`);\n });\n}\n\n// ─── Tool handlers ───────────────────────────────────────────────────────────\n\nfunction 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\nfunction handleReadNote(\n manager: VaultManager,\n vaultName: string,\n path: string,\n): object {\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 return {\n path: note.path,\n title: note.title,\n content: note.content,\n frontmatter: note.frontmatter ? JSON.parse(note.frontmatter) : null,\n hash: note.hash,\n mtime: note.mtime,\n word_count: note.word_count,\n };\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 */\nfunction 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\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(\n model.id,\n queryVec,\n fanK,\n );\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\n for (const vault of targets) {\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 if (skipped.length > 0) {\n out.note = `Skipped vault(s) currently indexing: ${skipped.join(\", \")}.`;\n }\n return out;\n}\n\nasync 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): 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 const hits = await hybridSearch({\n query,\n embeddingModel: defaultModel,\n ollama,\n vaults: targets,\n topK: innerTopK,\n rrfK,\n includeBreakdown: true,\n reranker,\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 */\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(\n `Invalid id: ${id}. Expected format <vault>:<vault-relative-path>.`,\n );\n }\n return { vault: id.slice(0, idx), path: id.slice(idx + 1) };\n}\n\n/**\n * Build an `obsidian://` URL pointing at a note in the vault. Mirrors the\n * pattern documented in our Linear-backlink convention (memory:\n * feedback_linear_obsidian_backlinks): clickable from any connector UI,\n * opens the actual note locally.\n */\nexport function obsidianUrl(vaultName: string, notePath: string): string {\n return `obsidian://open?vault=${encodeURIComponent(vaultName)}&file=${encodeURIComponent(notePath)}`;\n}\n\nasync function handleSearchCompat(\n manager: VaultManager,\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.\n const hits = await hybridSearch({\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: obsidianUrl(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\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\nfunction handleFetchCompat(manager: VaultManager, 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: obsidianUrl(vaultName, note.path),\n metadata,\n };\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\nfunction handleVaultStats(\n manager: VaultManager,\n vaultFilter: string | undefined,\n): object {\n const targets = vaultFilter\n ? [manager.require(vaultFilter)]\n : 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 }>(\n \"SELECT SUM(word_count) AS total FROM notes\",\n )\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\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\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\nfunction handleRecentNotes(\n manager: VaultManager,\n vaultFilter: string | undefined,\n limit: number,\n since: number | undefined,\n): object {\n const targets = vaultFilter\n ? [manager.require(vaultFilter)]\n : manager.list();\n\n const all: RecentNoteRow[] = [];\n for (const v of targets) {\n const rows = since !== undefined\n ? v.db.handle\n .prepare<\n [number, number],\n { path: string; title: string | null; mtime: number; word_count: number | null; frontmatter: string | null }\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 { path: string; title: string | null; mtime: number; word_count: number | null; frontmatter: string | null }\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\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:\n \"Draft mode: no backlinks contributed. Provide `path` (and index the note first) for richer neighbor-inference.\",\n ...result,\n };\n}\n\nfunction 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\nfunction defaultBasename(path: string): string {\n const base = path.split(\"/\").pop() ?? path;\n return base.replace(/\\.md$/i, \"\");\n}\n\nfunction 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// ─── Response helpers ────────────────────────────────────────────────────────\n\nfunction ok(data: object): { content: Array<{ type: \"text\"; text: string }> } {\n return {\n content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }],\n };\n}\n\nfunction 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 * 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\n ? [manager.require(vaultName)]\n : manager.list();\n\n for (const vault of targets) {\n const model =\n vault.config.embedding_model ??\n config.server.default_embedding_model ??\n \"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 =\n 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 Claude Code can\n * 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\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 === \"--no-index\") {\n skipIndex = true;\n } else if (arg === \"--help\" || arg === \"-h\") {\n console.error(`Usage: vault-memory add-vault <path> [--name <name>] [--write] [--no-index]\n\nRegisters a vault in ~/.vault-memory/config.toml, writes a .mcp.json\ninto the vault root, and runs an initial index. Idempotent.`);\n return;\n } else if (arg && !arg.startsWith(\"--\") && path === null) {\n path = arg;\n }\n }\n\n if (path === null) {\n console.error(\n \"Usage: vault-memory add-vault <path> [--name <name>] [--write] [--no-index]\",\n );\n process.exit(2);\n }\n\n console.error(`→ Registering vault: ${path}`);\n const result = await addVault({ path, name, writeEnabled });\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 Claude Code — 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","rows","path","rows","homedir","join","resolve","z","path","readFile","join","init_graph","fs","path","init_wikilinks","fs","path","toPosix","init_wikilinks","init_wikilinks","init_chunker","init_chunker","fs","resolve","sep","fs","matter","isPlainObject","countWords","extractTitle","row","aggregateEntries","safeParse","path","args","init_schema","path","upsert","insertWikilinks","init_chunker","relative","randomUUID","init_indexer","fs","basename","matter","computeHash","extractTitle","countWords","init_indexer","init_write","init_audit","init_audit","path","init_indexer","path","resolve","result","path","init_watcher","z","homedir","args","path","result","init_graph","init_schema","init_write","init_audit","init_watcher","init_indexer","loadConfig","VaultManager","OllamaClient","indexVault","addVault","path"]}
|