crosscheck-mcp 0.2.11 → 0.2.13

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../node_modules/tsup/assets/cjs_shims.js","../src/adapters/storage/migrations/0001_init.ts","../src/adapters/storage/migrations/index.ts","../src/adapters/storage/schema.ts","../src/adapters/storage/better-sqlite3.ts","../src/self-check.ts"],"sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","// 0001_init — initial schema. Mirrors the Python server's tables and\n// indexes byte-for-byte after canonicalization (`scripts/canonicalize_schema.py`\n// + `src/adapters/storage/schema.ts` produce identical strings).\n//\n// Every column declaration here MUST match\n// `servers/python/crosscheck_server.py:_db_init` exactly. The\n// Phase-1 schema-parity test enforces this; any drift fails CI before\n// any tool can be ported on top of a divergent storage.\n//\n// Tables (in dependency order):\n// sessions\n// usage_log (indexes: idx_usage_session, idx_usage_provider)\n// claims (indexes: idx_claims_session) — FK to sessions\n// claim_links (indexes: idx_links_src, idx_links_dst) — FK to claims\n// provider_stats\n// delegations (indexes: idx_deleg_session, idx_deleg_req)\n// session_memory (indexes: idx_session_memory_session, idx_session_memory_kind)\n// fetch_egress (composite PK on session_id + host)\n// transcripts_fts (FTS5 virtual table)\n//\n// The session usage-totals columns that the Python server adds via\n// `_add_session_usage_columns` (PRAGMA-driven idempotent ALTER) are\n// baked directly into the CREATE TABLE here; the runner reads PRAGMA\n// table_info() to assert parity.\n\nimport type { Migration } from \"./types.js\";\n\nexport const m0001_init: Migration = {\n id: \"0001_init\",\n name: \"initial schema (sessions, usage_log, claims, ...)\",\n up: [\n // sessions — includes the totals columns that Python adds via ALTER.\n `CREATE TABLE IF NOT EXISTS sessions (\n session_id TEXT PRIMARY KEY,\n started_at INTEGER NOT NULL,\n last_at INTEGER,\n calls INTEGER NOT NULL DEFAULT 0,\n wall_ms INTEGER NOT NULL DEFAULT 0,\n cache_hits INTEGER NOT NULL DEFAULT 0,\n total_prompt_tokens INTEGER NOT NULL DEFAULT 0,\n total_completion_tokens INTEGER NOT NULL DEFAULT 0,\n total_cached_tokens INTEGER NOT NULL DEFAULT 0,\n total_tokens INTEGER NOT NULL DEFAULT 0,\n total_cost_usd REAL NOT NULL DEFAULT 0.0,\n total_cpu_ms INTEGER NOT NULL DEFAULT 0\n )`,\n\n // usage_log — per-call ledger.\n `CREATE TABLE IF NOT EXISTS usage_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n ts INTEGER NOT NULL,\n tool TEXT,\n purpose TEXT NOT NULL,\n provider TEXT NOT NULL,\n model TEXT NOT NULL,\n prompt_tokens INTEGER NOT NULL DEFAULT 0,\n completion_tokens INTEGER NOT NULL DEFAULT 0,\n cached_tokens INTEGER NOT NULL DEFAULT 0,\n total_tokens INTEGER NOT NULL DEFAULT 0,\n cost_usd REAL NOT NULL DEFAULT 0.0,\n estimated INTEGER NOT NULL DEFAULT 0,\n wall_ms INTEGER NOT NULL DEFAULT 0,\n cpu_ms INTEGER NOT NULL DEFAULT 0\n )`,\n `CREATE INDEX IF NOT EXISTS idx_usage_session ON usage_log(session_id)`,\n `CREATE INDEX IF NOT EXISTS idx_usage_provider ON usage_log(provider)`,\n\n // claims (FK to sessions, ON DELETE CASCADE).\n `CREATE TABLE IF NOT EXISTS claims (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL REFERENCES sessions(session_id) ON DELETE CASCADE,\n text TEXT NOT NULL,\n provider TEXT,\n confidence REAL,\n citations_json TEXT,\n kind TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS idx_claims_session ON claims(session_id)`,\n\n // claim_links (FK to claims). Kind enum widened in Python's\n // _migrate_claim_links_check; mirrored here as the CHECK clause.\n `CREATE TABLE IF NOT EXISTS claim_links (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n src_id INTEGER NOT NULL REFERENCES claims(id) ON DELETE CASCADE,\n dst_id INTEGER NOT NULL REFERENCES claims(id) ON DELETE CASCADE,\n kind TEXT NOT NULL CHECK (kind IN ('supports','attacks','derives_from','merges_with')),\n created_at INTEGER NOT NULL,\n UNIQUE(src_id, dst_id, kind)\n )`,\n `CREATE INDEX IF NOT EXISTS idx_links_src ON claim_links(src_id)`,\n `CREATE INDEX IF NOT EXISTS idx_links_dst ON claim_links(dst_id)`,\n\n // provider_stats — ballots accumulator for the smart router.\n `CREATE TABLE IF NOT EXISTS provider_stats (\n provider TEXT PRIMARY KEY,\n wins INTEGER NOT NULL DEFAULT 0,\n losses INTEGER NOT NULL DEFAULT 0,\n abstains INTEGER NOT NULL DEFAULT 0,\n last_at INTEGER\n )`,\n\n // delegations — cross-model handshake ledger.\n `CREATE TABLE IF NOT EXISTS delegations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT,\n requester TEXT,\n tool_call TEXT NOT NULL,\n via TEXT NOT NULL,\n accepted INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS idx_deleg_session ON delegations(session_id)`,\n `CREATE INDEX IF NOT EXISTS idx_deleg_req ON delegations(requester)`,\n\n // session_memory — facts / open_questions / decisions ledger.\n `CREATE TABLE IF NOT EXISTS session_memory (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('fact','open_question','decision')),\n content TEXT NOT NULL,\n source_tool TEXT,\n source_call_id TEXT,\n confidence REAL,\n created_at INTEGER NOT NULL,\n stale_at INTEGER,\n stale_reason TEXT\n )`,\n `CREATE INDEX IF NOT EXISTS idx_session_memory_session ON session_memory(session_id)`,\n `CREATE INDEX IF NOT EXISTS idx_session_memory_kind ON session_memory(kind)`,\n\n // fetch_egress — per-session per-host byte ledger.\n `CREATE TABLE IF NOT EXISTS fetch_egress (\n session_id TEXT NOT NULL,\n host TEXT NOT NULL,\n total_bytes INTEGER NOT NULL DEFAULT 0,\n last_at INTEGER NOT NULL,\n PRIMARY KEY (session_id, host)\n )`,\n\n // transcripts_fts — FTS5 virtual table for the recall tool. Python uses\n // `tokenize='unicode61 remove_diacritics 2'`; mirrored exactly.\n `CREATE VIRTUAL TABLE IF NOT EXISTS transcripts_fts USING fts5(\n session_id, tool, ts UNINDEXED, path UNINDEXED, content,\n tokenize='unicode61 remove_diacritics 2'\n )`,\n ],\n};\n","// Migrations registry. Adapters call `MIGRATIONS` to get the ordered\n// list to apply at init time. Adding a new migration = appending it\n// here in id order.\n\nimport { m0001_init } from \"./0001_init.js\";\nimport type { Migration } from \"./types.js\";\n\nexport const MIGRATIONS: readonly Migration[] = [m0001_init];\nexport type { Migration };\n","// Canonical schema string — derived from PRAGMA reads, NOT raw\n// sqlite_master text. Two SQLite databases with the same logical\n// schema produce the same canonical string regardless of how the\n// CREATE statements were originally written (whitespace, IF NOT\n// EXISTS, declaration order, etc.).\n//\n// Must produce byte-identical output with `scripts/canonicalize_schema.py`.\n// Update both files in lockstep.\n//\n// Output shape (multi-line):\n// table:<name>\n// col:<cid>:<name>:<type>:<notnull>:<dflt>:<pk>\n// ...\n// idx:<name>:<unique>:<origin>:<partial>\n// <indexed-col-list>\n// fk:<id>:<seq>:<to_table>:<from_col>:<to_col>:<on_update>:<on_delete>:<match>\n// table:<next>\n// ...\n// fts:<name>:<tokenize-spec>\n//\n// Tables are sorted by name. Within a table, columns are sorted by\n// PRAGMA cid (== declaration order, matching Python). Indexes within\n// a table are sorted by name; foreign keys by (id, seq).\n\n// Generic row shape — accepts better-sqlite3's mixed-type rows. We narrow\n// at the leaves where needed.\ntype Row = Record<string, unknown>;\n\n/** A reader interface so this module isn't coupled to better-sqlite3 —\n * the wa-sqlite adapter will pass a similar reader. */\nexport interface SchemaReader {\n /** Run a `PRAGMA <name>(<arg?>)` and return rows. */\n pragma(name: string, arg?: string): readonly Row[];\n /** Run any SELECT used to enumerate names. */\n list(sql: string): readonly Row[];\n}\n\nexport function canonicalSchema(reader: SchemaReader): string {\n const tables = listTables(reader);\n const lines: string[] = [];\n\n for (const table of tables) {\n if (isFtsShadowTable(table)) continue; // skip FTS5 shadow content tables\n if (isInternal(table)) continue; // sqlite_*\n if (table === \"schema_migrations\") continue; // TS-side migration tracking metadata\n\n if (isFtsVirtualTable(reader, table)) {\n lines.push(`fts:${table}:${ftsTokenize(reader, table)}`);\n continue;\n }\n\n lines.push(`table:${table}`);\n for (const c of tableInfo(reader, table)) {\n lines.push(\n ` col:${c.cid}:${c.name}:${c.type}:${c.notnull}:${formatDefault(c.dflt_value)}:${c.pk}`,\n );\n }\n for (const idx of listIndexes(reader, table)) {\n lines.push(\n ` idx:${idx.name}:${idx.unique}:${idx.origin}:${idx.partial}`,\n );\n const cols = indexInfo(reader, idx.name);\n lines.push(` cols:${cols.map((c) => c.name).join(\",\")}`);\n }\n for (const fk of listFks(reader, table)) {\n lines.push(\n ` fk:${fk.id}:${fk.seq}:${fk.table}:${fk.from}:${fk.to}:${fk.on_update}:${fk.on_delete}:${fk.match}`,\n );\n }\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n\n// ----------------------------------------------------------------------\n// Helpers — each returns a sorted, typed list.\n// ----------------------------------------------------------------------\n\nfunction listTables(reader: SchemaReader): string[] {\n const rows = reader.list(\n \"SELECT name FROM sqlite_master WHERE type IN ('table') AND name NOT LIKE 'sqlite_%' ORDER BY name\",\n );\n return rows.map((r) => String(r[\"name\"]));\n}\n\nfunction isInternal(name: string): boolean {\n return name.startsWith(\"sqlite_\");\n}\n\nfunction isFtsShadowTable(name: string): boolean {\n // FTS5 creates several shadow tables per virtual table: <name>_data,\n // _idx, _docsize, _content, _config. They're implementation detail.\n return /_(data|idx|docsize|content|config)$/.test(name);\n}\n\nfunction isFtsVirtualTable(reader: SchemaReader, name: string): boolean {\n const r = reader.list(\n `SELECT sql FROM sqlite_master WHERE type='table' AND name='${name.replace(/'/g, \"''\")}'`,\n );\n if (r.length === 0) return false;\n const sql = String(r[0]?.[\"sql\"] ?? \"\").toLowerCase();\n return sql.includes(\"using fts\");\n}\n\nfunction ftsTokenize(reader: SchemaReader, name: string): string {\n const r = reader.list(\n `SELECT sql FROM sqlite_master WHERE type='table' AND name='${name.replace(/'/g, \"''\")}'`,\n );\n if (r.length === 0) return \"\";\n const sql = String(r[0]?.[\"sql\"] ?? \"\");\n const m = sql.match(/tokenize\\s*=\\s*['\"]([^'\"]+)['\"]/i);\n return m?.[1] ?? \"\";\n}\n\ninterface ColumnRow {\n cid: number;\n name: string;\n type: string;\n notnull: number;\n dflt_value: unknown;\n pk: number;\n}\n\nfunction tableInfo(reader: SchemaReader, name: string): ColumnRow[] {\n // PRAGMA table_info returns rows in column-declaration order; sort by\n // cid to make sure even if a future SQLite reorders them, we don't.\n const rows = reader.pragma(\"table_info\", name);\n return rows\n .map(\n (r): ColumnRow => ({\n cid: Number(r[\"cid\"] ?? 0),\n name: String(r[\"name\"] ?? \"\"),\n type: String(r[\"type\"] ?? \"\"),\n notnull: Number(r[\"notnull\"] ?? 0),\n dflt_value: r[\"dflt_value\"],\n pk: Number(r[\"pk\"] ?? 0),\n }),\n )\n .sort((a, b) => a.cid - b.cid);\n}\n\nfunction formatDefault(v: unknown): string {\n // SQLite stores defaults as the literal text from the CREATE TABLE,\n // including quoting. The Python canonicalizer renders these the same\n // way, so we just stringify with explicit null marker.\n if (v === null || v === undefined) return \"<null>\";\n return String(v);\n}\n\ninterface IndexListRow {\n seq: number;\n name: string;\n unique: number;\n origin: string;\n partial: number;\n}\n\nfunction listIndexes(reader: SchemaReader, table: string): IndexListRow[] {\n const rows = reader.pragma(\"index_list\", table);\n return rows\n .map(\n (r): IndexListRow => ({\n seq: Number(r[\"seq\"] ?? 0),\n name: String(r[\"name\"] ?? \"\"),\n unique: Number(r[\"unique\"] ?? 0),\n origin: String(r[\"origin\"] ?? \"\"),\n partial: Number(r[\"partial\"] ?? 0),\n }),\n )\n .filter((r) => !r.name.startsWith(\"sqlite_autoindex_\"))\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\ninterface IndexInfoRow {\n seqno: number;\n name: string;\n}\n\nfunction indexInfo(reader: SchemaReader, indexName: string): IndexInfoRow[] {\n const rows = reader.pragma(\"index_info\", indexName);\n return rows\n .map(\n (r): IndexInfoRow => ({\n seqno: Number(r[\"seqno\"] ?? 0),\n name: String(r[\"name\"] ?? \"\"),\n }),\n )\n .sort((a, b) => a.seqno - b.seqno);\n}\n\ninterface FkRow {\n id: number;\n seq: number;\n table: string;\n from: string;\n to: string;\n on_update: string;\n on_delete: string;\n match: string;\n}\n\nfunction listFks(reader: SchemaReader, table: string): FkRow[] {\n const rows = reader.pragma(\"foreign_key_list\", table);\n return rows\n .map(\n (r): FkRow => ({\n id: Number(r[\"id\"] ?? 0),\n seq: Number(r[\"seq\"] ?? 0),\n table: String(r[\"table\"] ?? \"\"),\n from: String(r[\"from\"] ?? \"\"),\n to: String(r[\"to\"] ?? \"\"),\n on_update: String(r[\"on_update\"] ?? \"\"),\n on_delete: String(r[\"on_delete\"] ?? \"\"),\n match: String(r[\"match\"] ?? \"\"),\n }),\n )\n .sort((a, b) => a.id - b.id || a.seq - b.seq);\n}\n","// better-sqlite3 adapter. Sync work under the hood (better-sqlite3 is\n// sync-only); every method returns a Promise.resolve() so the Storage\n// interface stays async-uniform with the wa-sqlite/OPFS adapter.\n//\n// Owns:\n// - PRAGMA setup: journal_mode=WAL, synchronous=NORMAL, busy_timeout,\n// foreign_keys=ON.\n// - Prepared-statement cache (Database#prepare returns cached\n// statements automatically; we keep the resulting Statement objects\n// on `this` for hot-path queries).\n// - Migration runner that applies the ordered list inside a single\n// transaction per migration and records applied ids.\n\nimport DatabaseCtor from \"better-sqlite3\";\nimport type { Database as BetterDb, Statement } from \"better-sqlite3\";\n\nimport { MIGRATIONS, type Migration } from \"./migrations/index.js\";\nimport { canonicalSchema, type SchemaReader } from \"./schema.js\";\nimport type {\n ClaimInsert,\n ClaimLinkKind,\n ClaimLinkRow,\n ClaimRow,\n DelegationRow,\n FetchEgressRow,\n ListSessionMemoryOpts,\n MarkStaleOpts,\n ProviderStatsRow,\n RecallSearchOpts,\n SearchHit,\n SessionMemoryInsert,\n SessionMemoryKind,\n SessionMemoryRow,\n SessionRow,\n Storage,\n Txn,\n UnsafeStorage,\n UsageLogInsert,\n UsageLogRow,\n} from \"./interface.js\";\n\nconst ALLOWED_LINK_KINDS: ReadonlySet<ClaimLinkKind> = new Set([\n \"supports\",\n \"attacks\",\n \"derives_from\",\n \"merges_with\",\n]);\n\nconst ALLOWED_MEMORY_KINDS: ReadonlySet<SessionMemoryKind> = new Set([\n \"fact\",\n \"open_question\",\n \"decision\",\n]);\n\nexport interface BetterSqliteAdapterOptions {\n /** Path to the SQLite file. `\":memory:\"` for an ephemeral DB. */\n path: string;\n /** `busy_timeout` PRAGMA (default 5000 ms — matches Python's choice\n * during the Phase-4 bridge work). */\n busyTimeoutMs?: number;\n /** Set to false to skip WAL setup (useful for :memory:). Defaults to\n * true unless path == \":memory:\". */\n wal?: boolean;\n}\n\nexport function openBetterSqliteStorage(\n opts: BetterSqliteAdapterOptions,\n): Storage {\n const db = new DatabaseCtor(opts.path);\n const wantWal = opts.wal ?? opts.path !== \":memory:\";\n if (wantWal) db.pragma(\"journal_mode = WAL\");\n db.pragma(\"synchronous = NORMAL\");\n db.pragma(`busy_timeout = ${opts.busyTimeoutMs ?? 5000}`);\n db.pragma(\"foreign_keys = ON\");\n return new BetterSqliteStorage(db);\n}\n\n// ----------------------------------------------------------------------\n// Implementation. The class implements both the public Storage surface\n// and the inner Txn surface (within `txn(fn)` we wrap calls in a\n// transaction via better-sqlite3's `db.transaction()`).\n// ----------------------------------------------------------------------\n\nclass BetterSqliteStorage implements Storage {\n private readonly stmts = new Map<string, Statement>();\n\n constructor(private readonly db: BetterDb) {}\n\n // ------------------------------------------------------------------\n // Lifecycle / lifetime\n // ------------------------------------------------------------------\n\n async migrate(): Promise<{ applied: readonly string[] }> {\n this.db.exec(\n \"CREATE TABLE IF NOT EXISTS schema_migrations (id TEXT PRIMARY KEY, applied_at INTEGER NOT NULL)\",\n );\n const existing = new Set(\n (\n this.db\n .prepare<unknown[], { id: string }>(\"SELECT id FROM schema_migrations\")\n .all() as { id: string }[]\n ).map((r) => r.id),\n );\n const applied: string[] = [];\n // Migrations are immutable + idempotent — apply in id order.\n const ordered = [...MIGRATIONS].sort((a, b) => a.id.localeCompare(b.id));\n for (const m of ordered) {\n if (existing.has(m.id)) continue;\n this.applyMigration(m);\n applied.push(m.id);\n }\n return { applied };\n }\n\n private applyMigration(m: Migration): void {\n const txn = this.db.transaction(() => {\n for (const stmt of m.up) this.db.exec(stmt);\n this.db\n .prepare(\n \"INSERT INTO schema_migrations(id, applied_at) VALUES (?, ?)\",\n )\n .run(m.id, Date.now());\n });\n txn();\n }\n\n async canonicalSchema(): Promise<string> {\n const reader: SchemaReader = {\n pragma: (name, arg) =>\n arg === undefined\n ? (this.db.pragma(name) as Record<string, unknown>[])\n : (this.db.pragma(`${name}('${arg.replace(/'/g, \"''\")}')`) as Record<\n string,\n unknown\n >[]),\n list: (sql) =>\n this.db.prepare(sql).all() as Record<string, unknown>[],\n };\n return canonicalSchema(reader);\n }\n\n unsafe(): UnsafeStorage {\n return {\n exec: async (sql, params) => {\n const stmt = this.db.prepare(sql);\n const info = stmt.run(...(params ?? []));\n return Number(info.changes);\n },\n query: async (sql, params) =>\n this.db.prepare(sql).all(...(params ?? [])) as Record<string, unknown>[],\n };\n }\n\n async close(): Promise<void> {\n this.db.close();\n }\n\n // ------------------------------------------------------------------\n // Transactions. better-sqlite3 supports nested transactions via\n // SAVEPOINTs automatically when the outer caller is already inside\n // db.transaction(). We expose a Txn that mirrors Storage's surface;\n // every method call inside the callback runs inside the open txn.\n // ------------------------------------------------------------------\n\n async txn<T>(fn: (txn: Txn) => Promise<T>): Promise<T> {\n // Manual BEGIN IMMEDIATE / COMMIT / ROLLBACK rather than better-\n // sqlite3's `db.transaction()` — that wrapper insists on a sync\n // callback, which doesn't compose with our async Storage interface.\n //\n // Safe here because: every adapter method does synchronous better-\n // sqlite3 work wrapped in Promise.resolve(), so `await fn(this)`\n // unwraps within the same microtask flush — the txn does not stay\n // open across real event-loop ticks unless the user fn deliberately\n // does I/O (which would be a misuse — callers should keep txn\n // bodies tight).\n this.db.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = await fn(this);\n this.db.exec(\"COMMIT\");\n return result;\n } catch (e) {\n try {\n this.db.exec(\"ROLLBACK\");\n } catch {\n // ROLLBACK can fail if the txn was already auto-rolled back\n // (e.g. SQLITE_BUSY). Swallow so we surface the original error.\n }\n throw e;\n }\n }\n\n // ==================================================================\n // sessions\n // ==================================================================\n\n async getSession(sessionId: string): Promise<SessionRow | null> {\n const row = this.cached(\n \"session-get\",\n \"SELECT * FROM sessions WHERE session_id = ?\",\n ).get(sessionId) as SessionRow | undefined;\n return row ?? null;\n }\n\n async listSessions(opts?: { limit?: number }): Promise<readonly SessionRow[]> {\n const limit = opts?.limit ?? 100;\n return this.cached(\n \"session-list\",\n \"SELECT * FROM sessions ORDER BY last_at DESC LIMIT ?\",\n ).all(limit) as SessionRow[];\n }\n\n async upsertSession(row: SessionRow): Promise<void> {\n this.cached(\n \"session-upsert\",\n `INSERT INTO sessions\n (session_id, started_at, last_at, calls, wall_ms, cache_hits,\n total_prompt_tokens, total_completion_tokens, total_cached_tokens,\n total_tokens, total_cost_usd, total_cpu_ms)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(session_id) DO UPDATE SET\n last_at = excluded.last_at,\n calls = excluded.calls,\n wall_ms = excluded.wall_ms,\n cache_hits = excluded.cache_hits,\n total_prompt_tokens = excluded.total_prompt_tokens,\n total_completion_tokens = excluded.total_completion_tokens,\n total_cached_tokens = excluded.total_cached_tokens,\n total_tokens = excluded.total_tokens,\n total_cost_usd = excluded.total_cost_usd,\n total_cpu_ms = excluded.total_cpu_ms`,\n ).run(\n row.session_id,\n row.started_at,\n row.last_at,\n row.calls,\n row.wall_ms,\n row.cache_hits,\n row.total_prompt_tokens,\n row.total_completion_tokens,\n row.total_cached_tokens,\n row.total_tokens,\n row.total_cost_usd,\n row.total_cpu_ms,\n );\n }\n\n async accumulateSessionTotals(\n sessionId: string,\n delta: Parameters<Storage[\"accumulateSessionTotals\"]>[1],\n ): Promise<void> {\n // First make sure the row exists; otherwise the UPDATE no-ops silently.\n this.cached(\n \"session-touch\",\n `INSERT OR IGNORE INTO sessions\n (session_id, started_at, last_at, calls, wall_ms, cache_hits)\n VALUES (?, ?, ?, 0, 0, 0)`,\n ).run(sessionId, delta.last_at ?? 0, delta.last_at ?? null);\n this.cached(\n \"session-acc\",\n `UPDATE sessions SET\n calls = calls + ?,\n wall_ms = wall_ms + ?,\n cache_hits = cache_hits + ?,\n total_prompt_tokens = total_prompt_tokens + ?,\n total_completion_tokens = total_completion_tokens + ?,\n total_cached_tokens = total_cached_tokens + ?,\n total_tokens = total_tokens + ?,\n total_cost_usd = total_cost_usd + ?,\n total_cpu_ms = total_cpu_ms + ?,\n last_at = COALESCE(?, last_at)\n WHERE session_id = ?`,\n ).run(\n delta.calls ?? 0,\n delta.wall_ms ?? 0,\n delta.cache_hits ?? 0,\n delta.total_prompt_tokens ?? 0,\n delta.total_completion_tokens ?? 0,\n delta.total_cached_tokens ?? 0,\n delta.total_tokens ?? 0,\n delta.total_cost_usd ?? 0,\n delta.total_cpu_ms ?? 0,\n delta.last_at ?? null,\n sessionId,\n );\n }\n\n // ==================================================================\n // usage_log\n // ==================================================================\n\n async insertUsage(rows: readonly UsageLogInsert[]): Promise<void> {\n if (rows.length === 0) return;\n const stmt = this.cached(\n \"usage-insert\",\n `INSERT INTO usage_log\n (session_id, ts, tool, purpose, provider, model,\n prompt_tokens, completion_tokens, cached_tokens, total_tokens,\n cost_usd, estimated, wall_ms, cpu_ms)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n const tx = this.db.transaction((items: readonly UsageLogInsert[]) => {\n for (const r of items) {\n stmt.run(\n r.session_id,\n r.ts,\n r.tool,\n r.purpose,\n r.provider,\n r.model,\n r.prompt_tokens ?? 0,\n r.completion_tokens ?? 0,\n r.cached_tokens ?? 0,\n r.total_tokens ?? 0,\n r.cost_usd ?? 0,\n r.estimated ?? 0,\n r.wall_ms ?? 0,\n r.cpu_ms ?? 0,\n );\n }\n });\n tx(rows);\n }\n\n async listUsageForSession(\n sessionId: string,\n opts?: Parameters<Storage[\"listUsageForSession\"]>[1],\n ): Promise<readonly UsageLogRow[]> {\n // Dynamic IN-clause length means we can't fully cache the prepared\n // statement — build per-call but reuse the rest. For Phase 1 this is\n // simple; later we'll memoize by signature.\n const where: string[] = [\"session_id = ?\"];\n const params: unknown[] = [sessionId];\n if (opts?.only_purpose && opts.only_purpose.length > 0) {\n where.push(`purpose IN (${opts.only_purpose.map(() => \"?\").join(\",\")})`);\n params.push(...opts.only_purpose);\n }\n if (opts?.only_provider && opts.only_provider.length > 0) {\n where.push(`provider IN (${opts.only_provider.map(() => \"?\").join(\",\")})`);\n params.push(...opts.only_provider);\n }\n const limit = opts?.limit ?? 1000;\n params.push(limit);\n const sql = `SELECT * FROM usage_log WHERE ${where.join(\" AND \")} ORDER BY id ASC LIMIT ?`;\n return this.db.prepare(sql).all(...params) as UsageLogRow[];\n }\n\n async listUsageGroupedByPurpose(\n sessionId: string,\n ): Promise<\n Awaited<ReturnType<Storage[\"listUsageGroupedByPurpose\"]>>\n > {\n return this.cached(\n \"usage-grp-purpose\",\n `SELECT purpose,\n COUNT(*) AS calls,\n COALESCE(SUM(prompt_tokens), 0) AS prompt_tokens,\n COALESCE(SUM(completion_tokens), 0) AS completion_tokens,\n COALESCE(SUM(total_tokens), 0) AS total_tokens,\n COALESCE(SUM(cost_usd), 0) AS cost_usd,\n COALESCE(SUM(wall_ms), 0) AS wall_ms,\n COALESCE(SUM(cpu_ms), 0) AS cpu_ms\n FROM usage_log\n WHERE session_id = ?\n GROUP BY purpose\n ORDER BY purpose`,\n ).all(sessionId) as Awaited<ReturnType<Storage[\"listUsageGroupedByPurpose\"]>>;\n }\n\n async listUsageGroupedByModel(\n sessionId: string,\n ): Promise<\n Awaited<ReturnType<Storage[\"listUsageGroupedByModel\"]>>\n > {\n return this.cached(\n \"usage-grp-model\",\n `SELECT provider,\n model,\n COUNT(*) AS calls,\n COALESCE(SUM(prompt_tokens), 0) AS prompt_tokens,\n COALESCE(SUM(completion_tokens), 0) AS completion_tokens,\n COALESCE(SUM(total_tokens), 0) AS total_tokens,\n COALESCE(SUM(cost_usd), 0) AS cost_usd\n FROM usage_log\n WHERE session_id = ?\n GROUP BY provider, model\n ORDER BY cost_usd DESC, provider, model`,\n ).all(sessionId) as Awaited<ReturnType<Storage[\"listUsageGroupedByModel\"]>>;\n }\n\n async listUsageGroupedByProvider(\n purpose?: string,\n ): Promise<\n Awaited<ReturnType<Storage[\"listUsageGroupedByProvider\"]>>\n > {\n if (purpose !== undefined) {\n return this.cached(\n \"usage-grp-provider-p\",\n `SELECT provider,\n COUNT(*) AS calls,\n COALESCE(SUM(total_tokens), 0) AS total_tokens,\n COALESCE(SUM(cost_usd), 0) AS cost_usd,\n 0 AS errors\n FROM usage_log\n WHERE purpose = ?\n GROUP BY provider\n ORDER BY provider`,\n ).all(purpose) as Awaited<\n ReturnType<Storage[\"listUsageGroupedByProvider\"]>\n >;\n }\n return this.cached(\n \"usage-grp-provider\",\n `SELECT provider,\n COUNT(*) AS calls,\n COALESCE(SUM(total_tokens), 0) AS total_tokens,\n COALESCE(SUM(cost_usd), 0) AS cost_usd,\n 0 AS errors\n FROM usage_log\n GROUP BY provider\n ORDER BY provider`,\n ).all() as Awaited<ReturnType<Storage[\"listUsageGroupedByProvider\"]>>;\n }\n\n async listRouterStatsByPurpose(\n purpose: string,\n sinceMs?: number,\n ): Promise<\n Awaited<ReturnType<Storage[\"listRouterStatsByPurpose\"]>>\n > {\n if (sinceMs !== undefined) {\n return this.cached(\n \"router-stats-purpose-since\",\n `SELECT LOWER(provider) AS provider,\n COUNT(*) AS calls,\n COALESCE(SUM(total_tokens), 0) AS tokens_sum,\n COALESCE(AVG(total_tokens), 0) AS avg_total_tokens,\n COALESCE(AVG(cost_usd), 0) AS avg_cost_usd,\n COALESCE(AVG(wall_ms), 0) AS avg_wall_ms\n FROM usage_log\n WHERE purpose = ? AND ts >= ? AND provider IS NOT NULL\n GROUP BY LOWER(provider)\n ORDER BY provider`,\n ).all(purpose, sinceMs) as Awaited<ReturnType<Storage[\"listRouterStatsByPurpose\"]>>;\n }\n return this.cached(\n \"router-stats-purpose\",\n `SELECT LOWER(provider) AS provider,\n COUNT(*) AS calls,\n COALESCE(SUM(total_tokens), 0) AS tokens_sum,\n COALESCE(AVG(total_tokens), 0) AS avg_total_tokens,\n COALESCE(AVG(cost_usd), 0) AS avg_cost_usd,\n COALESCE(AVG(wall_ms), 0) AS avg_wall_ms\n FROM usage_log\n WHERE purpose = ? AND provider IS NOT NULL\n GROUP BY LOWER(provider)\n ORDER BY provider`,\n ).all(purpose) as Awaited<ReturnType<Storage[\"listRouterStatsByPurpose\"]>>;\n }\n\n // ==================================================================\n // claims\n // ==================================================================\n\n async insertClaim(claim: ClaimInsert): Promise<number> {\n const info = this.cached(\n \"claim-insert\",\n `INSERT INTO claims\n (session_id, text, provider, confidence, citations_json, kind, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n claim.session_id,\n claim.text,\n claim.provider ?? null,\n claim.confidence ?? null,\n claim.citations ? JSON.stringify(claim.citations) : null,\n claim.kind ?? null,\n Date.now(),\n );\n return Number(info.lastInsertRowid);\n }\n\n async insertClaimLink(\n srcId: number,\n dstId: number,\n kind: ClaimLinkKind,\n ): Promise<void> {\n if (!ALLOWED_LINK_KINDS.has(kind)) {\n throw new RangeError(`invalid claim_link kind: ${kind}`);\n }\n this.cached(\n \"claim-link-insert\",\n `INSERT OR IGNORE INTO claim_links (src_id, dst_id, kind, created_at)\n VALUES (?, ?, ?, ?)`,\n ).run(srcId, dstId, kind, Date.now());\n }\n\n async listClaimsForSession(sessionId: string): Promise<readonly ClaimRow[]> {\n return this.cached(\n \"claim-list-session\",\n \"SELECT * FROM claims WHERE session_id = ? ORDER BY id\",\n ).all(sessionId) as ClaimRow[];\n }\n\n async getClaim(claimId: number): Promise<ClaimRow | null> {\n const row = this.cached(\n \"claim-get\",\n \"SELECT * FROM claims WHERE id = ?\",\n ).get(claimId) as ClaimRow | undefined;\n return row ?? null;\n }\n\n async listClaimLinksForSession(\n sessionId: string,\n ): Promise<readonly ClaimLinkRow[]> {\n return this.cached(\n \"claim-link-list-session\",\n `SELECT cl.*\n FROM claim_links cl\n JOIN claims c ON c.id = cl.src_id OR c.id = cl.dst_id\n WHERE c.session_id = ?\n GROUP BY cl.id\n ORDER BY cl.id`,\n ).all(sessionId) as ClaimLinkRow[];\n }\n\n async deleteClaimsForSession(sessionId: string): Promise<number> {\n const info = this.cached(\n \"claim-delete-session\",\n \"DELETE FROM claims WHERE session_id = ?\",\n ).run(sessionId);\n return Number(info.changes);\n }\n\n // ==================================================================\n // provider_stats\n // ==================================================================\n\n async listProviderStats(opts?: {\n limit?: number;\n }): Promise<readonly ProviderStatsRow[]> {\n const limit = opts?.limit ?? 100;\n return this.cached(\n \"provider-stats-list\",\n \"SELECT * FROM provider_stats ORDER BY (wins + losses + abstains) DESC, provider ASC LIMIT ?\",\n ).all(limit) as ProviderStatsRow[];\n }\n\n async getProviderStats(\n provider: string,\n ): Promise<ProviderStatsRow | null> {\n const row = this.cached(\n \"provider-stats-get\",\n \"SELECT * FROM provider_stats WHERE provider = ?\",\n ).get(provider) as ProviderStatsRow | undefined;\n return row ?? null;\n }\n\n async bumpProviderBallot(\n provider: string,\n ballot: \"agree\" | \"disagree\" | \"abstain\",\n at: number,\n ): Promise<void> {\n const column =\n ballot === \"agree\" ? \"wins\" : ballot === \"disagree\" ? \"losses\" : \"abstains\";\n this.db\n .prepare(\n `INSERT INTO provider_stats(provider, wins, losses, abstains, last_at)\n VALUES (?, 0, 0, 0, ?)\n ON CONFLICT(provider) DO NOTHING`,\n )\n .run(provider, at);\n this.db\n .prepare(\n `UPDATE provider_stats SET ${column} = ${column} + 1, last_at = ? WHERE provider = ?`,\n )\n .run(at, provider);\n }\n\n // ==================================================================\n // delegations\n // ==================================================================\n\n async insertDelegation(\n row: Parameters<Storage[\"insertDelegation\"]>[0],\n ): Promise<void> {\n this.cached(\n \"delegation-insert\",\n `INSERT INTO delegations\n (session_id, requester, tool_call, via, accepted, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n ).run(\n row.session_id,\n row.requester,\n row.tool_call,\n row.via,\n row.accepted,\n row.created_at,\n );\n }\n\n async listDelegationsForSession(\n sessionId: string,\n ): Promise<readonly DelegationRow[]> {\n return this.cached(\n \"delegation-list-session\",\n \"SELECT * FROM delegations WHERE session_id = ? ORDER BY id\",\n ).all(sessionId) as DelegationRow[];\n }\n\n async countDelegationsByRequester(requester: string): Promise<number> {\n const r = this.cached(\n \"delegation-count-req\",\n \"SELECT COUNT(*) AS n FROM delegations WHERE requester = ?\",\n ).get(requester) as { n: number };\n return Number(r.n);\n }\n\n async countDelegationsBySession(sessionId: string): Promise<number> {\n const r = this.cached(\n \"delegation-count-session\",\n \"SELECT COUNT(*) AS n FROM delegations WHERE session_id = ?\",\n ).get(sessionId) as { n: number };\n return Number(r.n);\n }\n\n async countAcceptedDelegationsBySession(sessionId: string): Promise<number> {\n const r = this.cached(\n \"delegation-count-accepted-session\",\n \"SELECT COUNT(*) AS n FROM delegations WHERE session_id = ? AND accepted = 1\",\n ).get(sessionId) as { n: number };\n return Number(r.n);\n }\n\n async countAcceptedDelegationsByRequester(requester: string): Promise<number> {\n const r = this.cached(\n \"delegation-count-accepted-requester\",\n \"SELECT COUNT(*) AS n FROM delegations WHERE requester = ? AND accepted = 1\",\n ).get(requester) as { n: number };\n return Number(r.n);\n }\n\n async listDelegationAggregatesByRequester(): Promise<\n readonly { requester: string; accepted: 0 | 1; count: number }[]\n > {\n const rows = this.cached(\n \"delegation-agg-requester\",\n \"SELECT requester, accepted, COUNT(*) AS n FROM delegations \" +\n \"WHERE requester IS NOT NULL GROUP BY requester, accepted\",\n ).all() as { requester: string; accepted: number; n: number }[];\n return rows.map((r) => ({\n requester: String(r.requester),\n accepted: (r.accepted === 1 ? 1 : 0) as 0 | 1,\n count: Number(r.n),\n }));\n }\n\n // ==================================================================\n // global counts (scoreboard / observability)\n // ==================================================================\n\n async countScoreboardTotals(): Promise<{\n sessions: number;\n claims: number;\n claim_links: number;\n delegations: number;\n }> {\n // Best-effort per-table count; missing tables degrade to 0 to\n // match Python's `try/except sqlite3.OperationalError` handling.\n const countOne = (table: string): number => {\n try {\n const r = this.db\n .prepare(`SELECT COUNT(*) AS n FROM ${table}`)\n .get() as { n: number } | undefined;\n return r ? Number(r.n) : 0;\n } catch {\n return 0;\n }\n };\n return {\n sessions: countOne(\"sessions\"),\n claims: countOne(\"claims\"),\n claim_links: countOne(\"claim_links\"),\n delegations: countOne(\"delegations\"),\n };\n }\n\n // ==================================================================\n // session_memory\n // ==================================================================\n\n async insertSessionMemory(\n row: SessionMemoryInsert & { created_at: number },\n ): Promise<number> {\n if (!ALLOWED_MEMORY_KINDS.has(row.kind)) {\n throw new RangeError(`invalid session_memory kind: ${row.kind}`);\n }\n const info = this.cached(\n \"memory-insert\",\n `INSERT INTO session_memory\n (session_id, kind, content, source_tool, source_call_id, confidence, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n row.session_id,\n row.kind,\n row.content,\n row.source_tool ?? null,\n row.source_call_id ?? null,\n row.confidence ?? null,\n row.created_at,\n );\n return Number(info.lastInsertRowid);\n }\n\n async listSessionMemory(\n sessionId: string,\n opts?: ListSessionMemoryOpts,\n ): Promise<readonly SessionMemoryRow[]> {\n const where: string[] = [\"session_id = ?\"];\n const params: unknown[] = [sessionId];\n if (!opts?.include_stale) where.push(\"stale_at IS NULL\");\n if (opts?.kinds && opts.kinds.length > 0) {\n where.push(`kind IN (${opts.kinds.map(() => \"?\").join(\",\")})`);\n params.push(...opts.kinds);\n }\n const limit = opts?.limit ?? 50;\n params.push(limit);\n const sql = `SELECT * FROM session_memory WHERE ${where.join(\" AND \")} ORDER BY id DESC LIMIT ?`;\n return this.db.prepare(sql).all(...params) as SessionMemoryRow[];\n }\n\n async markSessionMemoryStale(\n sessionId: string,\n at: number,\n opts?: MarkStaleOpts,\n ): Promise<number> {\n const where: string[] = [\"session_id = ?\", \"stale_at IS NULL\"];\n const params: unknown[] = [at, opts?.reason ?? \"manual\", sessionId];\n if (opts?.ids && opts.ids.length > 0) {\n where.push(`id IN (${opts.ids.map(() => \"?\").join(\",\")})`);\n params.push(...opts.ids);\n }\n if (opts?.kinds && opts.kinds.length > 0) {\n where.push(`kind IN (${opts.kinds.map(() => \"?\").join(\",\")})`);\n params.push(...opts.kinds);\n }\n const sql = `UPDATE session_memory SET stale_at = ?, stale_reason = ? WHERE ${where.join(\" AND \")}`;\n const info = this.db.prepare(sql).run(...params);\n return Number(info.changes);\n }\n\n async clearSessionMemory(sessionId: string): Promise<number> {\n const info = this.cached(\n \"memory-clear\",\n \"DELETE FROM session_memory WHERE session_id = ?\",\n ).run(sessionId);\n return Number(info.changes);\n }\n\n // ==================================================================\n // fetch_egress\n // ==================================================================\n\n async recordFetchEgress(\n sessionId: string,\n host: string,\n bytes: number,\n at: number,\n ): Promise<void> {\n this.cached(\n \"fetch-egress-upsert\",\n `INSERT INTO fetch_egress (session_id, host, total_bytes, last_at)\n VALUES (?, ?, ?, ?)\n ON CONFLICT(session_id, host) DO UPDATE SET\n total_bytes = total_bytes + excluded.total_bytes,\n last_at = excluded.last_at`,\n ).run(sessionId, host, bytes, at);\n }\n\n async getFetchEgressTotals(\n sessionId: string,\n ): Promise<{ total_bytes: number; unique_hosts: number }> {\n const r = this.cached(\n \"fetch-egress-totals\",\n \"SELECT COALESCE(SUM(total_bytes), 0) AS total_bytes, COUNT(DISTINCT host) AS unique_hosts FROM fetch_egress WHERE session_id = ?\",\n ).get(sessionId) as { total_bytes: number; unique_hosts: number };\n return {\n total_bytes: Number(r.total_bytes ?? 0),\n unique_hosts: Number(r.unique_hosts ?? 0),\n };\n }\n\n async hasFetchEgressHost(sessionId: string, host: string): Promise<boolean> {\n const r = this.cached(\n \"fetch-egress-has-host\",\n \"SELECT 1 AS one FROM fetch_egress WHERE session_id = ? AND host = ? LIMIT 1\",\n ).get(sessionId, host) as { one: number } | undefined;\n return r !== undefined;\n }\n\n // ==================================================================\n // transcripts_fts — the encapsulated FTS5 surface.\n // ==================================================================\n\n async indexTranscript(row: {\n path: string;\n session_id: string | null;\n tool: string;\n ts: number;\n content: string;\n }): Promise<void> {\n this.cached(\n \"fts-insert\",\n `INSERT INTO transcripts_fts (session_id, tool, ts, path, content)\n VALUES (?, ?, ?, ?, ?)`,\n ).run(row.session_id ?? \"\", row.tool, String(row.ts), row.path, row.content);\n }\n\n async recallSearch(\n query: string,\n k: number,\n opts?: RecallSearchOpts,\n ): Promise<readonly SearchHit[]> {\n const where: string[] = [\"transcripts_fts MATCH ?\"];\n const params: unknown[] = [query];\n if (opts?.session_id) {\n where.push(\"session_id = ?\");\n params.push(opts.session_id);\n }\n if (opts?.tool) {\n where.push(\"tool = ?\");\n params.push(opts.tool);\n }\n if (opts?.since_ms !== undefined) {\n where.push(\"CAST(ts AS INTEGER) >= ?\");\n params.push(opts.since_ms);\n }\n params.push(k);\n const sql =\n `SELECT path, session_id, tool, ts, ` +\n ` snippet(transcripts_fts, -1, '[[', ']]', '...', 16) AS snippet, ` +\n ` bm25(transcripts_fts) AS rank ` +\n `FROM transcripts_fts WHERE ${where.join(\" AND \")} ORDER BY rank LIMIT ?`;\n const rows = this.db.prepare(sql).all(...params) as Array<{\n path: string;\n session_id: string | null;\n tool: string | null;\n ts: string | number;\n snippet: string;\n rank: number;\n }>;\n // Normalize bm25 (lower-is-better, unbounded) to a [0, 1] score\n // (higher-is-better). Simple monotone transform; sufficient for the\n // surfaces that consume score.\n return rows.map((r) => ({\n path: r.path,\n session_id: r.session_id ?? null,\n tool: r.tool ?? null,\n ts: Number(r.ts),\n snippet: r.snippet,\n score: 1 / (1 + Math.max(0, r.rank ?? 0)),\n }));\n }\n\n // ------------------------------------------------------------------\n // Prepared-statement cache. better-sqlite3 prepares statements lazily\n // and caches them internally, but the JS-side reference still costs\n // a hashmap lookup on every call. Keeping our own map by key lets\n // hot-path queries hit a single Map.get().\n // ------------------------------------------------------------------\n\n private cached(key: string, sql: string): Statement {\n let s = this.stmts.get(key);\n if (!s) {\n s = this.db.prepare(sql);\n this.stmts.set(key, s);\n }\n return s;\n }\n}\n\n","// Standalone, importable health check for the local stats DB.\n//\n// Exists because `crosscheck doctor` (in crosscheck-cli, a separate package)\n// reported \"All clear\" while `scoreboard`/`explain` failed at runtime with a\n// better-sqlite3 native-binary ABI mismatch — doctor checked Node version,\n// secret store, activation, and keys, but never actually tried to open the\n// DB. This module runs the EXACT same open+migrate sequence node-stdio.ts\n// uses at boot (same dbPath resolution, same dynamic import so a missing/\n// incompatible native build throws here instead of crashing process boot),\n// so a doctor-side caller gets a real, actionable answer instead of an\n// opaque \"SCOREBOARD_STORAGE_UNAVAILABLE\" the first time a stats tool runs.\n//\n// Exported as its own subpath (`crosscheck-mcp/self-check`, see package.json\n// `exports`) rather than a doctor-specific export off the main entry — it's\n// a small, dependency-light surface deliberately kept separate from the full\n// MCP server bootstrap in node-stdio.ts.\n\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\n// better-sqlite3's ABI-mismatch error wraps across newlines (real example:\n// \"...NODE_MODULE_VERSION 127. This version of Node.js requires\\nNODE_MODULE_VERSION 137.\")\n// — `\\s+` between \"requires\" and the number is required, not a literal space.\nconst ABI_MISMATCH_RE =\n /NODE_MODULE_VERSION (\\d+)\\..*requires\\s+NODE_MODULE_VERSION (\\d+)/s;\n\nexport type StorageCheckResult =\n | { ok: true; path: string }\n | {\n ok: false;\n path: string;\n error: string;\n /** Populated when the failure matches better-sqlite3's own ABI-\n * mismatch message (loaded vs. required NODE_MODULE_VERSION). */\n abiMismatch?: { loaded: number; required: number };\n };\n\nfunction resolveRepoFile(rel: string): string | undefined {\n // __dirname works in both the ESM and CJS build outputs — tsup's `shims`\n // option provides it for ESM. Deliberately not `new URL(import.meta.url)`:\n // its `.pathname` carries a leading slash on Windows (`/C:/...`) that\n // breaks path.join, exactly the platform this check exists to support.\n let dir = __dirname;\n for (let i = 0; i < 8; i++) {\n const candidate = path.join(dir, rel);\n if (existsSync(candidate)) return candidate;\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\n/** Pure — extracts loaded/required NODE_MODULE_VERSION from a better-sqlite3\n * load error message, if present. Exported so the parsing logic is\n * unit-testable without needing an actual ABI-mismatched binary on disk. */\nexport function parseAbiMismatch(\n message: string,\n): { loaded: number; required: number } | undefined {\n const m = ABI_MISMATCH_RE.exec(message);\n return m ? { loaded: Number(m[1]), required: Number(m[2]) } : undefined;\n}\n\nfunction defaultDbPath(): string {\n const dataDir =\n process.env[\"CROSSCHECK_DATA_DIR\"] ||\n path.join(process.env[\"HOME\"] ?? process.env[\"USERPROFILE\"] ?? \"\", \".crosscheck\");\n return (\n process.env[\"CROSSCHECK_DB_PATH\"]\n ?? resolveRepoFile(\".crosscheck/db.sqlite\")\n ?? path.join(dataDir, \"db.sqlite\")\n );\n}\n\n/**\n * Open the real local stats DB at its real resolved path (or `dbPath` if\n * given), run a migration, and close it. Mirrors node-stdio.ts's boot-time\n * storage init exactly, so a pass here means `scoreboard`/`explain`/`recall`\n * will actually work — not just that the package resolves.\n */\nexport async function checkLocalStorage(dbPath?: string): Promise<StorageCheckResult> {\n const resolvedPath = dbPath ?? defaultDbPath();\n try {\n mkdirSync(path.dirname(resolvedPath), { recursive: true });\n const { openBetterSqliteStorage } = await import(\"./adapters/storage/better-sqlite3.js\");\n const storage = openBetterSqliteStorage({ path: resolvedPath });\n await storage.migrate();\n await storage.close();\n return { ok: true, path: resolvedPath };\n } catch (e) {\n const message = (e as Error).message ?? String(e);\n const abiMismatch = parseAbiMismatch(message);\n return {\n ok: false,\n path: resolvedPath,\n error: message,\n ...(abiMismatch ? { abiMismatch } : {}),\n };\n }\n}\n"],"mappings":"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","names":["DatabaseCtor","path","openBetterSqliteStorage"]}
@@ -0,0 +1,30 @@
1
+ type StorageCheckResult = {
2
+ ok: true;
3
+ path: string;
4
+ } | {
5
+ ok: false;
6
+ path: string;
7
+ error: string;
8
+ /** Populated when the failure matches better-sqlite3's own ABI-
9
+ * mismatch message (loaded vs. required NODE_MODULE_VERSION). */
10
+ abiMismatch?: {
11
+ loaded: number;
12
+ required: number;
13
+ };
14
+ };
15
+ /** Pure — extracts loaded/required NODE_MODULE_VERSION from a better-sqlite3
16
+ * load error message, if present. Exported so the parsing logic is
17
+ * unit-testable without needing an actual ABI-mismatched binary on disk. */
18
+ declare function parseAbiMismatch(message: string): {
19
+ loaded: number;
20
+ required: number;
21
+ } | undefined;
22
+ /**
23
+ * Open the real local stats DB at its real resolved path (or `dbPath` if
24
+ * given), run a migration, and close it. Mirrors node-stdio.ts's boot-time
25
+ * storage init exactly, so a pass here means `scoreboard`/`explain`/`recall`
26
+ * will actually work — not just that the package resolves.
27
+ */
28
+ declare function checkLocalStorage(dbPath?: string): Promise<StorageCheckResult>;
29
+
30
+ export { type StorageCheckResult, checkLocalStorage, parseAbiMismatch };
@@ -0,0 +1,30 @@
1
+ type StorageCheckResult = {
2
+ ok: true;
3
+ path: string;
4
+ } | {
5
+ ok: false;
6
+ path: string;
7
+ error: string;
8
+ /** Populated when the failure matches better-sqlite3's own ABI-
9
+ * mismatch message (loaded vs. required NODE_MODULE_VERSION). */
10
+ abiMismatch?: {
11
+ loaded: number;
12
+ required: number;
13
+ };
14
+ };
15
+ /** Pure — extracts loaded/required NODE_MODULE_VERSION from a better-sqlite3
16
+ * load error message, if present. Exported so the parsing logic is
17
+ * unit-testable without needing an actual ABI-mismatched binary on disk. */
18
+ declare function parseAbiMismatch(message: string): {
19
+ loaded: number;
20
+ required: number;
21
+ } | undefined;
22
+ /**
23
+ * Open the real local stats DB at its real resolved path (or `dbPath` if
24
+ * given), run a migration, and close it. Mirrors node-stdio.ts's boot-time
25
+ * storage init exactly, so a pass here means `scoreboard`/`explain`/`recall`
26
+ * will actually work — not just that the package resolves.
27
+ */
28
+ declare function checkLocalStorage(dbPath?: string): Promise<StorageCheckResult>;
29
+
30
+ export { type StorageCheckResult, checkLocalStorage, parseAbiMismatch };