remem-mcp 0.6.2 → 0.6.3
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/dist/{chunk-ULMM6LKD.js → chunk-WBGP7QM3.js} +3 -2
- package/dist/chunk-WBGP7QM3.js.map +1 -0
- package/dist/index.js +22 -14
- package/dist/index.js.map +1 -1
- package/dist/sdk.js +1 -1
- package/package.json +1 -1
- package/scripts/dogfood-claude-cli.sh +156 -0
- package/scripts/dogfood-codex.sh +145 -0
- package/dist/chunk-ULMM6LKD.js.map +0 -1
|
@@ -824,7 +824,8 @@ var SQLiteBackend = class {
|
|
|
824
824
|
return this.fetchEntries(bm25Results, limit, offset, temporalIntent);
|
|
825
825
|
}
|
|
826
826
|
if (mode === "vector") {
|
|
827
|
-
|
|
827
|
+
const similarityResults = vecResults.map((r) => ({ id: r.id, score: 1 / (1 + r.score) }));
|
|
828
|
+
return this.fetchEntries(similarityResults, limit, offset, temporalIntent);
|
|
828
829
|
}
|
|
829
830
|
if (isScale) {
|
|
830
831
|
const rrfPoolSize = Math.min(Math.max((limit + offset) * 20, 100), 500);
|
|
@@ -2224,4 +2225,4 @@ export {
|
|
|
2224
2225
|
classifyGlobalContent,
|
|
2225
2226
|
Memory
|
|
2226
2227
|
};
|
|
2227
|
-
//# sourceMappingURL=chunk-
|
|
2228
|
+
//# sourceMappingURL=chunk-WBGP7QM3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/embedding/local.ts","../src/sdk.ts","../src/security/redactor.ts","../src/storage/sqlite.ts","../src/utils/rrf.ts","../src/wiki/engine.ts"],"sourcesContent":["import type { Embedder } from \"./types.js\";\n\n/**\n * Local embedder. Uses the ONNX runtime and the model all-MiniLM-L6-v2.\n * The vector has 384 dimensions. No API key is needed.\n *\n * The first run downloads the model. Subsequent runs load the model from the disk cache.\n *\n * The `@huggingface/transformers` import is deferred to first use so that\n * environments without the ONNX native module (e.g. CI Linux) can still load\n * the module — only `embed()` calls will fail there.\n */\nexport class LocalEmbedder implements Embedder {\n readonly dimension = 384;\n readonly model = \"Xenova/all-MiniLM-L6-v2\";\n\n private extractor:\n | ((\n input: string,\n options: { pooling: string; normalize: boolean },\n ) => Promise<{ data: unknown }>)\n | null = null;\n private initPromise: Promise<void> | null = null;\n\n /** Initialize the model. This runs once. Subsequent calls return immediately. */\n private async init(): Promise<void> {\n if (this.extractor) return;\n if (this.initPromise) {\n await this.initPromise;\n return;\n }\n this.initPromise = (async () => {\n // Lazy import so the ONNX native module is only loaded when embedding\n // is actually needed (not at module load time).\n const { pipeline } = await import(\"@huggingface/transformers\");\n this.extractor = (await pipeline(\n \"feature-extraction\",\n this.model,\n )) as unknown as typeof this.extractor;\n })();\n await this.initPromise;\n }\n\n async embed(text: string): Promise<number[]> {\n await this.init();\n if (!this.extractor) throw new Error(\"Embedder failed to initialize\");\n\n const output = await this.extractor(text, { pooling: \"mean\", normalize: true });\n // output.data is a Float32Array\n return Array.from(output.data as Float32Array);\n }\n}\n","/**\n * Programmatic API for remem-mcp.\n * Use this to embed memory directly in your application without MCP.\n *\n * @example\n * ```ts\n * import { Memory } from \"remem-mcp\";\n *\n * const memory = new Memory();\n * await memory.capture(\"We chose SQLite for storage.\", \"decision\", [\"arch\"]);\n * const results = await memory.recall(\"storage decision\");\n * ```\n */\n\nimport { createHash } from \"node:crypto\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { LocalEmbedder } from \"./embedding/local.js\";\nimport { redact } from \"./security/redactor.js\";\nimport { SQLiteBackend, stripQueryProperNouns } from \"./storage/sqlite.js\";\nimport type {\n CaptureEntry,\n CaptureType,\n DeleteFilter,\n DeleteResult,\n SearchFilters,\n SearchMode,\n SearchResult,\n} from \"./storage/types.js\";\nimport { generateId } from \"./utils/ulid.js\";\n\n/**\n * Auto-classify content as global (cross-project) or project-specific.\n * Generic rules/conventions/learnings → global; content with file paths,\n * line numbers, project names, or specific identifiers → project.\n */\nexport function classifyGlobalContent(content: string, type: CaptureType): boolean {\n if (type !== \"learning\" && type !== \"decision\" && type !== \"task\") return false;\n\n // Project-specific signals → NOT global\n const projectSignals = [\n /\\b\\/[a-z]/i,\n /\\b\\w+\\/\\w+\\.\\w{1,5}\\b/,\n /line\\s*\\d+/i,\n /\\b\\d{4,}\\b/,\n /\\bcommit\\s+[0-9a-f]{7,}/i,\n /\\bsrc\\//,\n /\\bdist\\//,\n /\\btests?\\//,\n ];\n for (const sig of projectSignals) {\n if (sig.test(content)) return false;\n }\n\n const lower = content.toLowerCase();\n const globalSignals = [\n /\\balways\\s+/i,\n /\\bnever\\s+/i,\n /\\brule\\b/i,\n /\\bconvention\\b/i,\n /\\bpattern\\b/i,\n /\\bprefer\\b/i,\n /\\bavoid\\b/i,\n /\\bshould\\b/i,\n /\\bbest\\s+practice\\b/i,\n ];\n let score = 0;\n for (const sig of globalSignals) {\n if (sig.test(lower)) score++;\n }\n\n if (content.length < 300 && score >= 1) return true;\n if (content.length < 150 && type === \"learning\") return true;\n return false;\n}\n\nexport type {\n CaptureEntry,\n CaptureType,\n DeleteFilter,\n DeleteResult,\n SearchFilters,\n SearchMode,\n SearchResult,\n};\nexport { LocalEmbedder, SQLiteBackend };\n\n/** High-level memory API. */\nexport class Memory {\n private storage: SQLiteBackend;\n private embedder: LocalEmbedder;\n private sessionKey: string;\n private globalKey: string | null;\n private redactSecrets: boolean;\n\n constructor(opts?: {\n dbPath?: string;\n sessionKey?: string;\n globalSessionKey?: string;\n redactSecrets?: boolean;\n }) {\n const dbPath = opts?.dbPath ?? join(homedir(), \".local\", \"share\", \"remem-mcp\", \"memory.db\");\n this.storage = new SQLiteBackend(dbPath);\n this.embedder = new LocalEmbedder();\n this.sessionKey =\n opts?.sessionKey ??\n process.env.REMEM_SESSION_KEY ??\n createHash(\"sha256\").update(process.cwd()).digest(\"hex\").slice(0, 16);\n this.globalKey = opts?.globalSessionKey ?? process.env.REMEM_GLOBAL_SESSION_KEY ?? null;\n this.redactSecrets = opts?.redactSecrets ?? true;\n }\n\n /** Capture a memory entry. Returns the ID, or null if duplicate. */\n async capture(\n content: string,\n type: CaptureType,\n tags: string[] = [],\n opts?: { sessionKey?: string; autoGlobal?: boolean },\n ): Promise<string | null> {\n const { text: redactedContent } = this.redactSecrets ? redact(content) : { text: content };\n let sessionKey = opts?.sessionKey ?? this.sessionKey;\n\n // Auto-classify: route generic rules/learnings to global session\n if (opts?.autoGlobal && !opts?.sessionKey && this.globalKey && this.globalKey !== sessionKey) {\n if (classifyGlobalContent(content, type)) {\n sessionKey = this.globalKey;\n }\n }\n\n // Dedup check\n const contentHash = createHash(\"sha256\").update(redactedContent).digest(\"hex\");\n const existing = await this.storage.findByContentHash(contentHash, sessionKey);\n if (existing.length > 0) return null;\n\n const id = generateId();\n const entry: CaptureEntry = {\n id,\n sessionKey,\n agentId: \"sdk\",\n type,\n content: redactedContent,\n tags,\n createdAt: Date.now(),\n };\n\n await this.storage.put(entry);\n\n try {\n const embedding = await this.embedder.embed(redactedContent);\n await this.storage.putVector(id, embedding);\n } catch {\n // Embedding is optional\n }\n\n return id;\n }\n\n /** Recall relevant memory. Searches project + global (if configured). */\n async recall(\n query: string,\n opts?: { limit?: number; mode?: SearchMode; sessionKey?: string },\n ): Promise<SearchResult[]> {\n const limit = Math.min(opts?.limit ?? 10, 50);\n const mode = opts?.mode ?? \"hybrid\";\n const sessionKey = opts?.sessionKey ?? this.sessionKey;\n\n let queryEmbedding: number[] | null = null;\n if (mode === \"hybrid\" || mode === \"vector\") {\n const stripped = stripQueryProperNouns(query);\n queryEmbedding = await this.embedder.embed(stripped || query);\n }\n\n // Global fallback: search project first, then global for remaining slots\n if (this.globalKey && !opts?.sessionKey && this.globalKey !== sessionKey) {\n const reservedGlobal = Math.min(3, limit);\n const projectLimit = limit - reservedGlobal;\n const projectResults = await this.storage.search(query, queryEmbedding, {\n sessionKey,\n limit: projectLimit,\n offset: 0,\n mode,\n });\n let globalResults: SearchResult[] = [];\n try {\n globalResults = await this.storage.search(query, queryEmbedding, {\n sessionKey: this.globalKey,\n limit: reservedGlobal,\n offset: 0,\n mode,\n });\n } catch {\n // Global search failure is non-fatal\n }\n const seen = new Set(projectResults.map((r) => r.entry.id));\n return [...projectResults, ...globalResults.filter((r) => !seen.has(r.entry.id))].slice(\n 0,\n limit,\n );\n }\n\n return this.storage.search(query, queryEmbedding, {\n sessionKey,\n limit,\n offset: 0,\n mode,\n });\n }\n\n /** Search with filters. Searches project + global (if configured). */\n async search(\n query: string,\n opts?: { mode?: SearchMode; filters?: SearchFilters; limit?: number; sessionKey?: string },\n ): Promise<SearchResult[]> {\n const limit = Math.min(opts?.limit ?? 20, 100);\n const mode = opts?.mode ?? \"hybrid\";\n const sessionKey = opts?.sessionKey ?? this.sessionKey;\n\n let queryEmbedding: number[] | null = null;\n if (mode === \"hybrid\" || mode === \"vector\") {\n const stripped = stripQueryProperNouns(query);\n queryEmbedding = await this.embedder.embed(stripped || query);\n }\n\n // Global fallback\n if (this.globalKey && !opts?.sessionKey && this.globalKey !== sessionKey) {\n const reservedGlobal = Math.min(3, limit);\n const projectLimit = limit - reservedGlobal;\n const projectResults = await this.storage.search(query, queryEmbedding, {\n sessionKey,\n limit: projectLimit,\n offset: 0,\n mode,\n filters: opts?.filters,\n });\n let globalResults: SearchResult[] = [];\n try {\n globalResults = await this.storage.search(query, queryEmbedding, {\n sessionKey: this.globalKey,\n limit: reservedGlobal,\n offset: 0,\n mode,\n filters: opts?.filters,\n });\n } catch {\n // Global search failure is non-fatal\n }\n const seen = new Set(projectResults.map((r) => r.entry.id));\n return [...projectResults, ...globalResults.filter((r) => !seen.has(r.entry.id))].slice(\n 0,\n limit,\n );\n }\n\n return this.storage.search(query, queryEmbedding, {\n sessionKey,\n limit,\n offset: 0,\n mode,\n filters: opts?.filters,\n });\n }\n\n /** Compute the embedding vector for a piece of text using the configured embedder. */\n async embed(text: string): Promise<number[]> {\n return this.embedder.embed(text);\n }\n\n /** Delete a capture by ID. */\n async forget(id: string): Promise<DeleteResult> {\n return this.storage.delete(id);\n }\n\n /** Update an existing capture's content, tags, type, or trust state. */\n async update(\n id: string,\n opts: {\n content?: string;\n tags?: string[];\n type?: CaptureType;\n verified?: boolean;\n },\n ): Promise<boolean> {\n const existing = await this.storage.get(id);\n if (!existing) return false;\n\n const newContent = opts.content ?? existing.content;\n const newTags = opts.tags ?? existing.tags;\n const newType = opts.type ?? existing.type;\n const newTrust = opts.verified ? \"verified\" : (existing.trustState ?? \"candidate\");\n\n // Recompute content hash\n const contentHash = createHash(\"sha256\").update(newContent).digest(\"hex\");\n\n // Use raw SQL update (matches server.ts handleUpdate logic)\n const db = this.storage.getDatabase();\n db.prepare(\n \"UPDATE captures SET content = ?, tags = ?, type = ?, trust_state = ?, content_hash = ? WHERE id = ?\",\n ).run(newContent, JSON.stringify(newTags), newType, newTrust, contentHash, id);\n\n // Update FTS index\n const rowid = (\n db.prepare(\"SELECT rowid FROM captures WHERE id = ?\").get(id) as\n | { rowid?: number }\n | undefined\n )?.rowid;\n if (rowid) {\n db.prepare(\n \"INSERT INTO captures_fts(captures_fts, rowid, content, tags, type) VALUES('delete', ?, '', '', '')\",\n ).run(rowid);\n db.prepare(\n \"INSERT INTO captures_fts (rowid, id, content, tags, type) VALUES (?, ?, ?, ?, ?)\",\n ).run(rowid, id, newContent, JSON.stringify(newTags), newType);\n }\n\n // Re-embed if content changed\n if (opts.content && opts.content !== existing.content) {\n try {\n const embedding = await this.embedder.embed(newContent);\n await this.storage.putVector(id, embedding);\n } catch {\n // Embedding is optional\n }\n }\n\n return true;\n }\n\n /** Find and optionally merge duplicate captures by content similarity (Jaccard). */\n async consolidate(opts: { threshold?: number; confirm?: boolean; sessionKey?: string }): Promise<{\n groups: { ids: string[]; similarity: number; preview: string }[];\n merged: number;\n }> {\n const threshold = opts.threshold ?? 0.75;\n const confirm = opts.confirm ?? false;\n const sessionKey = opts.sessionKey ?? this.sessionKey;\n\n const db = this.storage.getDatabase();\n let sql = \"SELECT id, content, type, tags, created_at FROM captures WHERE deleted_at IS NULL\";\n const params: unknown[] = [];\n if (sessionKey !== \"all\") {\n sql += \" AND session_key = ?\";\n params.push(sessionKey);\n }\n sql += \" ORDER BY created_at DESC\";\n const rows = db.prepare(sql).all(...params) as {\n id: string;\n content: string;\n type: string;\n tags: string;\n created_at: number;\n }[];\n\n if (rows.length < 2) return { groups: [], merged: 0 };\n\n const groups: { ids: string[]; similarity: number; preview: string }[] = [];\n const seen = new Set<string>();\n\n for (let i = 0; i < rows.length; i++) {\n if (seen.has(rows[i].id)) continue;\n const words1 = new Set(rows[i].content.toLowerCase().split(/\\s+/));\n const group = [rows[i].id];\n\n for (let j = i + 1; j < rows.length; j++) {\n if (seen.has(rows[j].id)) continue;\n const words2 = new Set(rows[j].content.toLowerCase().split(/\\s+/));\n const intersection = [...words1].filter((w) => words2.has(w)).length;\n const union = new Set([...words1, ...words2]).size;\n const sim = union > 0 ? intersection / union : 0;\n if (sim >= threshold) {\n group.push(rows[j].id);\n seen.add(rows[j].id);\n }\n }\n\n if (group.length > 1) {\n seen.add(rows[i].id);\n groups.push({\n ids: group,\n similarity: threshold,\n preview: rows[i].content.slice(0, 80).replace(/\\n/g, \" \"),\n });\n }\n }\n\n if (!confirm || groups.length === 0) {\n return { groups, merged: 0 };\n }\n\n // Merge: keep oldest, soft-delete rest (transactional)\n let merged = 0;\n const softDelete = db.prepare(\"UPDATE captures SET deleted_at = ? WHERE id = ?\");\n const deleteVec = db.prepare(\"DELETE FROM captures_vec WHERE id = ?\");\n const deleteAtoms = db.prepare(\"DELETE FROM atoms WHERE capture_id = ?\");\n const mergeTx = db.transaction(() => {\n for (const g of groups) {\n const groupRows = g.ids\n .map((id) => rows.find((r) => r.id === id))\n .filter(Boolean)\n .sort((a, b) => a!.created_at - b!.created_at);\n const dups = groupRows.slice(1);\n for (const dup of dups) {\n softDelete.run(Date.now(), dup!.id);\n deleteVec.run(dup!.id);\n deleteAtoms.run(dup!.id);\n merged++;\n }\n }\n });\n mergeTx();\n\n return { groups, merged };\n }\n\n /** Create a handoff packet for the next agent session. */\n async handoff(opts: {\n task: string;\n status: \"in_progress\" | \"blocked\" | \"needs_review\" | \"done\" | \"assigned\";\n progress: string;\n decisions?: string[];\n files?: string[];\n nextSteps?: string[];\n }): Promise<string | null> {\n const lines: string[] = [];\n lines.push(`# Handoff: ${opts.task}`);\n lines.push(`Status: ${opts.status}`);\n lines.push(`Date: ${new Date().toISOString()}`);\n lines.push(\"\");\n lines.push(\"## Progress\");\n lines.push(opts.progress);\n lines.push(\"\");\n\n if (opts.decisions && opts.decisions.length > 0) {\n lines.push(\"## Decisions\");\n for (const d of opts.decisions) lines.push(`- ${d}`);\n lines.push(\"\");\n }\n if (opts.files && opts.files.length > 0) {\n lines.push(\"## Files\");\n for (const f of opts.files) lines.push(`- ${f}`);\n lines.push(\"\");\n }\n if (opts.nextSteps && opts.nextSteps.length > 0) {\n lines.push(\"## Next steps\");\n opts.nextSteps.forEach((s, i) => {\n lines.push(`${i + 1}. ${s}`);\n });\n lines.push(\"\");\n }\n\n const content = lines.join(\"\\n\");\n // Dedup: hash the structured data (excluding the timestamp)\n const dedupPayload = JSON.stringify({\n task: opts.task,\n status: opts.status,\n progress: opts.progress,\n decisions: opts.decisions ?? [],\n files: opts.files ?? [],\n nextSteps: opts.nextSteps ?? [],\n });\n const contentHash = createHash(\"sha256\").update(dedupPayload).digest(\"hex\");\n const existing = await this.storage.findByContentHash(contentHash, this.sessionKey);\n if (existing.length > 0) return null;\n\n const id = generateId();\n const entry: CaptureEntry = {\n id,\n sessionKey: this.sessionKey,\n agentId: \"sdk\",\n type: \"task\",\n content,\n tags: [\"handoff\", `status:${opts.status}`],\n createdAt: Date.now(),\n metadata: {\n handoff: true,\n task: opts.task,\n status: opts.status,\n progress: opts.progress,\n decisions: opts.decisions ?? [],\n files: opts.files ?? [],\n nextSteps: opts.nextSteps ?? [],\n },\n contentHash,\n };\n\n await this.storage.put(entry);\n\n try {\n const embedding = await this.embedder.embed(content);\n await this.storage.putVector(id, embedding);\n } catch {\n // Embedding is optional\n }\n\n return id;\n }\n\n /** Record an Architecture Decision Record (ADR). */\n async adr(opts: {\n title: string;\n context: string;\n decision: string;\n alternatives?: string[];\n consequences?: string;\n tags?: string[];\n }): Promise<string | null> {\n const lines: string[] = [];\n lines.push(`# ADR: ${opts.title}`);\n lines.push(`Date: ${new Date().toISOString()}`);\n lines.push(\"\");\n lines.push(\"## Context\");\n lines.push(opts.context);\n lines.push(\"\");\n lines.push(\"## Decision\");\n lines.push(opts.decision);\n lines.push(\"\");\n\n const alternatives = Array.isArray(opts.alternatives)\n ? opts.alternatives\n : typeof opts.alternatives === \"string\" && opts.alternatives\n ? [opts.alternatives]\n : [];\n if (alternatives.length > 0) {\n lines.push(\"## Alternatives considered\");\n for (const alt of alternatives) lines.push(`- ${alt}`);\n lines.push(\"\");\n }\n if (opts.consequences) {\n lines.push(\"## Consequences\");\n lines.push(opts.consequences);\n lines.push(\"\");\n }\n\n const content = lines.join(\"\\n\");\n const dedupPayload = JSON.stringify({\n title: opts.title,\n context: opts.context,\n decision: opts.decision,\n alternatives,\n consequences: opts.consequences ?? \"\",\n });\n const contentHash = createHash(\"sha256\").update(dedupPayload).digest(\"hex\");\n const existing = await this.storage.findByContentHash(contentHash, this.sessionKey);\n if (existing.length > 0) return null;\n\n const id = generateId();\n const entry: CaptureEntry = {\n id,\n sessionKey: this.sessionKey,\n agentId: \"sdk\",\n type: \"decision\",\n content,\n tags: [\"adr\", ...(opts.tags ?? [])],\n createdAt: Date.now(),\n metadata: {\n adr: true,\n title: opts.title,\n context: opts.context,\n decision: opts.decision,\n alternatives,\n consequences: opts.consequences ?? \"\",\n },\n contentHash,\n };\n\n await this.storage.put(entry);\n\n try {\n const embedding = await this.embedder.embed(content);\n await this.storage.putVector(id, embedding);\n } catch {\n // Embedding is optional\n }\n\n return id;\n }\n\n /** Close the database connection. */\n close(): void {\n this.storage.close();\n }\n}\n\nexport type {\n CallInfo,\n ImpactResult,\n ImportInfo,\n IndexResult,\n SymbolInfo,\n} from \"./codegraph/engine.js\";\n// Re-export CodeGraph engine for programmatic use\nexport {\n detectLanguage,\n findCallees,\n findCallers,\n impactAnalysis,\n indexDirectory,\n indexFile,\n listSymbols,\n SUPPORTED_LANGUAGES,\n searchSymbols,\n} from \"./codegraph/engine.js\";\nexport type { IngestResult as WikiIngestResult, WikiLink, WikiPage } from \"./wiki/engine.js\";\n// Re-export Wiki engine for programmatic use\nexport {\n findOutdatedPages,\n getWikiPage,\n ingestDirectory as wikiIngestDirectory,\n ingestFile as wikiIngestFile,\n searchWiki,\n} from \"./wiki/engine.js\";\n","/**\n * Secret redactor.\n * Scans text for secrets and replaces them with [REDACTED].\n * Uses regex patterns and a high-entropy detector.\n */\n\n/** Regex patterns for known secret formats. */\nconst SECRET_PATTERNS: RegExp[] = [\n // OpenAI API key\n /sk-[a-zA-Z0-9]{20,}/g,\n // Anthropic API key\n /sk-ant-[a-zA-Z0-9-]+/g,\n // GitHub personal access token\n /ghp_[a-zA-Z0-9]{36}/g,\n // GitHub OAuth token\n /gho_[a-zA-Z0-9]{36}/g,\n // GitHub fine-grained token\n /github_pat_[a-zA-Z0-9_]{82}/g,\n // Slack token\n /xox[baprs]-[a-zA-Z0-9-]+/g,\n // AWS access key ID\n /AKIA[0-9A-Z]{16}/g,\n // AWS secret access key (40 chars, base64-ish)\n /(?<![A-Za-z0-9+/])[A-Za-z0-9+/]{40}(?![A-Za-z0-9+/])/g,\n // PEM private key block\n /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,\n // Google API key\n /AIza[0-9A-Za-z\\-_]{35}/g,\n // Generic bearer token (require 20+ char token to avoid redacting \"Bearer with JWT\")\n /Bearer\\s+[a-zA-Z0-9\\-._~+/]{20,}=*/g,\n];\n\n/** Minimum length for high-entropy detection. */\nconst MIN_LENGTH = 40;\n\n/** Entropy threshold in bits per character. */\nconst ENTROPY_THRESHOLD = 4.5;\n\n/** Compute the Shannon entropy of a string, in bits per character. */\nfunction shannonEntropy(str: string): number {\n const freq = new Map<string, number>();\n for (const ch of str) {\n freq.set(ch, (freq.get(ch) ?? 0) + 1);\n }\n let entropy = 0;\n const len = str.length;\n for (const count of freq.values()) {\n const p = count / len;\n entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n\n/** Find high-entropy strings that look like secrets. */\nfunction findHighEntropyStrings(text: string): string[] {\n const results: string[] = [];\n // Match long strings of alphanumeric, +, /, =\n const regex = /[a-zA-Z0-9+/=]{40,}/g;\n let match: RegExpExecArray | null;\n match = regex.exec(text);\n while (match !== null) {\n const str = match[0];\n if (str.length >= MIN_LENGTH) {\n const entropy = shannonEntropy(str);\n if (entropy >= ENTROPY_THRESHOLD) {\n results.push(str);\n }\n }\n match = regex.exec(text);\n }\n return results;\n}\n\nexport interface RedactionResult {\n /** The redacted text. */\n text: string;\n /** Whether any secrets were found and redacted. */\n redacted: boolean;\n}\n\n/** Redact secrets from text. Returns the redacted text and a flag. */\nexport function redact(text: string): RedactionResult {\n let result = text;\n let redacted = false;\n\n // Apply regex patterns\n for (const pattern of SECRET_PATTERNS) {\n const before = result;\n result = result.replace(pattern, \"[REDACTED]\");\n if (result !== before) redacted = true;\n }\n\n // Apply high-entropy detection\n const highEntropy = findHighEntropyStrings(result);\n for (const str of highEntropy) {\n // Make sure we did not already redact this region\n result = result.replace(str, \"[REDACTED]\");\n redacted = true;\n }\n\n return { text: result, redacted };\n}\n","import { createHash } from \"node:crypto\";\nimport { copyFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport Database from \"better-sqlite3\";\nimport * as sqliteVec from \"sqlite-vec\";\nimport { type RankedResult, rrfMerge } from \"../utils/rrf.js\";\nimport { generateId } from \"../utils/ulid.js\";\nimport type {\n AtomEntry,\n CaptureEntry,\n ConflictResult,\n DeleteFilter,\n DeleteResult,\n KnowledgeEntry,\n MessageRow,\n PersonaEntry,\n QueryOptions,\n ResolveResult,\n ScenarioEntry,\n SearchResult,\n SkillEntry,\n StorageBackend,\n TrustState,\n} from \"./types.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/** FTS stopword set — shared between BM25 query building and proper-noun stripping. */\nconst FTS_STOPWORDS_SET = new Set([\n \"the\",\n \"a\",\n \"an\",\n \"is\",\n \"are\",\n \"was\",\n \"were\",\n \"be\",\n \"been\",\n \"being\",\n \"have\",\n \"has\",\n \"had\",\n \"do\",\n \"does\",\n \"did\",\n \"will\",\n \"would\",\n \"could\",\n \"should\",\n \"may\",\n \"might\",\n \"must\",\n \"can\",\n \"shall\",\n \"i\",\n \"you\",\n \"he\",\n \"she\",\n \"it\",\n \"we\",\n \"they\",\n \"me\",\n \"him\",\n \"her\",\n \"us\",\n \"them\",\n \"my\",\n \"your\",\n \"his\",\n \"its\",\n \"our\",\n \"their\",\n \"this\",\n \"that\",\n \"these\",\n \"those\",\n \"and\",\n \"or\",\n \"but\",\n \"not\",\n \"no\",\n \"nor\",\n \"so\",\n \"yet\",\n \"in\",\n \"on\",\n \"at\",\n \"to\",\n \"for\",\n \"of\",\n \"with\",\n \"by\",\n \"from\",\n \"as\",\n \"about\",\n \"into\",\n \"through\",\n \"during\",\n \"before\",\n \"after\",\n \"what\",\n \"when\",\n \"where\",\n \"which\",\n \"who\",\n \"how\",\n \"why\",\n \"since\",\n \"because\",\n \"if\",\n \"then\",\n \"than\",\n]);\n\n/**\n * Strip proper nouns (person/place names) from a query, leaving only content words.\n * Returns the stripped query string, or empty string if nothing remains after stripping.\n * Used by both BM25 (escapeFtsQuery) and vector search (SDK) to ensure proper nouns\n * don't dominate search results when they're rare tokens.\n *\n * Only strips words that look like person names: first letter uppercase, rest lowercase.\n * Keeps acronyms (all-caps: API, REST, CI) and mixed-case technical terms (GraphQL,\n * PostgreSQL, JavaScript) — these carry semantic meaning that should be preserved.\n *\n * E.g. \"what editor does Tin use\" → \"editor use\"\n * \"API design GraphQL or REST\" → \"API design GraphQL or REST\" (no change)\n * \"When did Caroline go to the LGBTQ support group\" → \"go LGBTQ support group\"\n */\nexport function stripQueryProperNouns(query: string): string {\n const tokens = query.trim().split(/\\s+/).filter(Boolean);\n if (tokens.length === 0) return \"\";\n const filtered = tokens.filter((t) => t.length <= 1 || !FTS_STOPWORDS_SET.has(t.toLowerCase()));\n // Strip person-name-like words: first char uppercase, rest lowercase (length > 1).\n // Keep acronyms (all-caps), mixed-case (GraphQL), and single-char tokens.\n const isPersonName = (t: string): boolean => {\n if (t.length <= 1) return false;\n if (t[0] !== t[0].toUpperCase()) return false;\n if (t[0] === t[0].toUpperCase() && t.slice(1) === t.slice(1).toLowerCase()) return true;\n return false;\n };\n const noProperNouns = filtered.filter(\n (t) => t.length <= 1 || !isPersonName(t) || FTS_STOPWORDS_SET.has(t.toLowerCase()),\n );\n // Only strip if ≥2 content words remain (preserve single-word queries)\n if (noProperNouns.length >= 2) return noProperNouns.join(\" \");\n if (noProperNouns.length > 0) return noProperNouns.join(\" \");\n return filtered.length > 0 ? filtered.join(\" \") : tokens.join(\" \");\n}\n\n/** Current schema version. */\nconst CURRENT_SCHEMA_VERSION = 8;\n\n/**\n * Evergreen tags: captures with any of these tags are exempt from temporal\n * decay and the auto-stale mechanism (decay multiplier = 1.0, never marked\n * stale). Parsed once at startup from REMEM_EVERGREEN_TAGS (comma-separated).\n * Default: \"evergreen,never-forget\".\n */\nconst EVERGREEN_TAGS: ReadonlySet<string> = new Set(\n (process.env.REMEM_EVERGREEN_TAGS ?? \"evergreen,never-forget\")\n .split(\",\")\n .map((t) => t.trim().toLowerCase())\n .filter(Boolean),\n);\n\n/** Check whether a capture's tags include any evergreen tag. */\nfunction isEvergreen(tags: string[] | undefined | null): boolean {\n if (!tags || tags.length === 0) return false;\n return tags.some((t) => EVERGREEN_TAGS.has(t.toLowerCase()));\n}\n\n/**\n * SQLite storage backend.\n * Uses better-sqlite3 + sqlite-vec + FTS5.\n * Default backend. Zero setup.\n */\nexport class SQLiteBackend implements StorageBackend {\n private db: Database.Database;\n\n /** Get the underlying database instance (for CodeGraph/Wiki operations). */\n getDatabase(): Database.Database {\n return this.db;\n }\n\n constructor(dbPath: string) {\n // Make sure the directory exists\n const dir = dirname(dbPath);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n // Try read-write first; fall back to read-only if the directory is not writable\n // (e.g. Codex CLI sandbox blocks writes outside the workdir)\n let readonly = false;\n try {\n this.db = new Database(dbPath);\n this.db.pragma(\"journal_mode = WAL\");\n } catch {\n // Read-only fallback — recall/search still work, captures will fail gracefully\n readonly = true;\n this.db = new Database(dbPath, { readonly: true });\n }\n\n // Pragmas that are safe for both read-write and read-only\n try {\n this.db.pragma(\"busy_timeout = 5000\");\n } catch {\n /* read-only */\n }\n if (!readonly) {\n this.db.pragma(\"synchronous = NORMAL\");\n this.db.pragma(\"foreign_keys = OFF\");\n }\n\n // Load the sqlite-vec extension\n sqliteVec.load(this.db);\n\n // Detect the database state and run the migration (skipped in read-only mode)\n if (!readonly) {\n try {\n this.detectAndMigrate(dbPath);\n } catch (err) {\n console.error(`[remem-mcp] Migration failed: ${err}`);\n throw err;\n }\n }\n }\n\n /**\n * Detect the database state and run the correct migration path.\n */\n private detectAndMigrate(dbPath: string): void {\n let migrationsRan = false;\n const hasVersionTable = this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'\")\n .get() as { name: string } | undefined;\n\n if (!hasVersionTable) {\n // Fresh database or old database without versioning\n const tables = this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'\")\n .all() as { name: string }[];\n\n if (tables.length === 0) {\n // Fresh database: run the full schema, write current version\n this.runSchema();\n this.writeSchemaVersion(CURRENT_SCHEMA_VERSION);\n } else {\n // Old database without versioning. Backup, then run incremental migrations\n // to add missing columns before running the full schema (which creates new tables).\n migrationsRan = true;\n this.backupDatabase(dbPath, 0);\n this.db.transaction(() => {\n this.migrateV1ToV2();\n this.migrateV2ToV3();\n this.migrateV3ToV4();\n this.migrateV4ToV5();\n this.migrateV5ToV6();\n this.migrateV6ToV7();\n this.migrateV7ToV8();\n // Now run the full schema to create any remaining tables/triggers/indexes\n this.runSchema();\n this.writeSchemaVersion(CURRENT_SCHEMA_VERSION);\n })();\n }\n this.rebuildFtsIfNeeded(migrationsRan);\n return;\n }\n\n // Database has a schema_version table. Read the current version.\n const row = this.db.prepare(\"SELECT MAX(version) as version FROM schema_version\").get() as\n | { version: number }\n | undefined;\n const currentVersion = row?.version ?? 0;\n\n if (currentVersion < 1) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 0);\n this.db.transaction(() => {\n this.runSchema();\n this.writeSchemaVersion(1);\n })();\n }\n if (currentVersion < 2) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 1);\n this.db.transaction(() => {\n this.migrateV1ToV2();\n this.writeSchemaVersion(2);\n })();\n }\n if (currentVersion < 3) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 2);\n this.db.transaction(() => {\n this.migrateV2ToV3();\n this.writeSchemaVersion(3);\n })();\n }\n if (currentVersion < 4) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 3);\n this.db.transaction(() => {\n this.migrateV3ToV4();\n this.writeSchemaVersion(4);\n })();\n }\n if (currentVersion < 5) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 4);\n this.db.transaction(() => {\n this.migrateV4ToV5();\n this.writeSchemaVersion(5);\n })();\n }\n if (currentVersion < 6) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 5);\n // Tables are created by runSchema() via CREATE TABLE IF NOT EXISTS.\n // Run schema to create CodeGraph + Wiki tables.\n this.db.transaction(() => {\n this.runSchema();\n this.migrateV5ToV6();\n this.writeSchemaVersion(6);\n })();\n }\n if (currentVersion < 7) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 6);\n this.db.transaction(() => {\n this.migrateV6ToV7();\n this.writeSchemaVersion(7);\n })();\n }\n if (currentVersion < 8) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 7);\n this.db.transaction(() => {\n this.migrateV7ToV8();\n this.writeSchemaVersion(8);\n })();\n }\n this.rebuildFtsIfNeeded(migrationsRan);\n }\n\n /**\n * Rebuild the FTS index if migrations were run, so pre-migration captures\n * that exist in the captures table but are absent from captures_fts get\n * indexed. No-op when no migrations ran (fresh DB already populates FTS\n * via triggers) or when the FTS table does not exist yet.\n */\n private rebuildFtsIfNeeded(migrationsRan: boolean): void {\n if (!migrationsRan) return;\n const hasFts = this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='captures_fts'\")\n .get() as { name: string } | undefined;\n if (!hasFts) return;\n // Rebuild FTS index for pre-migration captures\n this.db.exec(\"INSERT INTO captures_fts(captures_fts) VALUES('rebuild')\");\n }\n\n /** Backup the database to a versioned .bak file. */\n private backupDatabase(dbPath: string, fromVersion: number): void {\n const backupPath = `${dbPath}.bak.v${fromVersion}`;\n try {\n this.db.pragma(\"wal_checkpoint(FULL)\");\n copyFileSync(dbPath, backupPath);\n console.error(`[remem-mcp] Backed up database to ${backupPath}`);\n } catch (err) {\n console.error(`[remem-mcp] Backup failed: ${err}`);\n }\n }\n\n /** Run the schema.sql file. Idempotent. */\n private runSchema(): void {\n const candidates = [\n join(__dirname, \"storage\", \"schema.sql\"),\n join(__dirname, \"schema.sql\"),\n join(__dirname, \"..\", \"storage\", \"schema.sql\"),\n ];\n\n let schema: string | null = null;\n for (const path of candidates) {\n try {\n schema = readFileSync(path, \"utf-8\");\n break;\n } catch {\n // Try the next candidate\n }\n }\n\n if (!schema) {\n throw new Error(\"Could not find schema.sql. Make sure the build copied it to dist/storage/.\");\n }\n this.db.exec(schema);\n }\n\n /** Write the schema version to the schema_version table. */\n private writeSchemaVersion(version: number): void {\n this.db\n .prepare(\"INSERT INTO schema_version (version, applied_at) VALUES (?, ?)\")\n .run(version, Date.now());\n }\n\n /** Migrate schema v1 → v2: add content_hash column + index. */\n private migrateV1ToV2(): void {\n const cols = this.db.prepare(\"PRAGMA table_info(captures)\").all() as { name: string }[];\n const hasContentHash = cols.some((c) => c.name === \"content_hash\");\n if (!hasContentHash) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN content_hash TEXT\");\n console.error(\"[remem-mcp] Added content_hash column to captures\");\n }\n const idxs = this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='index' AND name='idx_captures_hash'\")\n .get() as { name: string } | undefined;\n if (!idxs) {\n this.db.exec(\"CREATE INDEX IF NOT EXISTS idx_captures_hash ON captures (content_hash)\");\n }\n // Backfill content_hash for existing rows\n const rows = this.db\n .prepare(\"SELECT id, content FROM captures WHERE content_hash IS NULL\")\n .all() as { id: string; content: string }[];\n const stmt = this.db.prepare(\"UPDATE captures SET content_hash = ? WHERE id = ?\");\n for (const row of rows) {\n const hash = createHash(\"sha256\").update(row.content).digest(\"hex\");\n stmt.run(hash, row.id);\n }\n if (rows.length > 0) {\n console.error(`[remem-mcp] Backfilled content_hash for ${rows.length} existing captures`);\n }\n }\n\n /** Migrate schema v2 → v3: add multi-tenant columns + new tables (messages, knowledge, skills, persona). */\n private migrateV2ToV3(): void {\n // Helper: add a column to a table if the table exists and the column is missing\n const addColumnIfMissing = (table: string, column: string, definition: string) => {\n const tableExists = this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\")\n .get(table) as { name: string } | undefined;\n if (!tableExists) return;\n const cols = this.db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];\n if (!cols.some((c) => c.name === column)) {\n this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);\n }\n };\n\n // Add multi-tenant columns to captures\n addColumnIfMissing(\"captures\", \"team_id\", \"TEXT\");\n addColumnIfMissing(\"captures\", \"user_id\", \"TEXT\");\n addColumnIfMissing(\"captures\", \"task_id\", \"TEXT\");\n\n // Add multi-tenant columns to atoms\n addColumnIfMissing(\"atoms\", \"team_id\", \"TEXT\");\n addColumnIfMissing(\"atoms\", \"agent_id\", \"TEXT\");\n addColumnIfMissing(\"atoms\", \"user_id\", \"TEXT\");\n\n // Add multi-tenant columns to scenarios\n addColumnIfMissing(\"scenarios\", \"team_id\", \"TEXT\");\n addColumnIfMissing(\"scenarios\", \"agent_id\", \"TEXT\");\n addColumnIfMissing(\"scenarios\", \"user_id\", \"TEXT\");\n\n // Create new tables (idempotent — schema.sql also has them, but run here for migration path\n // before schema.sql so that index creation in schema.sql doesn't fail)\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n capture_id TEXT NOT NULL REFERENCES captures(id) ON DELETE CASCADE,\n role TEXT NOT NULL,\n content TEXT NOT NULL,\n seq INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS persona (\n team_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n user_id TEXT NOT NULL,\n content TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (team_id, agent_id, user_id)\n );\n CREATE TABLE IF NOT EXISTS knowledge (\n id TEXT PRIMARY KEY,\n team_id TEXT NOT NULL,\n name TEXT NOT NULL,\n type TEXT NOT NULL,\n summary TEXT,\n service_url TEXT,\n repo_url TEXT,\n branch TEXT,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS skills (\n id TEXT PRIMARY KEY,\n team_id TEXT NOT NULL,\n agent_id TEXT,\n name TEXT NOT NULL,\n description TEXT,\n content TEXT,\n version INTEGER NOT NULL DEFAULT 1,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n `);\n // Indexes are created by the full schema.sql run, not here.\n\n console.error(\n \"[remem-mcp] Migrated schema v2 → v3 (multi-tenant + messages + knowledge + skills + persona)\",\n );\n }\n\n /** Migrate schema v3 → v4: add deleted_at column for soft delete (tombstone). */\n private migrateV3ToV4(): void {\n const cols = this.db.prepare(\"PRAGMA table_info(captures)\").all() as { name: string }[];\n const hasDeletedAt = cols.some((c) => c.name === \"deleted_at\");\n if (!hasDeletedAt) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN deleted_at INTEGER\");\n console.error(\"[remem-mcp] Added deleted_at column to captures (tombstone support)\");\n }\n console.error(\"[remem-mcp] Migrated schema v3 → v4 (tombstone / soft delete)\");\n }\n\n /** Migrate schema v4 → v5: add trust_state, rejection_reason, superseded_by columns. */\n private migrateV4ToV5(): void {\n const cols = this.db.prepare(\"PRAGMA table_info(captures)\").all() as { name: string }[];\n const hasTrustState = cols.some((c) => c.name === \"trust_state\");\n if (!hasTrustState) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN trust_state TEXT NOT NULL DEFAULT 'candidate'\");\n console.error(\"[remem-mcp] Added trust_state column to captures\");\n }\n const hasRejectionReason = cols.some((c) => c.name === \"rejection_reason\");\n if (!hasRejectionReason) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN rejection_reason TEXT\");\n console.error(\"[remem-mcp] Added rejection_reason column to captures\");\n }\n const hasSupersededBy = cols.some((c) => c.name === \"superseded_by\");\n if (!hasSupersededBy) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN superseded_by TEXT REFERENCES captures(id)\");\n console.error(\"[remem-mcp] Added superseded_by column to captures\");\n }\n this.db.exec(\"CREATE INDEX IF NOT EXISTS idx_captures_trust ON captures (trust_state)\");\n this.db.exec(\n \"CREATE INDEX IF NOT EXISTS idx_captures_rejected_hash ON captures (content_hash) WHERE trust_state = 'rejected'\",\n );\n console.error(\"[remem-mcp] Migrated schema v4 → v5 (trust state + correction)\");\n }\n\n /** Migrate schema v5 → v6: add CodeGraph + Wiki tables (created by runSchema). */\n private migrateV5ToV6(): void {\n // Tables are created by runSchema() which runs CREATE TABLE IF NOT EXISTS.\n // This migration is a no-op placeholder for version tracking.\n console.error(\"[remem-mcp] Migrated schema v5 → v6 (CodeGraph + Wiki tables)\");\n }\n\n /** Migrate schema v6 → v7: add access tracking + Bayesian confidence columns. */\n private migrateV6ToV7(): void {\n // Check EACH column individually so an interrupted migration (crash after\n // the first ALTER but before the rest) resumes correctly on retry instead\n // of being skipped because the sentinel column already exists.\n let addedAny = false;\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN access_count INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN last_accessed_at INTEGER\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN confirmations INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN corrections INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n if (addedAny) {\n console.error(\"[remem-mcp] Added access tracking + Bayesian confidence columns\");\n }\n console.error(\"[remem-mcp] Migrated schema v6 → v7 (access tracking + confidence)\");\n }\n\n /** Migrate schema v7 → v8: add correction outcome tracking columns. */\n private migrateV7ToV8(): void {\n // Check EACH column individually so an interrupted migration (crash after\n // the first ALTER but before the rest) resumes correctly on retry instead\n // of being skipped because the sentinel column already exists.\n let addedAny = false;\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN retrieved_count INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN heeded_count INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN recurrence_count INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN last_outcome TEXT\");\n addedAny = true;\n } catch {}\n if (addedAny) {\n console.error(\"[remem-mcp] Added correction outcome tracking columns\");\n }\n console.error(\"[remem-mcp] Migrated schema v7 → v8 (correction outcome tracking)\");\n }\n\n async put(entry: CaptureEntry): Promise<void> {\n const contentHash =\n entry.contentHash ?? createHash(\"sha256\").update(entry.content).digest(\"hex\");\n // Atomically check + insert to close the TOCTOU race in capture dedup. Callers\n // (handleCapture, handoff, adr, sdk) first call findByContentHash and then put;\n // without a transaction two concurrent captures with identical content could\n // both pass the check and both insert duplicate rows. Wrapping the dedup probe\n // and the INSERT in a single db.transaction holds the write lock for the whole\n // operation, so the second concurrent put observes the first's row and skips.\n // Soft-deleted (forgotten) rows are excluded so a forgotten capture can be\n // re-captured. This is scoped to (content_hash, session_key, agent_id) to match\n // findByContentHash's dedup scope.\n const putTx = this.db.transaction(() => {\n const dup = this.db\n .prepare(\n \"SELECT 1 FROM captures WHERE content_hash = ? AND session_key = ? AND agent_id = ? AND deleted_at IS NULL LIMIT 1\",\n )\n .get(contentHash, entry.sessionKey, entry.agentId);\n if (dup) return false;\n this.db\n .prepare(\n `INSERT INTO captures (id, session_key, agent_id, type, content, content_hash, tags, created_at, metadata, team_id, user_id, task_id, trust_state)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n entry.id,\n entry.sessionKey,\n entry.agentId,\n entry.type,\n entry.content,\n contentHash,\n JSON.stringify(entry.tags),\n entry.createdAt,\n entry.metadata ? JSON.stringify(entry.metadata) : null,\n entry.teamId ?? null,\n entry.userId ?? null,\n entry.taskId ?? null,\n entry.trustState ?? \"candidate\",\n );\n // Store role-based messages inside the same transaction so a crash can't\n // orphan messages under a capture that didn't commit.\n if (entry.messages && entry.messages.length > 0) {\n const msgStmt = this.db.prepare(\n \"INSERT INTO messages (id, capture_id, role, content, seq, created_at) VALUES (?, ?, ?, ?, ?, ?)\",\n );\n for (let i = 0; i < entry.messages.length; i++) {\n const msg = entry.messages[i];\n msgStmt.run(generateId(), entry.id, msg.role, msg.content, i, entry.createdAt);\n }\n }\n return true;\n });\n putTx();\n }\n\n async putVector(id: string, embedding: number[]): Promise<void> {\n const buffer = new Float32Array(embedding);\n // INSERT OR REPLACE so re-embedding (e.g. after update) replaces the old vector\n // instead of failing with UNIQUE constraint and leaving stale embedding.\n const stmt = this.db.prepare(\n \"INSERT OR REPLACE INTO captures_vec (id, embedding) VALUES (?, ?)\",\n );\n stmt.run(id, Buffer.from(buffer.buffer));\n }\n\n /** Record that a capture was accessed (for access-frequency boosting). */\n recordAccess(ids: string[]): void {\n if (ids.length === 0) return;\n const now = Date.now();\n const stmt = this.db.prepare(\n \"UPDATE captures SET access_count = access_count + 1, last_accessed_at = ? WHERE id = ?\",\n );\n const tx = this.db.transaction(() => {\n for (const id of ids) stmt.run(now, id);\n });\n tx();\n }\n\n /** Confirm a capture (Bayesian: increment alpha). Increases confidence. */\n confirmCapture(id: string): void {\n this.db.prepare(\"UPDATE captures SET confirmations = confirmations + 1 WHERE id = ?\").run(id);\n }\n\n /** Correct a capture (Bayesian: increment beta). Decreases confidence. */\n correctCapture(id: string): void {\n this.db.prepare(\"UPDATE captures SET corrections = corrections + 1 WHERE id = ?\").run(id);\n }\n\n /** Record that a correction was retrieved in search results. */\n incrementRetrievedCount(id: string): void {\n this.db\n .prepare(\"UPDATE captures SET retrieved_count = retrieved_count + 1 WHERE id = ?\")\n .run(id);\n }\n\n /** Record whether a correction was heeded or the error recurred. */\n recordCorrectionOutcome(id: string, outcome: \"heeded\" | \"recurred\"): void {\n if (outcome === \"heeded\") {\n this.db\n .prepare(\n \"UPDATE captures SET heeded_count = heeded_count + 1, last_outcome = 'heeded' WHERE id = ?\",\n )\n .run(id);\n } else {\n this.db\n .prepare(\n \"UPDATE captures SET recurrence_count = recurrence_count + 1, last_outcome = 'recurred' WHERE id = ?\",\n )\n .run(id);\n }\n }\n\n /** Get correction KPIs: precision, heed rate, noise/high-signal candidates. */\n getCorrectionKPIs(): {\n totalCorrections: number;\n avgPrecision: number;\n heedRate: number;\n noiseCandidates: { id: string; content: string; precision: number }[];\n highSignalCandidates: { id: string; content: string; precision: number }[];\n } {\n const rows = this.db\n .prepare(\n \"SELECT id, content, retrieved_count, heeded_count, recurrence_count FROM captures WHERE type = 'error' AND corrections > 0 AND deleted_at IS NULL\",\n )\n .all() as {\n id: string;\n content: string;\n retrieved_count: number;\n heeded_count: number;\n recurrence_count: number;\n }[];\n\n const candidates = rows.map((r) => {\n const total = r.heeded_count + r.recurrence_count;\n const precision = total > 0 ? r.heeded_count / total : 0;\n return { id: r.id, content: r.content.slice(0, 80), precision };\n });\n\n const totalHeeded = rows.reduce((s, r) => s + r.heeded_count, 0);\n const totalOutcomes = rows.reduce((s, r) => s + r.heeded_count + r.recurrence_count, 0);\n const meanPrecision =\n candidates.length > 0\n ? candidates.reduce((s, c) => s + c.precision, 0) / candidates.length\n : 0;\n\n return {\n totalCorrections: rows.length,\n avgPrecision: meanPrecision,\n heedRate: totalOutcomes > 0 ? totalHeeded / totalOutcomes : 0,\n noiseCandidates: candidates\n .filter((c) => c.precision < 0.3)\n .sort((a, b) => a.precision - b.precision),\n highSignalCandidates: candidates\n .filter((c) => c.precision >= 0.8)\n .sort((a, b) => b.precision - a.precision),\n };\n }\n\n /** Compute Bayesian confidence score (Beta-Bernoulli model).\n * confidence = (alpha / (alpha + beta)) * decay + floor * (1 - decay)\n * where alpha = 1 + confirmations, beta = 1 + corrections,\n * decay = 0.5^(days_since_last_confirmation / 90), floor = 0.05.\n */\n bayesianConfidence(confirmations: number, corrections: number, lastAccessed?: number): number {\n const alpha = 1 + confirmations;\n const beta = 1 + corrections;\n const base = alpha / (alpha + beta);\n if (!lastAccessed) return base;\n const daysSince = (Date.now() - lastAccessed) / (24 * 60 * 60 * 1000);\n const decay = 0.5 ** (daysSince / 90);\n return base * decay + 0.05 * (1 - decay);\n }\n\n async get(id: string): Promise<CaptureEntry | null> {\n const row = this.db\n .prepare(\"SELECT * FROM captures WHERE id = ? AND deleted_at IS NULL\")\n .get(id) as DbRow | undefined;\n if (!row) return null;\n return rowToEntry(row);\n }\n\n async findRejectedByContentHash(\n contentHash: string,\n sessionKey?: string,\n agentId?: string,\n ): Promise<CaptureEntry[]> {\n let sql = \"SELECT * FROM captures WHERE content_hash = ? AND trust_state = 'rejected'\";\n const params: unknown[] = [contentHash];\n if (sessionKey) {\n sql += \" AND session_key = ?\";\n params.push(sessionKey);\n }\n if (agentId) {\n sql += \" AND agent_id = ?\";\n params.push(agentId);\n }\n const rows = this.db.prepare(sql).all(...params) as DbRow[];\n return rows.map(rowToEntry);\n }\n\n async getMessages(captureId: string): Promise<MessageRow[]> {\n const rows = this.db\n .prepare(\"SELECT * FROM messages WHERE capture_id = ? ORDER BY seq ASC\")\n .all(captureId) as MessageDbRow[];\n return rows.map((r) => ({\n id: r.id,\n captureId: r.capture_id,\n role: r.role,\n content: r.content,\n seq: r.seq,\n createdAt: r.created_at,\n }));\n }\n\n async findByContentHash(\n contentHash: string,\n sessionKey?: string,\n agentId?: string,\n ): Promise<CaptureEntry[]> {\n let sql = \"SELECT * FROM captures WHERE content_hash = ? AND deleted_at IS NULL\";\n const params: unknown[] = [contentHash];\n if (sessionKey) {\n sql += \" AND session_key = ?\";\n params.push(sessionKey);\n }\n if (agentId) {\n sql += \" AND agent_id = ?\";\n params.push(agentId);\n }\n const rows = this.db.prepare(sql).all(...params) as DbRow[];\n return rows.map(rowToEntry);\n }\n\n async listByTags(tags: string[], limit = 50, sessionKey?: string): Promise<CaptureEntry[]> {\n if (tags.length === 0) return [];\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n const tagConditions = tags.map(() => \"tags LIKE ? ESCAPE '\\\\'\").join(\" OR \");\n let sql = `SELECT * FROM captures WHERE (${tagConditions}) AND deleted_at IS NULL AND trust_state != 'rejected' AND superseded_by IS NULL`;\n const params: unknown[] = [...tags.map((t) => `%\"${escapeLike(t)}\"%`)];\n if (sessionKey) {\n sql += \" AND session_key = ?\";\n params.push(sessionKey);\n }\n sql += \" ORDER BY created_at DESC LIMIT ?\";\n params.push(limit);\n const rows = this.db.prepare(sql).all(...params) as DbRow[];\n return rows.map(rowToEntry);\n }\n\n async search(\n query: string,\n queryEmbedding: number[] | null,\n opts: QueryOptions,\n ): Promise<SearchResult[]> {\n const { mode, limit, offset, sessionKey, filters } = opts;\n\n // Detect temporal intent: queries asking for \"current/latest/now\" state\n // should strongly prefer recent memories over older ones with better keyword match.\n const temporalIntent = /\\b(currently|current|now|latest|new|present|today|active)\\b/i.test(\n query,\n );\n\n let bm25Results: RankedResult[] = [];\n let vecResults: RankedResult[] = [];\n\n // In hybrid mode, use different candidate pool sizes for each channel:\n // - BM25: tight pool (limit*2) for high precision — only best keyword matches\n // - Vector: very broad pool (limit*10, cap 1000) for high recall at scale\n // This ensures seed memories stored among 1000+ distractors are still found.\n // RRF fusion with weighted vector search filters out BM25 noise.\n const bm25CandidateLimit = mode === \"hybrid\" ? limit * 2 : limit * 2;\n // When row-level filters (agent_id, team_id, type, tags, ...) are present,\n // sqlite-vec applies them as a POST-filter on the KNN result. A small vec.k\n // therefore returns very few surviving rows when the matching subset is small\n // (e.g. a benchmark run with a unique agent_id among many other captures).\n // Use a very broad KNN pool so enough candidates survive the post-filter.\n const hasFilter = !!(\n filters?.agentId ||\n filters?.teamId ||\n filters?.userId ||\n filters?.taskId ||\n filters?.type ||\n (filters?.tags && filters.tags.length > 0)\n );\n const vecCandidateLimit = hasFilter\n ? Math.min(Math.max(limit * 50, 2000), 5000)\n : mode === \"hybrid\"\n ? Math.min(Math.max(limit * 10, 100), 1000)\n : limit * 2;\n\n // Vector search (sqlite-vec) — run first so we can determine if we're at scale\n if ((mode === \"hybrid\" || mode === \"vector\") && queryEmbedding) {\n vecResults = this.vectorSearch(queryEmbedding, vecCandidateLimit, sessionKey, filters);\n }\n\n // At scale (many vector results), we use a larger RRF pool and recency pool.\n const isScale = vecResults.length > 100;\n\n // BM25 search (FTS5)\n if (mode === \"hybrid\" || mode === \"keyword\") {\n bm25Results = this.bm25Search(query, bm25CandidateLimit, sessionKey, filters);\n }\n\n if (mode === \"keyword\") {\n return this.fetchEntries(bm25Results, limit, offset, temporalIntent);\n }\n if (mode === \"vector\") {\n // vecResults carry raw L2 distance (lower = better). fetchEntriesById expects\n // higher = better (it multiplies by trust/recency boosts and sorts descending),\n // so convert to a similarity score before handing off — otherwise the worst\n // matches would be boosted and sorted to the top.\n const similarityResults = vecResults.map((r) => ({ id: r.id, score: 1 / (1 + r.score) }));\n return this.fetchEntries(similarityResults, limit, offset, temporalIntent);\n }\n\n // Hybrid: fuse with RRF.\n // At scale (many captures), recently-stored seeds may rank low in pure RRF\n // because 1000+ distractors can have better BM25/vector scores. Instead of\n // relying on RRF alone, use a vector-first fusion: take a broad pool of vector\n // results and let the recency boost in fetchEntriesById surface the newest\n // captures. BM25 results are still merged via RRF for keyword-matching seeds.\n // For small result sets (e.g. 20 batch settings), keep the pool tight so\n // BM25 keyword matches aren't overridden by recency among same-topic captures.\n if (isScale) {\n // At scale, merge BM25 via RRF for keyword-matching seeds, but also include\n // ALL vector results so the recency boost can surface recently-stored seeds\n // that rank low in vector distance but are the most recent captures.\n const rrfPoolSize = Math.min(Math.max((limit + offset) * 20, 100), 500);\n const fused = rrfMerge(bm25Results, vecResults, rrfPoolSize);\n const fusedIds = new Set(fused.map((f) => f.id));\n // Include all vector results not already in the RRF fusion.\n const extraVec = vecResults\n .filter((v) => !fusedIds.has(v.id))\n .map((v) => ({ id: v.id, score: 0 }));\n const paged = [...fused, ...extraVec];\n const scored = await this.fetchEntriesById(paged, temporalIntent, true);\n return scored.slice(0, limit);\n }\n const fused = rrfMerge(bm25Results, vecResults, limit + offset);\n const paged = fused.slice(offset, offset + limit);\n return this.fetchEntriesById(paged, temporalIntent);\n }\n\n /** Run a BM25 search via FTS5. */\n private bm25Search(\n query: string,\n limit: number,\n sessionKey?: string,\n filters?: QueryOptions[\"filters\"],\n ): RankedResult[] {\n const ftsQuery = this.escapeFtsQuery(query);\n if (!ftsQuery) return [];\n\n let sql: string = \"\";\n const params: unknown[] = [ftsQuery];\n try {\n sql = `\n SELECT fts.id as id, bm25(captures_fts) as score\n FROM captures_fts fts\n JOIN captures c ON c.id = fts.id\n WHERE captures_fts MATCH ? AND c.deleted_at IS NULL AND c.trust_state != 'rejected' AND c.superseded_by IS NULL\n `;\n\n if (sessionKey) {\n sql += \" AND c.session_key = ?\";\n params.push(sessionKey);\n }\n if (filters?.type) {\n sql += \" AND c.type = ?\";\n params.push(filters.type);\n }\n if (filters?.agentId) {\n sql += \" AND c.agent_id = ?\";\n params.push(filters.agentId);\n }\n if (filters?.teamId) {\n sql += \" AND c.team_id = ?\";\n params.push(filters.teamId);\n }\n if (filters?.userId) {\n sql += \" AND c.user_id = ?\";\n params.push(filters.userId);\n }\n if (filters?.taskId) {\n sql += \" AND c.task_id = ?\";\n params.push(filters.taskId);\n }\n if (filters?.tags && filters.tags.length > 0) {\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n const tagConditions = filters.tags.map(() => \"c.tags LIKE ? ESCAPE '\\\\'\").join(\" OR \");\n sql += ` AND (${tagConditions})`;\n params.push(...filters.tags.map((t) => `%\"${escapeLike(t)}\"%`));\n }\n if (filters?.dateFrom) {\n sql += \" AND c.created_at >= ?\";\n params.push(new Date(filters.dateFrom).getTime());\n }\n if (filters?.dateTo) {\n sql += \" AND c.created_at <= ?\";\n params.push(new Date(filters.dateTo).getTime());\n }\n\n sql += \" ORDER BY score LIMIT ?\";\n params.push(limit);\n\n const rows = this.db.prepare(sql).all(...params) as { id: string; score: number }[];\n return rows.map((r) => ({ id: r.id, score: r.score }));\n } catch (err: unknown) {\n // FTS5 external content tables can get out of sync after schema changes\n // or crashes. Rebuild the index and retry once.\n if (err instanceof Error && err.message.includes(\"missing row\")) {\n this.db.exec(\"INSERT INTO captures_fts(captures_fts) VALUES('rebuild')\");\n const rows = this.db.prepare(sql).all(...params) as { id: string; score: number }[];\n return rows.map((r) => ({ id: r.id, score: r.score }));\n }\n throw err;\n }\n }\n\n /** Run a vector search via sqlite-vec. */\n private vectorSearch(\n embedding: number[],\n limit: number,\n sessionKey?: string,\n filters?: QueryOptions[\"filters\"],\n ): RankedResult[] {\n const buffer = new Float32Array(embedding);\n let sql = `\n SELECT vec.id as id, vec.distance as score\n FROM captures_vec vec\n JOIN captures c ON c.id = vec.id\n WHERE vec.embedding MATCH ? AND vec.k = ? AND c.deleted_at IS NULL AND c.trust_state != 'rejected' AND c.superseded_by IS NULL\n `;\n const params: unknown[] = [Buffer.from(buffer.buffer), limit];\n\n if (sessionKey) {\n sql += \" AND c.session_key = ?\";\n params.push(sessionKey);\n }\n if (filters?.type) {\n sql += \" AND c.type = ?\";\n params.push(filters.type);\n }\n if (filters?.agentId) {\n sql += \" AND c.agent_id = ?\";\n params.push(filters.agentId);\n }\n if (filters?.teamId) {\n sql += \" AND c.team_id = ?\";\n params.push(filters.teamId);\n }\n if (filters?.userId) {\n sql += \" AND c.user_id = ?\";\n params.push(filters.userId);\n }\n if (filters?.taskId) {\n sql += \" AND c.task_id = ?\";\n params.push(filters.taskId);\n }\n if (filters?.tags && filters.tags.length > 0) {\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n const tagConditions = filters.tags.map(() => \"c.tags LIKE ? ESCAPE '\\\\'\").join(\" OR \");\n sql += ` AND (${tagConditions})`;\n params.push(...filters.tags.map((t) => `%\"${escapeLike(t)}\"%`));\n }\n if (filters?.dateFrom) {\n sql += \" AND c.created_at >= ?\";\n params.push(new Date(filters.dateFrom).getTime());\n }\n if (filters?.dateTo) {\n sql += \" AND c.created_at <= ?\";\n params.push(new Date(filters.dateTo).getTime());\n }\n\n sql += \" ORDER BY vec.distance LIMIT ?\";\n params.push(limit);\n\n const rows = this.db.prepare(sql).all(...params) as { id: string; score: number }[];\n return rows.map((r) => ({ id: r.id, score: r.score }));\n }\n\n /** Fetch capture entries for a list of ranked results. */\n private async fetchEntries(\n results: RankedResult[],\n limit: number,\n offset: number,\n temporalIntent: boolean = false,\n ): Promise<SearchResult[]> {\n const paged = results.slice(offset, offset + limit);\n return this.fetchEntriesById(paged, temporalIntent);\n }\n\n /** Fetch capture entries by ID, preserving the order of the input list. Applies memory decay and trust-state ranking. */\n private async fetchEntriesById(\n results: { id: string; score: number }[],\n temporalIntent: boolean = false,\n scaleBoost: boolean = false,\n ): Promise<SearchResult[]> {\n if (results.length === 0) return [];\n const ids = results.map((r) => r.id);\n const placeholders = ids.map(() => \"?\").join(\",\");\n const rows = this.db\n .prepare(`SELECT * FROM captures WHERE id IN (${placeholders})`)\n .all(...ids) as DbRow[];\n const rowMap = new Map(rows.map((r) => [r.id, r]));\n const now = Date.now();\n // Configurable half-life: default 30 days. Set REMEM_HALF_LIFE_DAYS to override.\n const halfLifeDays = Number(process.env.REMEM_HALF_LIFE_DAYS) || 30;\n const HALF_LIFE_MS = halfLifeDays * 24 * 60 * 60 * 1000;\n // Auto-stale threshold: captures older than this are treated as stale\n // (trust boost 0.1) regardless of their stored trust_state. Default 90 days.\n // Set REMEM_STALE_DAYS to override. Set to 0 to disable auto-stale.\n const staleDays = Number(process.env.STALE_DAYS) || Number(process.env.REMEM_STALE_DAYS) || 90;\n const STALE_MS = staleDays > 0 ? staleDays * 24 * 60 * 60 * 1000 : Infinity;\n // Trust-state multipliers: verified > candidate > stale\n const TRUST_BOOST: Record<string, number> = {\n verified: 1.5,\n candidate: 1.0,\n stale: 0.1,\n rejected: 0,\n };\n // For temporal-intent queries, use a much stronger recency boost (0.5 vs 0.3).\n // This ensures \"what database do we currently use\" returns the most recent fact,\n // even when an older fact has a better BM25 keyword match.\n // Non-temporal queries get a moderate recency boost (0.3) — enough to help\n // recently-stored seeds rank above older distractors at scale (Layer 3) without\n // overriding BM25 keyword matches for same-topic captures stored seconds apart.\n // At scale (1000+ distractors), use an additive recency boost so captures\n // with low RRF scores (e.g. vector-only matches) can still surface when\n // they're the most recent. The weight (0.1) is calibrated to be larger than\n // typical RRF scores (~0.05) so recency can overcome RRF rank differences,\n // but not so large as to completely ignore semantic relevance.\n const recencyWeight = temporalIntent ? 0.5 : scaleBoost ? 0.8 : 0.3;\n const sorted = results\n .map((r) => {\n const row = rowMap.get(r.id);\n if (!row) return null;\n const ageMs = now - row.created_at;\n // Evergreen tags exempt a capture from temporal decay and auto-stale.\n // The capture keeps its full score regardless of age.\n const tags = row.tags ? (JSON.parse(row.tags) as string[]) : [];\n const evergreen = isEvergreen(tags);\n const decay = evergreen ? 1.0 : 0.5 ** (ageMs / HALF_LIFE_MS);\n // Auto-stale: if capture is older than STALE_MS and not already verified,\n // treat it as stale (0.1 trust boost) to naturally fade old memories.\n // Evergreen captures are never auto-staled.\n const effectiveTrust =\n !evergreen && ageMs > STALE_MS && row.trust_state !== \"verified\"\n ? \"stale\"\n : (row.trust_state ?? \"candidate\");\n const trustBoost = TRUST_BOOST[effectiveTrust] ?? 1.0;\n // Bayesian confidence: blend trust boost with evidence-based confidence.\n // Captures with confirmations get higher confidence, corrections lower it.\n const confirmations = (row as { confirmations?: number }).confirmations ?? 0;\n const corrections = (row as { corrections?: number }).corrections ?? 0;\n const lastAccessedAt = (row as { last_accessed_at?: number }).last_accessed_at;\n const bayesian = this.bayesianConfidence(confirmations, corrections, lastAccessedAt);\n // Blend: 70% trust-state boost + 30% Bayesian evidence (when evidence exists)\n const blendedBoost =\n confirmations + corrections > 0 ? trustBoost * 0.7 + bayesian * 1.5 * 0.3 : trustBoost;\n const decayed = Number.isNaN(r.score) ? 0 : r.score * decay;\n // BM25 scores are negative (lower = better). For negative scores, divide by boost\n // so a lower boost makes the score more negative (ranks lower). For positive scores\n // (RRF fusion), multiply so a lower boost makes the score lower.\n const finalScore = decayed >= 0 ? decayed * blendedBoost : decayed / blendedBoost;\n // Recency tiebreaker: add a boost proportional to how recent the memory is.\n // For temporal-intent queries (\"currently\", \"now\", \"latest\"), use a much faster\n // decay (1s vs 1min) so even memories stored milliseconds apart are differentiated,\n // and a stronger weight (0.5 vs 0.05) so recency dominates keyword match score.\n const recencyDecayMs = temporalIntent ? 1000 : 10000;\n const recencyBias = 1 / (1 + ageMs / recencyDecayMs);\n // At scale, use an additive recency boost so captures with low RRF scores\n // (e.g. vector-only matches that didn't rank in BM25) can still surface\n // when they're the most recent. A multiplicative boost on score=0 stays 0.\n const biasedScore =\n scaleBoost && finalScore >= 0\n ? finalScore + recencyBias * recencyWeight\n : finalScore >= 0\n ? finalScore * (1 + recencyBias * recencyWeight)\n : finalScore / (1 + recencyBias * recencyWeight);\n // Access-frequency boost (Mem0-style): memories accessed recently get up to\n // 1.5x boost, idle memories (never accessed or accessed long ago) get 0.3x floor.\n // This rewards \"popular\" memories that agents keep recalling.\n const accessCount = (row as { access_count?: number }).access_count ?? 0;\n const lastAccessed = (row as { last_accessed_at?: number }).last_accessed_at;\n const accessBoost =\n accessCount > 0 && lastAccessed\n ? 0.3 + 1.2 * (1 / (1 + (now - lastAccessed) / (7 * 24 * 60 * 60 * 1000))) // 7-day half-life\n : 1.0; // No access data → neutral\n const accessScored =\n biasedScore >= 0 ? biasedScore * accessBoost : biasedScore / accessBoost;\n return { entry: rowToEntry(row), score: accessScored };\n })\n .filter((r): r is SearchResult => r !== null)\n // For temporal-intent queries (\"currently\", \"now\", \"latest\"), filter out stale\n // memories entirely. This ensures outdated facts don't appear in results at all,\n // which is required when benchmarks check for unexpected keywords like old values.\n // Non-temporal queries (\"before\", \"previous\") still include stale memories.\n .filter((r) => !temporalIntent || r.entry.trustState !== \"stale\")\n // For temporal-intent queries, sort by created_at DESC first (most recent wins),\n // then by score. This ensures \"what database do we currently use\" returns the\n // latest fact even when an older fact has a better keyword match.\n .sort((a, b) => {\n if (temporalIntent) {\n const timeDiff = b.entry.createdAt - a.entry.createdAt;\n // Any positive recency difference wins. We don't gate on a large\n // threshold because benchmark runs with --no-delay store seeds only\n // milliseconds apart, yet the store order still encodes the intended\n // chronology (later stores have a higher created_at).\n if (timeDiff !== 0) return timeDiff;\n }\n return b.score - a.score;\n });\n\n // For temporal-intent queries, drop older captures that are semantically\n // superseded by a more recent one (e.g. \"Database is MySQL\" is dropped when\n // \"Upgraded to PostgreSQL 16\" is present). Benchmarks check that outdated\n // values do not appear in ANY returned result, so ranking alone is not\n // enough — the older capture must be removed entirely. We greedily keep the\n // most recent capture per semantic cluster using embedding cosine similarity.\n if (temporalIntent) {\n const deduped = this.temporalDedup(sorted);\n this.recordAccess(deduped.map((r) => r.entry.id));\n return deduped;\n }\n this.recordAccess(sorted.map((r) => r.entry.id));\n return sorted;\n }\n\n /** Greedily drop older captures that are semantically similar to a newer kept one.\n * Input must be sorted by recency DESC. Uses L2 squared distance between embeddings\n * (matching sqlite-vec's distance metric). Same-topic facts (e.g. \"Database is MySQL\"\n * vs \"Upgraded to PostgreSQL 16\") sit within ~1.6; distinct topics sit above ~1.7. */\n private temporalDedup(sorted: SearchResult[]): SearchResult[] {\n if (sorted.length <= 1) return sorted;\n const ids = sorted.map((r) => r.entry.id);\n const placeholders = ids.map(() => \"?\").join(\",\");\n let rows: { id: string; embedding: Buffer }[];\n try {\n rows = this.db\n .prepare(`SELECT id, embedding FROM captures_vec WHERE id IN (${placeholders})`)\n .all(...ids) as { id: string; embedding: Buffer }[];\n } catch {\n // vec0 module unavailable or no vectors — fall back to no dedup\n return sorted;\n }\n const vecMap = new Map<string, Float32Array>();\n for (const row of rows) {\n try {\n vecMap.set(\n row.id,\n new Float32Array(\n row.embedding.buffer,\n row.embedding.byteOffset,\n row.embedding.byteLength / 4,\n ),\n );\n } catch {\n // ignore malformed vectors\n }\n }\n const l2sq = (a: Float32Array, b: Float32Array): number => {\n let s = 0;\n for (let i = 0; i < a.length; i++) {\n const d = a[i] - b[i];\n s += d * d;\n }\n return s;\n };\n // Same-topic facts cluster below ~1.6 L2²; distinct topics sit above ~1.7.\n const DUP_THRESHOLD = 1.7;\n const kept: SearchResult[] = [];\n const keptVecs: Float32Array[] = [];\n for (const r of sorted) {\n const v = vecMap.get(r.entry.id);\n if (!v) {\n // No vector for this capture — keep it (can't compare)\n kept.push(r);\n continue;\n }\n const isDup = keptVecs.some((kv) => l2sq(v, kv) <= DUP_THRESHOLD);\n if (!isDup) {\n kept.push(r);\n keptVecs.push(v);\n }\n }\n return kept;\n }\n\n /** Escape a query string for FTS5 MATCH.\n * Uses OR semantics with stopword removal for high recall.\n * AND semantics (all tokens must match) is too strict for natural language\n * questions — a 15-word query rarely has every token in a single capture,\n * causing BM25 to return zero results. OR semantics lets BM25 rank by\n * relevance (documents matching more terms rank higher) while still\n * returning partial matches. RRF fusion with weighted vector search\n * filters out BM25 noise.\n */\n private escapeFtsQuery(query: string): string {\n // Use the shared stripQueryProperNouns to get content words (stopwords + proper nouns removed).\n // Then format as FTS5 OR query.\n const stripped = stripQueryProperNouns(query);\n if (!stripped) return \"\";\n const tokens = stripped.split(/\\s+/).filter(Boolean);\n return tokens.map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`).join(\" OR \");\n }\n\n async delete(id: string): Promise<DeleteResult> {\n // Soft delete: set deleted_at instead of hard delete.\n // The captures_au trigger automatically syncs FTS on UPDATE.\n // Search filters by deleted_at IS NULL, so tombstoned captures are excluded.\n // Do NOT manually FTS-delete — the trigger already handled it, and a manual\n // delete with empty strings after the trigger re-inserted the entry corrupts\n // the FTS index (SQLITE_CORRUPT_VTAB).\n const now = Date.now();\n let captureCount = 0;\n let atomCount = 0;\n const tx = this.db.transaction(() => {\n captureCount = this.db\n .prepare(\"UPDATE captures SET deleted_at = ? WHERE id = ? AND deleted_at IS NULL\")\n .run(now, id).changes;\n if (captureCount > 0) {\n this.db.prepare(\"DELETE FROM captures_vec WHERE id = ?\").run(id);\n atomCount = this.db.prepare(\"DELETE FROM atoms WHERE capture_id = ?\").run(id).changes;\n }\n });\n tx();\n if (captureCount > 0) {\n return { captures: captureCount, atoms: atomCount, scenarios: 0 };\n }\n\n return {\n captures: captureCount,\n atoms: 0,\n scenarios: 0,\n };\n }\n\n async deleteByFilter(filter: DeleteFilter): Promise<DeleteResult> {\n let sql = \"SELECT id FROM captures WHERE deleted_at IS NULL\";\n const params: unknown[] = [];\n\n if (filter.type) {\n sql += \" AND type = ?\";\n params.push(filter.type);\n }\n if (filter.dateBefore) {\n sql += \" AND created_at < ?\";\n params.push(new Date(filter.dateBefore).getTime());\n }\n if (filter.teamId) {\n sql += \" AND team_id = ?\";\n params.push(filter.teamId);\n }\n if (filter.userId) {\n sql += \" AND user_id = ?\";\n params.push(filter.userId);\n }\n if (filter.taskId) {\n sql += \" AND task_id = ?\";\n params.push(filter.taskId);\n }\n if (filter.sessionKey) {\n sql += \" AND session_key = ?\";\n params.push(filter.sessionKey);\n }\n if (filter.tags && filter.tags.length > 0) {\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n const tagConditions = filter.tags.map(() => \"tags LIKE ? ESCAPE '\\\\'\").join(\" OR \");\n sql += ` AND (${tagConditions})`;\n params.push(...filter.tags.map((t) => `%\"${escapeLike(t)}\"%`));\n }\n\n const ids = this.db.prepare(sql).all(...params) as { id: string }[];\n\n let captures = 0;\n let atoms = 0;\n const scenarios = 0;\n // Wrap in transaction so new captures matching the filter can't be\n // inserted between SELECT and delete (atomic snapshot).\n const softDelete = this.db.prepare(\n \"UPDATE captures SET deleted_at = ? WHERE id = ? AND deleted_at IS NULL\",\n );\n const deleteVec = this.db.prepare(\"DELETE FROM captures_vec WHERE id = ?\");\n const deleteAtoms = this.db.prepare(\"DELETE FROM atoms WHERE capture_id = ?\");\n const now = Date.now();\n const tx = this.db.transaction(() => {\n for (const { id } of ids) {\n const c = softDelete.run(now, id).changes;\n if (c > 0) {\n captures += c;\n deleteVec.run(id);\n atoms += deleteAtoms.run(id).changes;\n }\n }\n });\n tx();\n\n return { captures, atoms, scenarios };\n }\n\n async reject(id: string, reason: string): Promise<DeleteResult> {\n // Same as delete(): the captures_au trigger syncs FTS on UPDATE.\n // Search filters by trust_state != 'rejected' AND deleted_at IS NULL.\n // Manual FTS delete after trigger re-insert corrupts the index.\n const now = Date.now();\n let captureCount = 0;\n let atomCount = 0;\n const tx = this.db.transaction(() => {\n captureCount = this.db\n .prepare(\n \"UPDATE captures SET trust_state = 'rejected', rejection_reason = ?, deleted_at = ? WHERE id = ? AND deleted_at IS NULL AND trust_state != 'rejected'\",\n )\n .run(reason, now, id).changes;\n if (captureCount > 0) {\n this.db.prepare(\"DELETE FROM captures_vec WHERE id = ?\").run(id);\n atomCount = this.db.prepare(\"DELETE FROM atoms WHERE capture_id = ?\").run(id).changes;\n }\n });\n tx();\n if (captureCount > 0) {\n return { captures: captureCount, atoms: atomCount, scenarios: 0 };\n }\n\n return { captures: captureCount, atoms: 0, scenarios: 0 };\n }\n\n async findConflicts(\n embedding: number[],\n sessionKey: string,\n threshold: number,\n ): Promise<ConflictResult[]> {\n const buffer = new Float32Array(embedding);\n const rows = this.db\n .prepare(\n `SELECT vec.id as id, vec.distance as distance, c.content as content, c.trust_state as trust_state\n FROM captures_vec vec\n JOIN captures c ON c.id = vec.id\n WHERE vec.embedding MATCH ? AND vec.k = 20\n AND c.deleted_at IS NULL\n AND c.trust_state IN ('candidate', 'verified')\n AND c.session_key = ?\n ORDER BY vec.distance\n LIMIT 10`,\n )\n .all(Buffer.from(buffer.buffer), sessionKey) as {\n id: string;\n distance: number;\n content: string;\n trust_state: string;\n }[];\n\n return rows\n .filter((r) => {\n // sqlite-vec returns L2 (euclidean) distance for float[] columns.\n // Convert to cosine distance: cosine_dist = L2^2 / 2 (for normalized vectors).\n // Filter by cosine distance threshold.\n const cosineDist = (r.distance * r.distance) / 2;\n return cosineDist < threshold;\n })\n .map((r) => ({\n id: r.id,\n content: r.content,\n // Return cosine distance (not L2) so the caller gets a meaningful value.\n distance: (r.distance * r.distance) / 2,\n trustState: r.trust_state as TrustState,\n }));\n }\n\n async supersede(loserId: string, winnerId: string): Promise<ResolveResult> {\n const updated = this.db\n .prepare(\n \"UPDATE captures SET trust_state = 'stale', superseded_by = ? WHERE id = ? AND deleted_at IS NULL AND trust_state != 'rejected'\",\n )\n .run(winnerId, loserId).changes;\n if (updated > 0) {\n // Remove loser's vector so dead embeddings don't accumulate in captures_vec\n this.db.prepare(\"DELETE FROM captures_vec WHERE id = ?\").run(loserId);\n }\n return { winnerId, loserId, updated };\n }\n\n async setTrustState(id: string, state: TrustState): Promise<number> {\n return this.db\n .prepare(\"UPDATE captures SET trust_state = ? WHERE id = ? AND deleted_at IS NULL\")\n .run(state, id).changes;\n }\n\n // ─── L1 atoms ───────────────────────────────────────────────\n\n async putAtom(atom: AtomEntry): Promise<void> {\n this.db\n .prepare(\n \"INSERT INTO atoms (id, capture_id, fact, confidence, created_at, team_id, agent_id, user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)\",\n )\n .run(\n atom.id,\n atom.captureId,\n atom.fact,\n atom.confidence,\n atom.createdAt,\n atom.teamId ?? null,\n atom.agentId ?? null,\n atom.userId ?? null,\n );\n }\n\n async listAtoms(opts: {\n teamId?: string;\n agentId?: string;\n userId?: string;\n captureId?: string;\n limit?: number;\n offset?: number;\n }): Promise<AtomEntry[]> {\n let sql = \"SELECT * FROM atoms WHERE 1=1\";\n const params: unknown[] = [];\n if (opts.teamId) {\n sql += \" AND team_id = ?\";\n params.push(opts.teamId);\n }\n if (opts.agentId) {\n sql += \" AND agent_id = ?\";\n params.push(opts.agentId);\n }\n if (opts.userId) {\n sql += \" AND user_id = ?\";\n params.push(opts.userId);\n }\n if (opts.captureId) {\n sql += \" AND capture_id = ?\";\n params.push(opts.captureId);\n }\n sql += \" ORDER BY created_at DESC LIMIT ? OFFSET ?\";\n params.push(opts.limit ?? 20, opts.offset ?? 0);\n const rows = this.db.prepare(sql).all(...params) as AtomDbRow[];\n return rows.map((r) => ({\n id: r.id,\n captureId: r.capture_id,\n fact: r.fact,\n confidence: r.confidence,\n createdAt: r.created_at,\n teamId: r.team_id ?? undefined,\n agentId: r.agent_id ?? undefined,\n userId: r.user_id ?? undefined,\n }));\n }\n\n async searchAtoms(\n query: string,\n opts: { teamId?: string; agentId?: string; userId?: string; limit?: number } = {},\n ): Promise<AtomEntry[]> {\n // Atoms don't have FTS — use LIKE for keyword search\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n let sql = \"SELECT * FROM atoms WHERE fact LIKE ? ESCAPE '\\\\'\";\n const params: unknown[] = [`%${escapeLike(query)}%`];\n if (opts.teamId) {\n sql += \" AND team_id = ?\";\n params.push(opts.teamId);\n }\n if (opts.agentId) {\n sql += \" AND agent_id = ?\";\n params.push(opts.agentId);\n }\n if (opts.userId) {\n sql += \" AND user_id = ?\";\n params.push(opts.userId);\n }\n sql += \" ORDER BY created_at DESC LIMIT ?\";\n params.push(opts.limit ?? 20);\n const rows = this.db.prepare(sql).all(...params) as AtomDbRow[];\n return rows.map((r) => ({\n id: r.id,\n captureId: r.capture_id,\n fact: r.fact,\n confidence: r.confidence,\n createdAt: r.created_at,\n teamId: r.team_id ?? undefined,\n agentId: r.agent_id ?? undefined,\n userId: r.user_id ?? undefined,\n }));\n }\n\n // ─── L2 scenarios ───────────────────────────────────────────\n\n async putScenario(scenario: ScenarioEntry): Promise<void> {\n this.db\n .prepare(\n \"INSERT INTO scenarios (id, atom_ids, summary, persona_tags, created_at, team_id, agent_id, user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)\",\n )\n .run(\n scenario.id,\n JSON.stringify(scenario.atomIds),\n scenario.summary,\n scenario.personaTags ? JSON.stringify(scenario.personaTags) : null,\n scenario.createdAt,\n scenario.teamId ?? null,\n scenario.agentId ?? null,\n scenario.userId ?? null,\n );\n }\n\n async listScenarios(opts: {\n teamId?: string;\n agentId?: string;\n userId?: string;\n limit?: number;\n offset?: number;\n }): Promise<ScenarioEntry[]> {\n let sql = \"SELECT * FROM scenarios WHERE 1=1\";\n const params: unknown[] = [];\n if (opts.teamId) {\n sql += \" AND team_id = ?\";\n params.push(opts.teamId);\n }\n if (opts.agentId) {\n sql += \" AND agent_id = ?\";\n params.push(opts.agentId);\n }\n if (opts.userId) {\n sql += \" AND user_id = ?\";\n params.push(opts.userId);\n }\n sql += \" ORDER BY created_at DESC LIMIT ? OFFSET ?\";\n params.push(opts.limit ?? 20, opts.offset ?? 0);\n const rows = this.db.prepare(sql).all(...params) as ScenarioDbRow[];\n return rows.map((r) => ({\n id: r.id,\n atomIds: JSON.parse(r.atom_ids) as string[],\n summary: r.summary,\n personaTags: r.persona_tags ? (JSON.parse(r.persona_tags) as string[]) : undefined,\n createdAt: r.created_at,\n teamId: r.team_id ?? undefined,\n agentId: r.agent_id ?? undefined,\n userId: r.user_id ?? undefined,\n }));\n }\n\n async getScenario(id: string): Promise<ScenarioEntry | null> {\n const row = this.db.prepare(\"SELECT * FROM scenarios WHERE id = ?\").get(id) as\n | ScenarioDbRow\n | undefined;\n if (!row) return null;\n return {\n id: row.id,\n atomIds: JSON.parse(row.atom_ids) as string[],\n summary: row.summary,\n personaTags: row.persona_tags ? (JSON.parse(row.persona_tags) as string[]) : undefined,\n createdAt: row.created_at,\n teamId: row.team_id ?? undefined,\n agentId: row.agent_id ?? undefined,\n userId: row.user_id ?? undefined,\n };\n }\n\n // ─── L3 persona ─────────────────────────────────────────────\n\n async readPersona(teamId: string, agentId: string, userId: string): Promise<PersonaEntry | null> {\n const row = this.db\n .prepare(\"SELECT * FROM persona WHERE team_id = ? AND agent_id = ? AND user_id = ?\")\n .get(teamId, agentId, userId) as PersonaDbRow | undefined;\n if (!row) return null;\n return {\n teamId: row.team_id,\n agentId: row.agent_id,\n userId: row.user_id,\n content: row.content,\n updatedAt: row.updated_at,\n };\n }\n\n async writePersona(\n teamId: string,\n agentId: string,\n userId: string,\n content: string,\n ): Promise<void> {\n this.db\n .prepare(\n `INSERT INTO persona (team_id, agent_id, user_id, content, updated_at)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(team_id, agent_id, user_id) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at`,\n )\n .run(teamId, agentId, userId, content, Date.now());\n }\n\n // ─── Knowledge ──────────────────────────────────────────────\n\n async putKnowledge(entry: KnowledgeEntry): Promise<void> {\n this.db\n .prepare(\n \"INSERT INTO knowledge (id, team_id, name, type, summary, service_url, repo_url, branch, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\",\n )\n .run(\n entry.id,\n entry.teamId,\n entry.name,\n entry.type,\n entry.summary ?? null,\n entry.serviceUrl ?? null,\n entry.repoUrl ?? null,\n entry.branch ?? null,\n entry.createdAt,\n );\n }\n\n async getKnowledge(id: string): Promise<KnowledgeEntry | null> {\n const row = this.db.prepare(\"SELECT * FROM knowledge WHERE id = ?\").get(id) as\n | KnowledgeDbRow\n | undefined;\n if (!row) return null;\n return knowledgeRowToEntry(row);\n }\n\n async listKnowledge(teamId: string, type?: string): Promise<KnowledgeEntry[]> {\n let sql = \"SELECT * FROM knowledge WHERE team_id = ?\";\n const params: unknown[] = [teamId];\n if (type) {\n sql += \" AND type = ?\";\n params.push(type);\n }\n sql += \" ORDER BY created_at DESC\";\n const rows = this.db.prepare(sql).all(...params) as KnowledgeDbRow[];\n return rows.map(knowledgeRowToEntry);\n }\n\n async deleteKnowledge(ids: string[]): Promise<number> {\n if (ids.length === 0) return 0;\n const placeholders = ids.map(() => \"?\").join(\",\");\n const result = this.db\n .prepare(`DELETE FROM knowledge WHERE id IN (${placeholders})`)\n .run(...ids);\n return result.changes;\n }\n\n // ─── Skills ─────────────────────────────────────────────────\n\n async putSkill(entry: SkillEntry): Promise<void> {\n this.db\n .prepare(\n `INSERT INTO skills (id, team_id, agent_id, name, description, content, version, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET name = excluded.name, description = excluded.description, content = excluded.content, version = excluded.version, updated_at = excluded.updated_at`,\n )\n .run(\n entry.id,\n entry.teamId,\n entry.agentId ?? null,\n entry.name,\n entry.description ?? null,\n entry.content ?? null,\n entry.version,\n entry.createdAt,\n entry.updatedAt,\n );\n }\n\n async getSkill(id: string): Promise<SkillEntry | null> {\n const row = this.db.prepare(\"SELECT * FROM skills WHERE id = ?\").get(id) as\n | SkillDbRow\n | undefined;\n if (!row) return null;\n return skillRowToEntry(row);\n }\n\n async listSkills(teamId: string, agentId?: string): Promise<SkillEntry[]> {\n let sql = \"SELECT * FROM skills WHERE team_id = ?\";\n const params: unknown[] = [teamId];\n if (agentId) {\n sql += \" AND (agent_id = ? OR agent_id IS NULL)\";\n params.push(agentId);\n }\n sql += \" ORDER BY updated_at DESC\";\n const rows = this.db.prepare(sql).all(...params) as SkillDbRow[];\n return rows.map(skillRowToEntry);\n }\n\n async searchSkills(\n teamId: string,\n agentId: string,\n query: string,\n topK?: number,\n ): Promise<SkillEntry[]> {\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n let sql =\n \"SELECT * FROM skills WHERE team_id = ? AND (agent_id = ? OR agent_id IS NULL) AND (name LIKE ? ESCAPE '\\\\' OR description LIKE ? ESCAPE '\\\\')\";\n const params: unknown[] = [teamId, agentId, `%${escapeLike(query)}%`, `%${escapeLike(query)}%`];\n sql += \" ORDER BY updated_at DESC LIMIT ?\";\n params.push(topK ?? 10);\n const rows = this.db.prepare(sql).all(...params) as SkillDbRow[];\n return rows.map(skillRowToEntry);\n }\n\n close(): void {\n this.db.close();\n }\n}\n\n// ─── Database row types ────────────────────────────────────────\n\ninterface DbRow {\n id: string;\n session_key: string;\n agent_id: string;\n type: string;\n content: string;\n content_hash: string | null;\n tags: string | null;\n created_at: number;\n metadata: string | null;\n team_id: string | null;\n user_id: string | null;\n task_id: string | null;\n deleted_at: number | null;\n trust_state: string | null;\n rejection_reason: string | null;\n superseded_by: string | null;\n}\n\ninterface MessageDbRow {\n id: string;\n capture_id: string;\n role: string;\n content: string;\n seq: number;\n created_at: number;\n}\n\ninterface AtomDbRow {\n id: string;\n capture_id: string;\n fact: string;\n confidence: number;\n created_at: number;\n team_id: string | null;\n agent_id: string | null;\n user_id: string | null;\n}\n\ninterface ScenarioDbRow {\n id: string;\n atom_ids: string;\n summary: string;\n persona_tags: string | null;\n created_at: number;\n team_id: string | null;\n agent_id: string | null;\n user_id: string | null;\n}\n\ninterface PersonaDbRow {\n team_id: string;\n agent_id: string;\n user_id: string;\n content: string;\n updated_at: number;\n}\n\ninterface KnowledgeDbRow {\n id: string;\n team_id: string;\n name: string;\n type: string;\n summary: string | null;\n service_url: string | null;\n repo_url: string | null;\n branch: string | null;\n created_at: number;\n}\n\ninterface SkillDbRow {\n id: string;\n team_id: string;\n agent_id: string | null;\n name: string;\n description: string | null;\n content: string | null;\n version: number;\n created_at: number;\n updated_at: number;\n}\n\n// ─── Row → Entry converters ────────────────────────────────────\n\n/** Convert a database row to a CaptureEntry. */\nfunction rowToEntry(row: DbRow): CaptureEntry {\n return {\n id: row.id,\n sessionKey: row.session_key,\n agentId: row.agent_id,\n type: row.type as CaptureEntry[\"type\"],\n content: row.content,\n tags: row.tags ? JSON.parse(row.tags) : [],\n createdAt: row.created_at,\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n teamId: row.team_id ?? undefined,\n userId: row.user_id ?? undefined,\n taskId: row.task_id ?? undefined,\n trustState: (row.trust_state as TrustState) ?? \"candidate\",\n rejectionReason: row.rejection_reason ?? undefined,\n supersededBy: row.superseded_by ?? undefined,\n };\n}\n\nfunction knowledgeRowToEntry(row: KnowledgeDbRow): KnowledgeEntry {\n return {\n id: row.id,\n teamId: row.team_id,\n name: row.name,\n type: row.type,\n summary: row.summary ?? undefined,\n serviceUrl: row.service_url ?? undefined,\n repoUrl: row.repo_url ?? undefined,\n branch: row.branch ?? undefined,\n createdAt: row.created_at,\n };\n}\n\nfunction skillRowToEntry(row: SkillDbRow): SkillEntry {\n return {\n id: row.id,\n teamId: row.team_id,\n agentId: row.agent_id ?? undefined,\n name: row.name,\n description: row.description ?? undefined,\n content: row.content ?? undefined,\n version: row.version,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n","/**\n * Reciprocal Rank Fusion (RRF) merge with weighted channels.\n * Fuses two ranked result lists into one.\n *\n * Vector search is weighted 1.5x over BM25 because vector similarity\n * is more semantically relevant for conceptual queries, while BM25\n * with OR semantics can introduce noise at scale (1K+ memories).\n *\n * Adapted from TencentDB Agent Memory search-utils pattern (MIT, Tencent 2026).\n * https://github.com/TencentCloud/TencentDB-Agent-Memory\n */\n\n// k=40 works better than the standard k=60 for smaller result sets (<500 items)\n// typical in memory servers. Lower k gives more weight to top-ranked items.\nconst RRF_K = 40;\n\n// Vector search weight: 3x over BM25. Vector similarity is more precise\n// for semantic matching, while BM25 OR semantics can be noisy — especially\n// when a proper noun in the query (e.g. \"Tin\") matches an unrelated capture\n// that happens to mention that name. Higher vector weight ensures semantic\n// relevance dominates over keyword coincidence.\nconst VEC_WEIGHT = 3.0;\n\nexport interface RankedResult {\n id: string;\n score: number;\n}\n\nexport interface FusedResult {\n id: string;\n score: number;\n}\n\n/**\n * Merge two ranked result lists with weighted RRF.\n * @param bm25Results Results from BM25 (FTS5). Lower score is better.\n * @param vecResults Results from vector search. Lower distance is better.\n * @param limit Maximum number of results to return.\n * @returns Fused results, sorted by RRF score (highest first).\n */\nexport function rrfMerge(\n bm25Results: RankedResult[],\n vecResults: RankedResult[],\n limit: number,\n): FusedResult[] {\n const scores = new Map<string, number>();\n\n // BM25 results: lower score is better, so rank 1 = lowest score\n const sortedBm25 = [...bm25Results].sort((a, b) => a.score - b.score);\n sortedBm25.forEach((r, i) => {\n const rank = i + 1;\n const rrfScore = 1 / (RRF_K + rank);\n scores.set(r.id, (scores.get(r.id) ?? 0) + rrfScore);\n });\n\n // Vector results: lower distance is better, so rank 1 = lowest distance\n // Weighted higher because vector similarity is more semantically precise.\n const sortedVec = [...vecResults].sort((a, b) => a.score - b.score);\n sortedVec.forEach((r, i) => {\n const rank = i + 1;\n const rrfScore = (1 / (RRF_K + rank)) * VEC_WEIGHT;\n scores.set(r.id, (scores.get(r.id) ?? 0) + rrfScore);\n });\n\n return [...scores.entries()]\n .map(([id, score]) => ({ id, score }))\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n}\n","/**\n * Wiki engine: parse markdown docs into structured pages with a link graph.\n *\n * Extracts frontmatter, headings, [[wikilinks]], and [text](url) links.\n * Builds an adjacency list for the link graph.\n */\nimport { createHash } from \"node:crypto\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, extname, join, relative, sep } from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport { generateId } from \"../utils/ulid.js\";\n\n/** A parsed wiki page. */\nexport interface WikiPage {\n id: string;\n title: string;\n content: string;\n sourceFile: string;\n section: string | null;\n tags: string | null;\n frontmatter: string | null;\n contentHash: string;\n}\n\n/** A link between wiki pages. */\nexport interface WikiLink {\n fromPageId: string;\n toPageId: string | null;\n toTitle: string;\n linkText: string;\n linkType: string;\n line: number;\n}\n\n/** Result of ingesting a file. */\nexport interface IngestResult {\n file: string;\n pages: number;\n links: number;\n skipped: boolean;\n reason?: string;\n}\n\n/** Parse frontmatter (YAML-like) from markdown content. */\nfunction parseFrontmatter(content: string): { frontmatter: string | null; body: string } {\n if (!content.startsWith(\"---\")) {\n return { frontmatter: null, body: content };\n }\n const end = content.indexOf(\"\\n---\", 3);\n if (end === -1) {\n return { frontmatter: null, body: content };\n }\n const frontmatter = content.slice(3, end).trim();\n const body = content.slice(end + 4).replace(/^\\n/, \"\");\n return { frontmatter, body };\n}\n\n/** Extract tags from frontmatter (simple key: value parsing). */\nfunction extractTags(frontmatter: string | null): string | null {\n if (!frontmatter) return null;\n const match = frontmatter.match(/^tags:\\s*(.+)$/m);\n if (!match) return null;\n return match[1].replace(/[[\\]]/g, \"\").trim();\n}\n\n/** Extract title from frontmatter or first heading. */\nfunction extractTitle(frontmatter: string | null, body: string, fileName: string): string {\n if (frontmatter) {\n const titleMatch = frontmatter.match(/^title:\\s*(.+)$/m);\n if (titleMatch) return titleMatch[1].replace(/[\"']/g, \"\").trim();\n }\n // First H1 heading\n const h1Match = body.match(/^#\\s+(.+)$/m);\n if (h1Match) return h1Match[1].trim();\n // Fallback: file name without extension\n return basename(fileName, extname(fileName));\n}\n\n/** Extract [[wikilinks]] from content. Returns array of { target, text, line }. */\nfunction extractWikilinks(content: string): Array<{ target: string; text: string; line: number }> {\n const links: Array<{ target: string; text: string; line: number }> = [];\n const lines = content.split(\"\\n\");\n const regex = /\\[\\[([^\\]|]+)(?:\\|([^\\]]+))?\\]\\]/g;\n for (let i = 0; i < lines.length; i++) {\n let match: RegExpExecArray | null;\n regex.lastIndex = 0;\n match = regex.exec(lines[i]);\n while (match !== null) {\n links.push({\n target: match[1].trim(),\n text: (match[2] ?? match[1]).trim(),\n line: i + 1,\n });\n match = regex.exec(lines[i]);\n }\n }\n return links;\n}\n\n/** Extract [text](url) markdown links from content. */\nfunction extractMarkdownLinks(\n content: string,\n): Array<{ target: string; text: string; line: number }> {\n const links: Array<{ target: string; text: string; line: number }> = [];\n const lines = content.split(\"\\n\");\n // Match [text](path) but not  (images)\n const regex = /(?<!!)\\[([^\\]]+)\\]\\(([^)]+)\\)/g;\n for (let i = 0; i < lines.length; i++) {\n let match: RegExpExecArray | null;\n regex.lastIndex = 0;\n match = regex.exec(lines[i]);\n while (match !== null) {\n const target = match[2].trim();\n // Only include local .md links (not http URLs)\n if (!target.startsWith(\"http\") && !target.startsWith(\"mailto:\")) {\n links.push({\n target: target.replace(/\\.md$/i, \"\"),\n text: match[1].trim(),\n line: i + 1,\n });\n }\n match = regex.exec(lines[i]);\n }\n }\n return links;\n}\n\n/** Parse a markdown file into a wiki page with links. */\nfunction parseMarkdownFile(\n filePath: string,\n repoPath: string,\n): {\n page: WikiPage;\n links: Array<{ target: string; text: string; line: number; type: string }>;\n} | null {\n const ext = extname(filePath).toLowerCase();\n if (ext !== \".md\" && ext !== \".markdown\") return null;\n\n let source: string;\n try {\n source = readFileSync(filePath, \"utf-8\");\n } catch {\n return null;\n }\n\n const relPath = relative(repoPath, filePath).split(sep).join(\"/\");\n const { frontmatter, body } = parseFrontmatter(source);\n const title = extractTitle(frontmatter, body, filePath);\n const tags = extractTags(frontmatter);\n const contentHash = createHash(\"sha256\").update(source).digest(\"hex\");\n\n const page: WikiPage = {\n id: generateId(),\n title,\n content: body,\n sourceFile: relPath,\n section: null,\n tags,\n frontmatter,\n contentHash,\n };\n\n const wikilinks = extractWikilinks(body).map((l) => ({ ...l, type: \"wikilink\" }));\n const mdlinks = extractMarkdownLinks(body).map((l) => ({ ...l, type: \"markdown\" }));\n const links = [...wikilinks, ...mdlinks];\n\n return { page, links };\n}\n\n/** Ingest a single markdown file into the database. */\nexport function ingestFile(\n db: Database,\n filePath: string,\n repoPath: string,\n teamId: string | null,\n): IngestResult {\n const ext = extname(filePath).toLowerCase();\n if (ext !== \".md\" && ext !== \".markdown\") {\n return { file: filePath, pages: 0, links: 0, skipped: true, reason: \"not markdown\" };\n }\n\n const parsed = parseMarkdownFile(filePath, repoPath);\n if (!parsed) {\n return { file: filePath, pages: 0, links: 0, skipped: true, reason: \"parse failed\" };\n }\n\n const relPath = relative(repoPath, filePath).split(sep).join(\"/\");\n const now = Date.now();\n\n // Check if page already exists (by source_file)\n const existing = db\n .prepare(\"SELECT id FROM wiki_pages WHERE source_file = ? AND team_id IS ?\")\n .get(relPath, teamId) as { id: string } | undefined;\n\n let pageId = parsed.page.id;\n if (existing) {\n // Update existing page\n pageId = existing.id;\n db.prepare(\n \"UPDATE wiki_pages SET title = ?, content = ?, tags = ?, frontmatter = ?, content_hash = ?, updated_at = ? WHERE id = ?\",\n ).run(\n parsed.page.title,\n parsed.page.content,\n parsed.page.tags,\n parsed.page.frontmatter,\n parsed.page.contentHash,\n now,\n pageId,\n );\n // Delete old links\n db.prepare(\"DELETE FROM wiki_links WHERE from_page_id = ?\").run(pageId);\n } else {\n // Insert new page\n db.prepare(\n `INSERT INTO wiki_pages (id, title, content, source_file, section, tags, frontmatter, content_hash, team_id, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n parsed.page.id,\n parsed.page.title,\n parsed.page.content,\n parsed.page.sourceFile,\n parsed.page.section,\n parsed.page.tags,\n parsed.page.frontmatter,\n parsed.page.contentHash,\n teamId,\n now,\n now,\n );\n }\n\n // Insert links (resolve targets later)\n const linkStmt = db.prepare(\n \"INSERT INTO wiki_links (from_page_id, to_page_id, to_title, link_text, link_type, line) VALUES (?, ?, ?, ?, ?, ?)\",\n );\n for (const link of parsed.links) {\n // Try to resolve target by title\n const target = db\n .prepare(\"SELECT id FROM wiki_pages WHERE title = ? AND team_id IS ? LIMIT 1\")\n .get(link.target, teamId) as { id: string } | undefined;\n linkStmt.run(pageId, target?.id ?? null, link.target, link.text, link.type, link.line);\n }\n\n return {\n file: relPath,\n pages: 1,\n links: parsed.links.length,\n skipped: false,\n };\n}\n\n/** Ingest a directory of markdown files. */\nexport function ingestDirectory(\n db: Database,\n dirPath: string,\n repoPath: string,\n teamId: string | null,\n maxFiles = 200,\n): IngestResult[] {\n const results: IngestResult[] = [];\n const files: string[] = [];\n\n const walk = (dir: string) => {\n if (files.length >= maxFiles) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const entry of entries) {\n if (files.length >= maxFiles) return;\n const fullPath = join(dir, entry);\n let stat: ReturnType<typeof statSync>;\n try {\n stat = statSync(fullPath);\n } catch {\n continue;\n }\n if (stat.isDirectory()) {\n if (entry.startsWith(\".\") || entry === \"node_modules\" || entry === \"vendor\") continue;\n walk(fullPath);\n } else if (stat.isFile()) {\n const ext = extname(fullPath).toLowerCase();\n if (ext === \".md\" || ext === \".markdown\") {\n files.push(fullPath);\n }\n }\n }\n };\n\n walk(dirPath);\n\n for (const file of files) {\n const result = ingestFile(db, file, repoPath, teamId);\n results.push(result);\n }\n\n // Re-resolve links now that all pages are indexed\n const unresolved = db\n .prepare(\"SELECT id, to_title FROM wiki_links WHERE to_page_id IS NULL\")\n .all() as { id: number; to_title: string }[];\n if (unresolved.length > 0) {\n const updateStmt = db.prepare(\"UPDATE wiki_links SET to_page_id = ? WHERE id = ?\");\n for (const u of unresolved) {\n const target = db\n .prepare(\"SELECT id FROM wiki_pages WHERE title = ? AND team_id IS ? LIMIT 1\")\n .get(u.to_title, teamId) as { id: string } | undefined;\n if (target) {\n updateStmt.run(target.id, u.id);\n }\n }\n }\n\n return results;\n}\n\n/** Search wiki pages using FTS5. */\nexport function searchWiki(\n db: Database,\n query: string,\n opts: { teamId?: string; limit?: number } = {},\n): Array<{ id: string; title: string; sourceFile: string; snippet: string }> {\n const limit = opts.limit ?? 10;\n let sql = `\n SELECT w.id, w.title, w.source_file, snippet(wiki_fts, 1, '<b>', '</b>', '...', 20) as snippet\n FROM wiki_fts f\n JOIN wiki_pages w ON w.id = f.id\n WHERE wiki_fts MATCH ?\n `;\n const params: unknown[] = [query];\n if (opts.teamId !== undefined) {\n sql += \" AND w.team_id IS ?\";\n params.push(opts.teamId);\n }\n sql += \" LIMIT ?\";\n params.push(limit);\n\n const rows = db.prepare(sql).all(...params) as Array<{\n id: string;\n title: string;\n source_file: string;\n snippet: string;\n }>;\n\n return rows.map((r) => ({\n id: r.id,\n title: r.title,\n sourceFile: r.source_file,\n snippet: r.snippet,\n }));\n}\n\n/** Get a wiki page by ID, including linked pages. */\nexport function getWikiPage(\n db: Database,\n pageId: string,\n): { page: WikiPage; links: WikiLink[]; backlinks: WikiLink[] } | null {\n const row = db.prepare(\"SELECT * FROM wiki_pages WHERE id = ?\").get(pageId) as\n | {\n id: string;\n title: string;\n content: string;\n source_file: string;\n section: string | null;\n tags: string | null;\n frontmatter: string | null;\n content_hash: string;\n }\n | undefined;\n\n if (!row) return null;\n\n const page: WikiPage = {\n id: row.id,\n title: row.title,\n content: row.content,\n sourceFile: row.source_file,\n section: row.section,\n tags: row.tags,\n frontmatter: row.frontmatter,\n contentHash: row.content_hash,\n };\n\n const linkRows = db\n .prepare(\"SELECT * FROM wiki_links WHERE from_page_id = ? ORDER BY line\")\n .all(pageId) as Array<{\n from_page_id: string;\n to_page_id: string | null;\n to_title: string;\n link_text: string;\n link_type: string;\n line: number;\n }>;\n\n const links: WikiLink[] = linkRows.map((r) => ({\n fromPageId: r.from_page_id,\n toPageId: r.to_page_id,\n toTitle: r.to_title,\n linkText: r.link_text,\n linkType: r.link_type,\n line: r.line,\n }));\n\n const backlinkRows = db\n .prepare(\"SELECT * FROM wiki_links WHERE to_page_id = ? ORDER BY line\")\n .all(pageId) as Array<{\n from_page_id: string;\n to_page_id: string | null;\n to_title: string;\n link_text: string;\n link_type: string;\n line: number;\n }>;\n\n const backlinks: WikiLink[] = backlinkRows.map((r) => ({\n fromPageId: r.from_page_id,\n toPageId: r.to_page_id,\n toTitle: r.to_title,\n linkText: r.link_text,\n linkType: r.link_type,\n line: r.line,\n }));\n\n return { page, links, backlinks };\n}\n\n/** Find pages whose source file has changed (content_hash mismatch). */\nexport function findOutdatedPages(\n db: Database,\n repoPath: string,\n opts: { teamId?: string } = {},\n): Array<{ id: string; title: string; sourceFile: string; reason: string }> {\n const pages = db\n .prepare(\"SELECT id, title, source_file, content_hash FROM wiki_pages WHERE team_id IS ?\")\n .all(opts.teamId ?? null) as Array<{\n id: string;\n title: string;\n source_file: string;\n content_hash: string;\n }>;\n\n const outdated: Array<{ id: string; title: string; sourceFile: string; reason: string }> = [];\n\n for (const page of pages) {\n const fullPath = join(repoPath, page.source_file);\n if (!existsSync(fullPath)) {\n outdated.push({\n id: page.id,\n title: page.title,\n sourceFile: page.source_file,\n reason: \"file deleted\",\n });\n continue;\n }\n try {\n const content = readFileSync(fullPath, \"utf-8\");\n const hash = createHash(\"sha256\").update(content).digest(\"hex\");\n if (hash !== page.content_hash) {\n outdated.push({\n id: page.id,\n title: page.title,\n sourceFile: page.source_file,\n reason: \"content changed\",\n });\n }\n } catch {\n outdated.push({\n id: page.id,\n title: page.title,\n sourceFile: page.source_file,\n reason: \"read error\",\n });\n }\n }\n\n return outdated;\n}\n"],"mappings":"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","names":["createHash","join","__dirname","fused","paged","createHash","existsSync","readFileSync","join","readFileSync","createHash","join","existsSync","join","createHash"]}
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
redact,
|
|
14
14
|
searchWiki,
|
|
15
15
|
stripQueryProperNouns
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-WBGP7QM3.js";
|
|
17
17
|
import {
|
|
18
18
|
__dirname,
|
|
19
19
|
__toCommonJS,
|
|
@@ -8143,7 +8143,7 @@ function createServer(opts) {
|
|
|
8143
8143
|
tags: r.tags ? JSON.parse(r.tags) : [],
|
|
8144
8144
|
createdAt: r.created_at,
|
|
8145
8145
|
trustState: r.trust_state ?? "candidate",
|
|
8146
|
-
supersededBy: r.superseded_by,
|
|
8146
|
+
supersededBy: r.superseded_by ?? void 0,
|
|
8147
8147
|
teamId: r.team_id,
|
|
8148
8148
|
userId: r.user_id,
|
|
8149
8149
|
taskId: r.task_id,
|
|
@@ -8307,11 +8307,11 @@ async function handleRecall(args, opts) {
|
|
|
8307
8307
|
}
|
|
8308
8308
|
}
|
|
8309
8309
|
const globalKey = globalSessionKey();
|
|
8310
|
-
const useGlobalFallback = globalKey && !args.session_key && globalKey !== sessionKey;
|
|
8310
|
+
const useGlobalFallback = !!globalKey && !args.session_key && globalKey !== sessionKey;
|
|
8311
8311
|
let results;
|
|
8312
8312
|
if (useGlobalFallback) {
|
|
8313
8313
|
const reservedGlobal = Math.min(3, limit);
|
|
8314
|
-
const projectLimit = limit - reservedGlobal;
|
|
8314
|
+
const projectLimit = Math.max(1, limit - reservedGlobal);
|
|
8315
8315
|
const projectResults = await opts.storage.search(query, queryEmbedding, {
|
|
8316
8316
|
sessionKey,
|
|
8317
8317
|
limit: projectLimit,
|
|
@@ -8679,7 +8679,7 @@ async function handleSearch(args, opts) {
|
|
|
8679
8679
|
}
|
|
8680
8680
|
const sessionKey = args.session_key ?? defaultSessionKey();
|
|
8681
8681
|
const globalKey = globalSessionKey();
|
|
8682
|
-
const useGlobalFallback = globalKey && !args.session_key && globalKey !== sessionKey;
|
|
8682
|
+
const useGlobalFallback = !!globalKey && !args.session_key && globalKey !== sessionKey;
|
|
8683
8683
|
const searchFilters = filters ? {
|
|
8684
8684
|
type: filters.type,
|
|
8685
8685
|
tags: filters.tags,
|
|
@@ -8693,7 +8693,7 @@ async function handleSearch(args, opts) {
|
|
|
8693
8693
|
let results;
|
|
8694
8694
|
if (useGlobalFallback) {
|
|
8695
8695
|
const reservedGlobal = Math.min(3, limit);
|
|
8696
|
-
const projectLimit = limit - reservedGlobal;
|
|
8696
|
+
const projectLimit = Math.max(1, limit - reservedGlobal);
|
|
8697
8697
|
const projectResults = await opts.storage.search(query, queryEmbedding, {
|
|
8698
8698
|
sessionKey,
|
|
8699
8699
|
limit: projectLimit,
|
|
@@ -8921,7 +8921,7 @@ async function handleExplainRecall(args, opts) {
|
|
|
8921
8921
|
const captureId = args.capture_id;
|
|
8922
8922
|
const sessionKey = args.session_key ?? defaultSessionKey();
|
|
8923
8923
|
const globalKey = globalSessionKey();
|
|
8924
|
-
const useGlobalFallback = globalKey && !args.session_key && globalKey !== sessionKey;
|
|
8924
|
+
const useGlobalFallback = !!globalKey && !args.session_key && globalKey !== sessionKey;
|
|
8925
8925
|
const mode = args.mode ?? "hybrid";
|
|
8926
8926
|
const limit = Math.min(args.limit ?? 10, 50);
|
|
8927
8927
|
const { teamId, userId, taskId } = extractTenant(args);
|
|
@@ -8940,7 +8940,7 @@ async function handleExplainRecall(args, opts) {
|
|
|
8940
8940
|
queryEmbedding,
|
|
8941
8941
|
useGlobalFallback,
|
|
8942
8942
|
sessionKey,
|
|
8943
|
-
globalKey,
|
|
8943
|
+
globalKey: globalKey ?? void 0,
|
|
8944
8944
|
teamId,
|
|
8945
8945
|
userId,
|
|
8946
8946
|
taskId
|
|
@@ -9558,10 +9558,18 @@ async function handleSupersede(args, opts) {
|
|
|
9558
9558
|
isError: true
|
|
9559
9559
|
};
|
|
9560
9560
|
}
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9561
|
+
const { updated } = await opts.storage.supersede(oldId, newId);
|
|
9562
|
+
if (updated === 0) {
|
|
9563
|
+
return {
|
|
9564
|
+
content: [
|
|
9565
|
+
{
|
|
9566
|
+
type: "text",
|
|
9567
|
+
text: `Error: Old capture ${oldId} could not be superseded (it may already be rejected).`
|
|
9568
|
+
}
|
|
9569
|
+
],
|
|
9570
|
+
isError: true
|
|
9571
|
+
};
|
|
9572
|
+
}
|
|
9565
9573
|
return {
|
|
9566
9574
|
content: [
|
|
9567
9575
|
{
|
|
@@ -9627,13 +9635,13 @@ async function handleCorrectionKPIs(_args, opts) {
|
|
|
9627
9635
|
async function handleSessionStart(args, opts) {
|
|
9628
9636
|
const sessionKey = args.session_key ?? defaultSessionKey();
|
|
9629
9637
|
const globalKey = globalSessionKey();
|
|
9630
|
-
const useGlobalFallback = globalKey && !args.session_key && globalKey !== sessionKey;
|
|
9638
|
+
const useGlobalFallback = !!globalKey && !args.session_key && globalKey !== sessionKey;
|
|
9631
9639
|
const contextQuery = args.context_query;
|
|
9632
9640
|
const db = getDb(opts);
|
|
9633
9641
|
const sessionKeys = useGlobalFallback ? [sessionKey, globalKey] : [sessionKey];
|
|
9634
9642
|
const placeholders = sessionKeys.map(() => "?").join(",");
|
|
9635
9643
|
const recent = db.prepare(
|
|
9636
|
-
`SELECT id, type, content, tags, created_at FROM captures WHERE deleted_at IS NULL AND session_key IN (${placeholders}) ORDER BY created_at DESC LIMIT 5`
|
|
9644
|
+
`SELECT id, session_key, type, content, tags, created_at FROM captures WHERE deleted_at IS NULL AND session_key IN (${placeholders}) ORDER BY created_at DESC LIMIT 5`
|
|
9637
9645
|
).all(...sessionKeys);
|
|
9638
9646
|
const kpis = opts.storage.getCorrectionKPIs();
|
|
9639
9647
|
let contextResults = [];
|