@remnic/core 9.3.596 → 9.3.598

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.
Files changed (89) hide show
  1. package/dist/access-cli.js +17 -17
  2. package/dist/access-http.js +6 -6
  3. package/dist/access-mcp.js +5 -5
  4. package/dist/access-service.js +4 -4
  5. package/dist/behavior-learner.js +2 -1
  6. package/dist/behavior-learner.js.map +1 -1
  7. package/dist/causal-behavior.js +3 -3
  8. package/dist/causal-chain.js +3 -3
  9. package/dist/causal-consolidation.js +4 -4
  10. package/dist/causal-retrieval.js +3 -3
  11. package/dist/causal-trajectory.js +2 -2
  12. package/dist/{chunk-A2Z6UCWT.js → chunk-33JBK2XP.js} +2 -2
  13. package/dist/{chunk-D2MMMTDV.js → chunk-5SQ5CQJP.js} +2 -2
  14. package/dist/{chunk-F4LM4ULA.js → chunk-65JSA4MP.js} +12 -12
  15. package/dist/{chunk-SKGV326D.js → chunk-6GDHLVJC.js} +2 -2
  16. package/dist/chunk-6HEM6HTQ.js +359 -0
  17. package/dist/chunk-6HEM6HTQ.js.map +1 -0
  18. package/dist/{chunk-WXACKLKP.js → chunk-75O6YQ63.js} +22 -7
  19. package/dist/chunk-75O6YQ63.js.map +1 -0
  20. package/dist/{chunk-D65TSG24.js → chunk-7DZRO2DC.js} +2 -2
  21. package/dist/{chunk-TYICDVQW.js → chunk-BDCCWRHR.js} +4 -4
  22. package/dist/{chunk-LYPDMKUT.js → chunk-CL3MWNNQ.js} +2 -2
  23. package/dist/{chunk-YQMZ7IH2.js → chunk-D4KJ74JJ.js} +65 -27
  24. package/dist/chunk-D4KJ74JJ.js.map +1 -0
  25. package/dist/{chunk-W5O2FQTZ.js → chunk-GUPISBV2.js} +2 -2
  26. package/dist/{chunk-472U7RDF.js → chunk-JGSKJHF7.js} +2 -2
  27. package/dist/{chunk-IEFHBIU2.js → chunk-KDUVQU6Y.js} +14 -14
  28. package/dist/{chunk-6HZ6AO2P.js → chunk-LBJBNWS2.js} +37 -10
  29. package/dist/chunk-LBJBNWS2.js.map +1 -0
  30. package/dist/{chunk-Z4R6RI2N.js → chunk-NSKYFGDL.js} +2 -2
  31. package/dist/{chunk-OD5LFAPZ.js → chunk-V67GWXM2.js} +1 -1
  32. package/dist/{chunk-5NXIJZFX.js → chunk-WR64DQFE.js} +3 -3
  33. package/dist/{chunk-5BUGGPBR.js → chunk-WZA5Y6AC.js} +3 -3
  34. package/dist/chunk-ZBJMUXZH.js +121 -0
  35. package/dist/chunk-ZBJMUXZH.js.map +1 -0
  36. package/dist/{chunk-XPXEJRUB.js → chunk-ZRWB5D4H.js} +2 -2
  37. package/dist/{chunk-MA5MWGKP.js → chunk-ZT3EGNLR.js} +2 -2
  38. package/dist/{chunk-LMPHTYJC.js → chunk-ZZYF3BUL.js} +2 -2
  39. package/dist/cli.js +14 -14
  40. package/dist/compounding/engine.js +1 -1
  41. package/dist/direct-answer-wiring.js +3 -3
  42. package/dist/direct-answer.d.ts +1 -1
  43. package/dist/direct-answer.js +2 -2
  44. package/dist/harmonic-retrieval.js +2 -2
  45. package/dist/index.js +21 -21
  46. package/dist/orchestrator.js +16 -16
  47. package/dist/policy-runtime.js +3 -2
  48. package/dist/recall-query-policy.js +2 -1
  49. package/dist/recall-tokenization.d.ts +5 -1
  50. package/dist/recall-tokenization.js +3 -1
  51. package/dist/resume-bundles.js +3 -3
  52. package/dist/retrieval-agents.js +2 -2
  53. package/dist/semantic-consolidation.js +2 -2
  54. package/dist/semantic-rule-verifier.js +2 -2
  55. package/dist/temporal-index.js +1 -1
  56. package/dist/trust-zones.js +2 -2
  57. package/dist/verified-recall.js +2 -2
  58. package/dist/work-product-ledger.js +2 -2
  59. package/package.json +1 -1
  60. package/src/causal-chain.ts +80 -42
  61. package/src/direct-answer.test.ts +618 -15
  62. package/src/direct-answer.ts +259 -20
  63. package/src/recall-query-policy.ts +49 -27
  64. package/src/recall-tokenization.ts +131 -21
  65. package/src/temporal-index.ts +23 -6
  66. package/dist/chunk-6HZ6AO2P.js.map +0 -1
  67. package/dist/chunk-DT5TVLJE.js +0 -32
  68. package/dist/chunk-DT5TVLJE.js.map +0 -1
  69. package/dist/chunk-WXACKLKP.js.map +0 -1
  70. package/dist/chunk-Y4FHOFJ2.js +0 -140
  71. package/dist/chunk-Y4FHOFJ2.js.map +0 -1
  72. package/dist/chunk-YQMZ7IH2.js.map +0 -1
  73. /package/dist/{chunk-A2Z6UCWT.js.map → chunk-33JBK2XP.js.map} +0 -0
  74. /package/dist/{chunk-D2MMMTDV.js.map → chunk-5SQ5CQJP.js.map} +0 -0
  75. /package/dist/{chunk-F4LM4ULA.js.map → chunk-65JSA4MP.js.map} +0 -0
  76. /package/dist/{chunk-SKGV326D.js.map → chunk-6GDHLVJC.js.map} +0 -0
  77. /package/dist/{chunk-D65TSG24.js.map → chunk-7DZRO2DC.js.map} +0 -0
  78. /package/dist/{chunk-TYICDVQW.js.map → chunk-BDCCWRHR.js.map} +0 -0
  79. /package/dist/{chunk-LYPDMKUT.js.map → chunk-CL3MWNNQ.js.map} +0 -0
  80. /package/dist/{chunk-W5O2FQTZ.js.map → chunk-GUPISBV2.js.map} +0 -0
  81. /package/dist/{chunk-472U7RDF.js.map → chunk-JGSKJHF7.js.map} +0 -0
  82. /package/dist/{chunk-IEFHBIU2.js.map → chunk-KDUVQU6Y.js.map} +0 -0
  83. /package/dist/{chunk-Z4R6RI2N.js.map → chunk-NSKYFGDL.js.map} +0 -0
  84. /package/dist/{chunk-OD5LFAPZ.js.map → chunk-V67GWXM2.js.map} +0 -0
  85. /package/dist/{chunk-5NXIJZFX.js.map → chunk-WR64DQFE.js.map} +0 -0
  86. /package/dist/{chunk-5BUGGPBR.js.map → chunk-WZA5Y6AC.js.map} +0 -0
  87. /package/dist/{chunk-XPXEJRUB.js.map → chunk-ZRWB5D4H.js.map} +0 -0
  88. /package/dist/{chunk-MA5MWGKP.js.map → chunk-ZT3EGNLR.js.map} +0 -0
  89. /package/dist/{chunk-LMPHTYJC.js.map → chunk-ZZYF3BUL.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/recall-query-policy.ts"],"sourcesContent":["export type RecallPromptShape = \"standard\" | \"instruction_heavy\";\nexport type CronConversationRecallMode = \"auto\" | \"always\" | \"never\";\nexport type RecallBudgetMode = \"full\" | \"minimal\";\n\nexport interface RecallQueryPolicyConfig {\n cronRecallPolicyEnabled: boolean;\n cronRecallNormalizedQueryMaxChars: number;\n cronRecallInstructionHeavyTokenCap: number;\n cronConversationRecallMode: CronConversationRecallMode;\n}\n\nexport interface RecallQueryPolicyResult {\n promptShape: RecallPromptShape;\n retrievalQuery: string;\n skipConversationRecall: boolean;\n retrievalBudgetMode: RecallBudgetMode;\n}\n\nconst DEFAULT_STOPWORDS = new Set([\n \"the\",\n \"and\",\n \"for\",\n \"with\",\n \"from\",\n \"that\",\n \"this\",\n \"your\",\n \"you\",\n \"are\",\n \"was\",\n \"were\",\n \"have\",\n \"has\",\n \"had\",\n \"not\",\n \"but\",\n \"its\",\n \"into\",\n \"only\",\n \"use\",\n \"run\",\n \"then\",\n \"when\",\n \"what\",\n \"where\",\n \"which\",\n \"will\",\n \"would\",\n \"should\",\n \"could\",\n \"goal\",\n \"output\",\n \"format\",\n \"rules\",\n \"section\",\n \"sections\",\n \"skip\",\n \"today\",\n \"yesterday\",\n \"return\",\n \"summary\",\n \"plain\",\n \"text\",\n \"before\",\n \"after\",\n \"time\",\n \"date\",\n \"daily\",\n \"cron\",\n \"agent\",\n \"mode\",\n \"data\",\n \"gathering\",\n \"context\",\n]);\n\nfunction collapseWhitespace(text: string): string {\n return text.replace(/\\s+/g, \" \").trim();\n}\n\nfunction stripFilesystemLikePaths(text: string): string {\n return text\n .replace(/(?:^|\\s)(~\\/[^\\s)]+)(?=\\s|$)/g, \" \")\n .replace(/(?:^|\\s)(\\/[A-Za-z0-9._\\-\\/]+)(?=\\s|$)/g, \" \")\n .replace(/(?:^|\\s)([A-Za-z]:\\\\[^\\s)]+)(?=\\s|$)/g, \" \");\n}\n\nfunction isBulletOrNumberedLine(line: string): boolean {\n if (line.startsWith(\"-\") || line.startsWith(\"*\")) {\n return true;\n }\n\n let i = 0;\n while (i < line.length) {\n const code = line.charCodeAt(i);\n if (code < 48 || code > 57) {\n break;\n }\n i += 1;\n }\n return i > 0 && i < line.length && line.charAt(i) === \".\";\n}\n\nfunction scoreInstructionHeavyShape(prompt: string): number {\n const lines = prompt\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n const lineCount = lines.length;\n if (lineCount === 0) return 0;\n\n const headingLineCount = lines.filter(\n (line) =>\n /^(goal|output format|tone rules|grounding rules|data gathering|date computation|crm context|follow-up|social|current time|return)\\b/i.test(\n line,\n ) || /^[A-Z][A-Z\\s/-]{4,}:$/.test(line),\n ).length;\n const bulletLineCount = lines.filter((line) => isBulletOrNumberedLine(line)).length;\n const longLineCount = lines.filter((line) => line.length >= 180).length;\n const hasPathDensity =\n (prompt.match(/(?:~\\/|\\/Users\\/|[A-Za-z]:\\\\)/g)?.length ?? 0) >= 2;\n const hasImperativeDensity =\n (prompt.match(/\\b(run|extract|read|parse|determine|include|omit|skip)\\b/gi)?.length ?? 0) >=\n 8;\n\n let score = 0;\n if (lineCount >= 24) score += 2;\n if (headingLineCount >= 4) score += 2;\n if (bulletLineCount >= 8) score += 1;\n if (longLineCount >= 3) score += 1;\n if (hasPathDensity) score += 1;\n if (hasImperativeDensity) score += 1;\n return score;\n}\n\nexport function classifyRecallPromptShape(prompt: string): RecallPromptShape {\n const score = scoreInstructionHeavyShape(prompt);\n return score >= 5 ? \"instruction_heavy\" : \"standard\";\n}\n\nfunction tokenizeForCompactQuery(text: string): string[] {\n const raw = text\n .toLowerCase()\n .replace(/[^a-z0-9\\s:_-]+/g, \" \")\n .split(/\\s+/)\n .filter((token) => token.length >= 3);\n const deduped: string[] = [];\n const seen = new Set<string>();\n for (const token of raw) {\n if (DEFAULT_STOPWORDS.has(token)) continue;\n if (seen.has(token)) continue;\n seen.add(token);\n deduped.push(token);\n }\n return deduped;\n}\n\nfunction buildInstructionHeavyQuery(\n prompt: string,\n tokenCap: number,\n maxChars: number,\n): string {\n const cleaned = stripFilesystemLikePaths(prompt);\n const tokens = tokenizeForCompactQuery(cleaned).slice(0, Math.max(8, tokenCap));\n const joined = tokens.join(\" \");\n const compact = collapseWhitespace(joined);\n if (compact.length <= maxChars) return compact;\n return compact.slice(0, maxChars).trim();\n}\n\nexport function clampInstructionHeavyTokenCap(value: number): number {\n if (!Number.isFinite(value)) return 8;\n return Math.max(8, Math.round(value));\n}\n\nfunction buildStandardQuery(prompt: string, maxChars: number): string {\n const trimmed = collapseWhitespace(prompt);\n if (trimmed.length <= maxChars) return trimmed;\n return trimmed.slice(0, maxChars).trim();\n}\n\nexport function buildRecallQueryPolicy(\n prompt: string,\n sessionKey: string | undefined,\n cfg: RecallQueryPolicyConfig,\n): RecallQueryPolicyResult {\n const normalizedPrompt = collapseWhitespace(prompt);\n const isCron = (sessionKey ?? \"\").includes(\":cron:\");\n if (!cfg.cronRecallPolicyEnabled || !isCron) {\n return {\n promptShape: \"standard\",\n retrievalQuery: prompt,\n skipConversationRecall: false,\n retrievalBudgetMode: \"full\",\n };\n }\n\n const promptShape = classifyRecallPromptShape(prompt);\n const maxChars = Math.max(120, cfg.cronRecallNormalizedQueryMaxChars);\n const tokenCap = clampInstructionHeavyTokenCap(cfg.cronRecallInstructionHeavyTokenCap);\n const retrievalQuery =\n promptShape === \"instruction_heavy\"\n ? buildInstructionHeavyQuery(prompt, tokenCap, maxChars)\n : buildStandardQuery(prompt, maxChars);\n\n const skipConversationRecall =\n cfg.cronConversationRecallMode === \"never\"\n ? true\n : cfg.cronConversationRecallMode === \"always\"\n ? false\n : promptShape === \"instruction_heavy\";\n\n const retrievalBudgetMode = promptShape === \"instruction_heavy\" ? \"minimal\" : \"full\";\n\n return {\n promptShape,\n retrievalQuery: retrievalQuery.length > 0 ? retrievalQuery : normalizedPrompt.slice(0, maxChars),\n skipConversationRecall,\n retrievalBudgetMode,\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,32 +0,0 @@
1
- // src/recall-tokenization.ts
2
- function normalizeRecallTokens(value, extraStopWords = []) {
3
- const stopWords = /* @__PURE__ */ new Set([
4
- "the",
5
- "and",
6
- "for",
7
- "with",
8
- "from",
9
- "into",
10
- "that",
11
- "this",
12
- "why",
13
- "did",
14
- ...extraStopWords
15
- ]);
16
- return value.toLowerCase().split(/[^a-z0-9]+/).map((token) => token.trim()).filter((token) => token.length >= 3 && !stopWords.has(token));
17
- }
18
- function countRecallTokenOverlap(queryTokens, value, extraStopWords = []) {
19
- if (!value) return 0;
20
- const tokens = new Set(normalizeRecallTokens(value, extraStopWords));
21
- let matches = 0;
22
- for (const token of queryTokens) {
23
- if (tokens.has(token)) matches += 1;
24
- }
25
- return matches;
26
- }
27
-
28
- export {
29
- normalizeRecallTokens,
30
- countRecallTokenOverlap
31
- };
32
- //# sourceMappingURL=chunk-DT5TVLJE.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/recall-tokenization.ts"],"sourcesContent":["export function normalizeRecallTokens(value: string, extraStopWords: string[] = []): string[] {\n const stopWords = new Set([\n \"the\",\n \"and\",\n \"for\",\n \"with\",\n \"from\",\n \"into\",\n \"that\",\n \"this\",\n \"why\",\n \"did\",\n ...extraStopWords,\n ]);\n\n return value\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .map((token) => token.trim())\n .filter((token) => token.length >= 3 && !stopWords.has(token));\n}\n\nexport function countRecallTokenOverlap(\n queryTokens: Set<string>,\n value: string | undefined,\n extraStopWords: string[] = [],\n): number {\n if (!value) return 0;\n const tokens = new Set(normalizeRecallTokens(value, extraStopWords));\n let matches = 0;\n for (const token of queryTokens) {\n if (tokens.has(token)) matches += 1;\n }\n return matches;\n}\n"],"mappings":";AAAO,SAAS,sBAAsB,OAAe,iBAA2B,CAAC,GAAa;AAC5F,QAAM,YAAY,oBAAI,IAAI;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,CAAC;AAED,SAAO,MACJ,YAAY,EACZ,MAAM,YAAY,EAClB,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAC3B,OAAO,CAAC,UAAU,MAAM,UAAU,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC;AACjE;AAEO,SAAS,wBACd,aACA,OACA,iBAA2B,CAAC,GACpB;AACR,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,IAAI,IAAI,sBAAsB,OAAO,cAAc,CAAC;AACnE,MAAI,UAAU;AACd,aAAW,SAAS,aAAa;AAC/B,QAAI,OAAO,IAAI,KAAK,EAAG,YAAW;AAAA,EACpC;AACA,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/temporal-index.ts"],"sourcesContent":["/**\n * Temporal and Tag Indexes (v8.1 — SwiftMem-inspired)\n *\n * Maintains two fast on-disk lookup structures in `state/`:\n * index_time.json — maps YYYY-MM-DD date buckets to memory file paths\n * index_tags.json — maps tag strings to memory file paths\n *\n * Used as an optional prefilter in the retrieval pipeline:\n * given a time range or a set of tags, narrow the candidate set\n * before the QMD hybrid search so we can pass a smaller pool to scoring.\n *\n * Design constraints:\n * - Must be fail-open (any error returns empty / unfiltered)\n * - Reads/writes are batched per extraction run\n * - Both indexes are plain JSON; no external dependencies\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nexport interface TemporalIndex {\n /** version bumped when schema changes */\n version: number;\n /** Last full rebuild timestamp (ISO string) */\n lastRebuildAt?: string;\n /** Map from YYYY-MM-DD → array of memory paths */\n dates: Record<string, string[]>;\n}\n\nexport interface TagIndex {\n version: number;\n lastRebuildAt?: string;\n /** Map from canonical tag string → node metadata */\n tags: Record<string, TagNode | string[]>;\n /** Map from alias string → canonical tags */\n aliases?: Record<string, string[]>;\n}\n\nexport interface TagNode {\n paths: string[];\n aliases?: string[];\n parents?: string[];\n}\n\nconst INDEX_VERSION = 1;\nconst TEMPORAL_INDEX_FILE = \"index_time.json\";\nconst TAG_INDEX_FILE = \"index_tags.json\";\nconst TAG_INDEX_VERSION = 2;\nconst INDEX_LOCK_STALE_MS = 60_000;\nconst INDEX_LOCK_POLL_MS = 10;\nconst INDEX_PROCESS_START_TOLERANCE_MS = 2_000;\nconst INDEX_LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(4));\nconst INDEX_PROCESS_STARTED_AT_MS = Date.now() - process.uptime() * 1000;\n\ninterface IndexLockOwner {\n pid: number;\n createdAt?: string;\n processStartedAtMs?: number;\n}\n\ntype IndexLockCleanupResult = \"removed\" | \"wait\" | \"blocked\";\n\nfunction stateDir(memoryDir: string): string {\n return path.join(memoryDir, \"state\");\n}\n\nfunction temporalIndexPath(memoryDir: string): string {\n return path.join(stateDir(memoryDir), TEMPORAL_INDEX_FILE);\n}\n\nfunction tagIndexPath(memoryDir: string): string {\n return path.join(stateDir(memoryDir), TAG_INDEX_FILE);\n}\n\nfunction ensureStateDir(memoryDir: string): void {\n const dir = stateDir(memoryDir);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n}\n\nfunction readJsonSafe<T>(filePath: string, fallback: T): T {\n try {\n const raw = fs.readFileSync(filePath, \"utf8\");\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n}\n\nfunction writeJsonSafe(filePath: string, data: unknown): void {\n try {\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), \"utf8\");\n } catch {\n // Fail silently — indexes are advisory only\n }\n}\n\nfunction sleepSync(ms: number): void {\n Atomics.wait(INDEX_LOCK_SLEEP, 0, 0, ms);\n}\n\nfunction uniqueTempPath(filePath: string): string {\n const dir = path.dirname(filePath);\n const base = path.basename(filePath);\n const nonce = crypto.randomBytes(6).toString(\"hex\");\n return path.join(dir, `.${base}.${process.pid}.${Date.now()}.${nonce}.tmp`);\n}\n\nfunction lockOwnerPath(lockDir: string): string {\n return path.join(lockDir, \"owner.json\");\n}\n\nfunction writeIndexLockOwner(lockDir: string): void {\n try {\n fs.writeFileSync(\n lockOwnerPath(lockDir),\n JSON.stringify({\n pid: process.pid,\n createdAt: new Date().toISOString(),\n processStartedAtMs: INDEX_PROCESS_STARTED_AT_MS,\n }),\n {\n encoding: \"utf8\",\n flag: \"wx\",\n }\n );\n } catch {\n // Fail silently — the directory lock is still the serialization primitive.\n }\n}\n\nfunction readIndexLockOwner(lockDir: string): IndexLockOwner | null {\n try {\n const parsed = JSON.parse(fs.readFileSync(lockOwnerPath(lockDir), \"utf8\")) as { pid?: unknown };\n if (!(typeof parsed.pid === \"number\" && Number.isInteger(parsed.pid) && parsed.pid > 0)) return null;\n const owner: IndexLockOwner = { pid: parsed.pid };\n if (\n \"createdAt\" in parsed &&\n typeof (parsed as { createdAt?: unknown }).createdAt === \"string\" &&\n (parsed as { createdAt: string }).createdAt.length > 0\n ) {\n owner.createdAt = (parsed as { createdAt: string }).createdAt;\n }\n const processStartedAtMs = (parsed as { processStartedAtMs?: unknown }).processStartedAtMs;\n if (typeof processStartedAtMs === \"number\" && Number.isFinite(processStartedAtMs) && processStartedAtMs > 0) {\n owner.processStartedAtMs = processStartedAtMs;\n }\n return owner;\n } catch {\n return null;\n }\n}\n\nfunction processIsAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException)?.code;\n return code === \"EPERM\";\n }\n}\n\nfunction readProcessStartedAtMs(pid: number): number | null {\n try {\n const output = execFileSync(\"ps\", [\"-p\", String(pid), \"-o\", \"lstart=\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 1_000,\n }).trim();\n if (!output) return null;\n const startedAtMs = Date.parse(output);\n return Number.isFinite(startedAtMs) ? startedAtMs : null;\n } catch {\n return null;\n }\n}\n\nfunction lockOwnerIsRunning(owner: IndexLockOwner): boolean {\n if (!processIsAlive(owner.pid)) return false;\n if (owner.processStartedAtMs === undefined) return true;\n const runningStartedAtMs = readProcessStartedAtMs(owner.pid);\n if (runningStartedAtMs === null) return true;\n return runningStartedAtMs <= owner.processStartedAtMs + INDEX_PROCESS_START_TOLERANCE_MS;\n}\n\nfunction removeAbandonedIndexLock(lockDir: string): IndexLockCleanupResult {\n try {\n const info = fs.lstatSync(lockDir);\n if (info.isSymbolicLink()) return \"blocked\";\n if (!info.isDirectory()) {\n fs.rmSync(lockDir, { force: true });\n return \"removed\";\n }\n const owner = readIndexLockOwner(lockDir);\n if (owner !== null && lockOwnerIsRunning(owner)) return \"wait\";\n if (owner === null && Date.now() - info.mtimeMs < INDEX_LOCK_STALE_MS) return \"wait\";\n fs.rmSync(lockDir, { recursive: true, force: true });\n return \"removed\";\n } catch {\n // Fail silently — indexes are advisory only\n return \"blocked\";\n }\n}\n\nfunction withIndexFileLock(filePath: string, update: () => void): void {\n const lockDir = `${filePath}.lock.d`;\n let acquired = false;\n\n while (!acquired) {\n try {\n fs.mkdirSync(lockDir);\n writeIndexLockOwner(lockDir);\n acquired = true;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException)?.code;\n if (code !== \"EEXIST\") return;\n const cleanupResult = removeAbandonedIndexLock(lockDir);\n if (cleanupResult === \"blocked\") return;\n sleepSync(INDEX_LOCK_POLL_MS);\n }\n }\n\n try {\n update();\n } finally {\n try {\n fs.rmSync(lockDir, { recursive: true, force: true });\n } catch {\n // Fail silently — indexes are advisory only\n }\n }\n}\n\n/**\n * Atomic write: write to a unique `.tmp` sibling then rename so readers never\n * observe a partially-written file.\n */\nfunction writeJsonAtomic(filePath: string, data: unknown): void {\n const payload = JSON.stringify(data, null, 2);\n for (let attempt = 0; attempt < 3; attempt += 1) {\n const tmp = uniqueTempPath(filePath);\n try {\n fs.writeFileSync(tmp, payload, \"utf8\");\n fs.renameSync(tmp, filePath);\n return;\n } catch {\n try {\n fs.unlinkSync(tmp);\n } catch {\n // Fail silently — indexes are advisory only\n }\n sleepSync(INDEX_LOCK_POLL_MS);\n }\n }\n}\n\nfunction updateTemporalIndex(memoryDir: string, update: (index: TemporalIndex) => void): void {\n const indexPath = temporalIndexPath(memoryDir);\n withIndexFileLock(indexPath, () => {\n const index = readJsonSafe<TemporalIndex>(indexPath, { version: INDEX_VERSION, dates: {} });\n update(index);\n writeJsonAtomic(indexPath, index);\n });\n}\n\nfunction updateTagIndex(memoryDir: string, update: (index: TagIndex) => void): void {\n const indexPath = tagIndexPath(memoryDir);\n withIndexFileLock(indexPath, () => {\n const index = normalizeTagIndex(\n readJsonSafe<TagIndex>(indexPath, { version: TAG_INDEX_VERSION, tags: {}, aliases: {} })\n );\n update(index);\n writeJsonAtomic(indexPath, index);\n });\n}\n\nfunction isoDateFromTimestamp(isoString: string): string {\n if (typeof isoString !== \"string\" || isoString.length < 10) {\n // Malformed frontmatter — fall back to today so the memory is still indexed.\n // Log a warning to surface data-quality issues without aborting the write.\n console.warn(`[engram] temporal-index: malformed timestamp \"${isoString}\", falling back to today`);\n return new Date().toISOString().slice(0, 10);\n }\n return isoString.slice(0, 10); // YYYY-MM-DD\n}\n\nfunction addPathToSet(record: Record<string, string[]>, key: string, p: string): void {\n if (!record[key]) {\n record[key] = [];\n }\n if (!record[key].includes(p)) {\n record[key].push(p);\n }\n}\n\nfunction removePathFromSet(record: Record<string, string[]>, key: string, p: string): void {\n if (!record[key]) return;\n record[key] = record[key].filter((x) => x !== p);\n if (record[key].length === 0) {\n delete record[key];\n }\n}\n\nfunction normalizeTagSegment(segment: string): string {\n return segment\n .trim()\n .toLowerCase()\n .replace(/[_\\s]+/g, \"-\")\n .replace(/[^a-z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nfunction normalizeCanonicalTag(tag: string): string {\n return tag\n .trim()\n .toLowerCase()\n .replace(/\\s*[>:|.]+\\s*/g, \"/\")\n .replace(/\\s*\\/\\s*/g, \"/\")\n .split(\"/\")\n .map(normalizeTagSegment)\n .filter(Boolean)\n .join(\"/\");\n}\n\nfunction normalizeAliasKey(tag: string): string {\n return tag\n .trim()\n .toLowerCase()\n .replace(/[\\/_.:-]+/g, \" \")\n .replace(/[-]+/g, \" \")\n .replace(/[^a-z0-9\\s]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction singularizeAlias(alias: string): string | null {\n if (!alias.endsWith(\"s\") || alias.length <= 3) return null;\n return alias.slice(0, -1);\n}\n\nfunction deriveParentTags(canonical: string): string[] {\n const parts = canonical.split(\"/\").filter(Boolean);\n const parents: string[] = [];\n for (let i = parts.length - 1; i > 0; i -= 1) {\n parents.push(parts.slice(0, i).join(\"/\"));\n }\n return parents;\n}\n\nfunction deriveTagAliases(rawTag: string, canonical: string): string[] {\n const aliases = new Set<string>();\n const canonicalAlias = normalizeAliasKey(canonical);\n const rawAlias = normalizeAliasKey(rawTag);\n const leaf = canonical.split(\"/\").at(-1) ?? canonical;\n const leafAlias = normalizeAliasKey(leaf);\n\n for (const candidate of [canonicalAlias, rawAlias, leafAlias]) {\n if (!candidate) continue;\n aliases.add(candidate);\n const singular = singularizeAlias(candidate);\n if (singular) aliases.add(singular);\n }\n\n return Array.from(aliases);\n}\n\nfunction normalizeTagIndex(raw: TagIndex | null | undefined): TagIndex {\n const normalized: TagIndex = {\n version: TAG_INDEX_VERSION,\n tags: {},\n aliases: {},\n };\n\n if (!raw || typeof raw !== \"object\") {\n return normalized;\n }\n\n const sourceTags = raw.tags ?? {};\n for (const [rawCanonical, nodeOrPaths] of Object.entries(sourceTags)) {\n const canonical = normalizeCanonicalTag(rawCanonical);\n if (!canonical) continue;\n const node: TagNode = Array.isArray(nodeOrPaths)\n ? { paths: [...new Set(nodeOrPaths)] }\n : {\n paths: Array.isArray(nodeOrPaths?.paths) ? [...new Set(nodeOrPaths.paths)] : [],\n aliases: Array.isArray(nodeOrPaths?.aliases) ? [...new Set(nodeOrPaths.aliases)] : [],\n parents: Array.isArray(nodeOrPaths?.parents) ? [...new Set(nodeOrPaths.parents)] : [],\n };\n const existingNode = normalized.tags[canonical];\n if (existingNode && !Array.isArray(existingNode)) {\n existingNode.paths = [...new Set([...existingNode.paths, ...node.paths])];\n existingNode.aliases = [...new Set([...(existingNode.aliases ?? []), ...(node.aliases ?? [])])];\n existingNode.parents = [...new Set([...(existingNode.parents ?? []), ...(node.parents ?? [])])];\n } else if (Array.isArray(existingNode)) {\n normalized.tags[canonical] = {\n paths: [...new Set([...existingNode, ...node.paths])],\n aliases: [...new Set(node.aliases ?? [])],\n parents: [...new Set(node.parents ?? deriveParentTags(canonical))],\n };\n } else {\n normalized.tags[canonical] = node;\n }\n for (const alias of deriveTagAliases(canonical, canonical)) {\n const list = normalized.aliases![alias] ?? [];\n if (!list.includes(canonical)) list.push(canonical);\n normalized.aliases![alias] = list;\n }\n for (const alias of node.aliases ?? []) {\n const aliasKey = normalizeAliasKey(alias);\n if (!aliasKey) continue;\n const list = normalized.aliases![aliasKey] ?? [];\n if (!list.includes(canonical)) list.push(canonical);\n normalized.aliases![aliasKey] = list;\n }\n const mergedNode = normalized.tags[canonical];\n if (mergedNode && !Array.isArray(mergedNode)) {\n mergedNode.parents = [...new Set(mergedNode.parents ?? deriveParentTags(canonical))];\n }\n }\n\n for (const [alias, canonicals] of Object.entries(raw.aliases ?? {})) {\n const aliasKey = normalizeAliasKey(alias);\n if (!aliasKey) continue;\n const list = normalized.aliases![aliasKey] ?? [];\n for (const canonical of canonicals ?? []) {\n const normalizedCanonical = normalizeCanonicalTag(canonical);\n if (normalizedCanonical && !list.includes(normalizedCanonical)) {\n list.push(normalizedCanonical);\n }\n }\n normalized.aliases![aliasKey] = list;\n }\n\n return normalized;\n}\n\nfunction ensureTagNode(index: TagIndex, canonical: string): TagNode {\n const existing = index.tags[canonical];\n if (existing && !Array.isArray(existing)) {\n return existing;\n }\n const created: TagNode = {\n paths: Array.isArray(existing) ? [...new Set(existing)] : [],\n aliases: [],\n parents: deriveParentTags(canonical),\n };\n index.tags[canonical] = created;\n return created;\n}\n\nfunction addTagGraphEntry(index: TagIndex, rawTag: string, memoryPath: string): void {\n const canonical = normalizeCanonicalTag(rawTag);\n if (!canonical) return;\n const node = ensureTagNode(index, canonical);\n if (!node.paths.includes(memoryPath)) {\n node.paths.push(memoryPath);\n }\n\n for (const alias of deriveTagAliases(rawTag, canonical)) {\n const aliasKey = normalizeAliasKey(alias);\n if (!aliasKey) continue;\n if (!node.aliases?.includes(aliasKey)) {\n node.aliases = [...new Set([...(node.aliases ?? []), aliasKey])];\n }\n const list = index.aliases?.[aliasKey] ?? [];\n if (!list.includes(canonical)) {\n index.aliases![aliasKey] = [...list, canonical];\n }\n }\n}\n\nfunction removeTagGraphEntry(index: TagIndex, rawTag: string, memoryPath: string): void {\n const canonical = normalizeCanonicalTag(rawTag);\n if (!canonical) return;\n const node = index.tags[canonical];\n if (!node || Array.isArray(node)) return;\n node.paths = node.paths.filter((value) => value !== memoryPath);\n if (node.paths.length === 0) {\n delete index.tags[canonical];\n }\n}\n\nfunction expandCanonicalTags(index: TagIndex, rawTags: string[]): string[] {\n const canonicals = new Set<string>();\n for (const rawTag of rawTags) {\n const canonical = normalizeCanonicalTag(rawTag);\n if (canonical && index.tags[canonical]) {\n canonicals.add(canonical);\n }\n const aliasKey = normalizeAliasKey(rawTag);\n for (const resolved of index.aliases?.[aliasKey] ?? []) {\n canonicals.add(resolved);\n }\n }\n\n const expanded = new Set<string>();\n for (const canonical of canonicals) {\n expanded.add(canonical);\n const node = index.tags[canonical];\n if (node && !Array.isArray(node)) {\n for (const parent of node.parents ?? []) {\n expanded.add(parent);\n }\n }\n }\n return Array.from(expanded);\n}\n\nfunction aliasPhrase(alias: string): string {\n return alias.replace(/\\//g, \" \").replace(/-/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\nfunction promptContainsAlias(prompt: string, alias: string): boolean {\n const phrase = aliasPhrase(alias);\n if (!phrase) return false;\n const normalizedPrompt = ` ${normalizeAliasKey(prompt)} `;\n return normalizedPrompt.includes(` ${phrase} `);\n}\n\n// ─── Public API ────────────────────────────────────────────────────────────────\n\n/**\n * Add (or update) a memory file in both indexes.\n *\n * @param memoryDir Root memory directory\n * @param memoryPath Absolute path to the memory file\n * @param createdAt ISO timestamp of the memory's creation date\n * @param tags Array of tag strings from the memory's frontmatter\n */\nexport function indexMemory(memoryDir: string, memoryPath: string, createdAt: string, tags: string[]): void {\n try {\n ensureStateDir(memoryDir);\n\n const dateKey = isoDateFromTimestamp(createdAt);\n updateTemporalIndex(memoryDir, (index) => {\n addPathToSet(index.dates, dateKey, memoryPath);\n });\n\n updateTagIndex(memoryDir, (index) => {\n for (const tag of tags) {\n if (tag && typeof tag === \"string\") {\n addTagGraphEntry(index, tag, memoryPath);\n }\n }\n });\n } catch {\n // Fail silently\n }\n}\n\n/**\n * Remove a memory file from both indexes (called on deletion/archival).\n */\nexport function deindexMemory(memoryDir: string, memoryPath: string, createdAt: string, tags: string[]): void {\n try {\n ensureStateDir(memoryDir);\n\n const dateKey = isoDateFromTimestamp(createdAt);\n updateTemporalIndex(memoryDir, (index) => {\n removePathFromSet(index.dates, dateKey, memoryPath);\n });\n\n updateTagIndex(memoryDir, (index) => {\n for (const tag of tags) {\n if (tag && typeof tag === \"string\") {\n removeTagGraphEntry(index, tag, memoryPath);\n }\n }\n });\n } catch {\n // Fail silently\n }\n}\n\n/**\n * Reset both index files to empty state.\n * Called before a full-corpus rebuild so stale paths in any surviving index\n * file do not persist after the rebuild completes.\n */\nexport function clearIndexes(memoryDir: string): void {\n try {\n ensureStateDir(memoryDir);\n updateTemporalIndex(memoryDir, (index) => {\n index.version = INDEX_VERSION;\n index.lastRebuildAt = undefined;\n index.dates = {};\n });\n updateTagIndex(memoryDir, (index) => {\n index.version = TAG_INDEX_VERSION;\n index.lastRebuildAt = undefined;\n index.tags = {};\n index.aliases = {};\n });\n } catch {\n // Fail silently — indexes are advisory only\n }\n}\n\n/**\n * Returns true when both index files exist on disk.\n * Used to detect first-time enablement so callers can trigger a full rebuild.\n */\nexport function indexesExist(memoryDir: string): boolean {\n try {\n return fs.existsSync(temporalIndexPath(memoryDir)) && fs.existsSync(tagIndexPath(memoryDir));\n } catch {\n return false;\n }\n}\n\n/**\n * Batch-add multiple memories to both indexes in a single read-modify-write cycle.\n * More efficient than calling indexMemory() per file when adding many at once.\n */\nexport function indexMemoriesBatch(\n memoryDir: string,\n entries: Array<{ path: string; createdAt: string; tags: string[] }>\n): void {\n if (entries.length === 0) return;\n try {\n ensureStateDir(memoryDir);\n\n updateTemporalIndex(memoryDir, (index) => {\n for (const entry of entries) {\n const dateKey = isoDateFromTimestamp(entry.createdAt);\n addPathToSet(index.dates, dateKey, entry.path);\n }\n });\n\n updateTagIndex(memoryDir, (index) => {\n for (const entry of entries) {\n for (const tag of entry.tags) {\n if (tag && typeof tag === \"string\") {\n addTagGraphEntry(index, tag, entry.path);\n }\n }\n }\n });\n } catch {\n // Fail silently\n }\n}\n\n/**\n * Return the set of memory paths whose index date falls in [fromDate, toDate).\n *\n * Boundary semantics (exclusive upper bound):\n * - `fromDate` is INCLUSIVE — entries on this date ARE returned.\n * - `toDate` is EXCLUSIVE — entries on this date are NOT returned.\n * Pass `recencyWindowBoundsFromPrompt(query).toDate` to get the correct\n * exclusive boundary; do NOT add +1 day yourself.\n * - Default `toDate` = tomorrow, so omitting it includes all of today.\n *\n * @param memoryDir - root memory directory (contains state/index_time.json)\n * @param fromDate - inclusive start date, YYYY-MM-DD\n * @param toDate - exclusive end date, YYYY-MM-DD (default: tomorrow)\n * @returns Set of matching file paths, or null if the index is unavailable.\n */\nexport async function queryByDateRangeAsync(\n memoryDir: string,\n fromDate: string,\n toDate?: string\n): Promise<Set<string> | null> {\n try {\n const tPath = temporalIndexPath(memoryDir);\n let raw: string;\n try {\n raw = await fs.promises.readFile(tPath, \"utf8\");\n } catch {\n return null; // File missing or unreadable\n }\n let tIndex: TemporalIndex;\n try {\n tIndex = JSON.parse(raw) as TemporalIndex;\n } catch {\n tIndex = { version: INDEX_VERSION, dates: {} };\n }\n // toDate is exclusive (first day NOT included). Default: tomorrow so \"all of today\" is included.\n const end = toDate ?? new Date(Date.now() + 86_400_000).toISOString().slice(0, 10);\n\n const results = new Set<string>();\n for (const [date, paths] of Object.entries(tIndex.dates)) {\n if (date >= fromDate && date < end) {\n for (const p of paths) {\n results.add(p);\n }\n }\n }\n return results;\n } catch {\n return null;\n }\n}\n\n/**\n * Async version of queryByTags — uses non-blocking fs.promises.readFile\n * to avoid blocking the Node.js event loop.\n */\nexport async function queryByTagsAsync(memoryDir: string, tags: string[]): Promise<Set<string> | null> {\n if (tags.length === 0) return null;\n try {\n const gPath = tagIndexPath(memoryDir);\n let raw: string;\n try {\n raw = await fs.promises.readFile(gPath, \"utf8\");\n } catch {\n return null; // File missing or unreadable\n }\n let gIndex: TagIndex;\n try {\n gIndex = normalizeTagIndex(JSON.parse(raw) as TagIndex);\n } catch {\n gIndex = { version: TAG_INDEX_VERSION, tags: {}, aliases: {} };\n }\n\n return queryByTagsFromIndex(gIndex, tags);\n } catch {\n return null;\n }\n}\n\nfunction queryByTagsFromIndex(index: TagIndex, tags: string[]): Set<string> | null {\n const expandedTags = expandCanonicalTags(index, tags);\n const results = new Set<string>();\n for (const canonical of expandedTags) {\n const nodeOrPaths = index.tags[canonical];\n const paths = Array.isArray(nodeOrPaths) ? nodeOrPaths : (nodeOrPaths?.paths ?? []);\n for (const pathValue of paths) {\n results.add(pathValue);\n }\n }\n return results.size > 0 ? results : null;\n}\n\n/**\n * Extract tags from a prompt for tag-based prefiltering.\n * Looks for hashtag-style tokens (#foo).\n * Returns lowercase, deduplicated list.\n */\nexport function extractTagsFromPrompt(prompt: string): string[] {\n const found = new Set<string>();\n\n // Match #tag style tokens\n const hashMatches = prompt.matchAll(/#([a-zA-Z][\\w-]{1,30})/g);\n for (const m of hashMatches) {\n const canonical = normalizeCanonicalTag(m[1]);\n if (canonical) found.add(canonical);\n }\n\n return Array.from(found);\n}\n\nexport async function resolvePromptTagPrefilterAsync(\n memoryDir: string,\n prompt: string\n): Promise<{\n matchedTags: string[];\n expandedTags: string[];\n paths: Set<string> | null;\n}> {\n const explicitTags = extractTagsFromPrompt(prompt);\n try {\n const raw = await fs.promises.readFile(tagIndexPath(memoryDir), \"utf8\");\n const tagIndex = normalizeTagIndex(JSON.parse(raw) as TagIndex);\n const matched = new Set<string>(explicitTags);\n\n for (const canonical of Object.keys(tagIndex.tags)) {\n if (promptContainsAlias(prompt, canonical)) {\n matched.add(canonical);\n }\n }\n for (const [alias, canonicals] of Object.entries(tagIndex.aliases ?? {})) {\n if (!promptContainsAlias(prompt, alias)) continue;\n for (const canonical of canonicals) {\n matched.add(canonical);\n }\n }\n\n const expandedTags = expandCanonicalTags(tagIndex, Array.from(matched));\n const paths = queryByTagsFromIndex(tagIndex, expandedTags);\n return {\n matchedTags: Array.from(matched),\n expandedTags,\n paths,\n };\n } catch {\n return { matchedTags: explicitTags, expandedTags: explicitTags, paths: null };\n }\n}\n\n/**\n * Detect if a prompt is time-sensitive (mentions specific time references).\n * Used to decide whether to activate the temporal prefilter.\n */\nexport function isTemporalQuery(prompt: string): boolean {\n return /\\b(today|yesterday|this week|last week|this month|last month|recent(?:ly)?|lately|just now|earlier today|this morning|last night|last year|this year|\\d+ days? ago|\\d+ hours? ago|\\d+ weeks? ago|\\d+ months? ago|(?:in |on |during |since |before |after )?(?:january|february|march|april|may|june|july|august|september|october|november|december)(?:\\s+\\d{1,4})?|\\d{4}-\\d{2}-\\d{2}|\\d{1,2}\\/\\d{1,2}\\/\\d{2,4}|(?:spring|summer|fall|autumn|winter)\\s+\\d{4}|on the \\d{1,2}(?:st|nd|rd|th)?|last (?:monday|tuesday|wednesday|thursday|friday|saturday|sunday))\\b/i.test(\n prompt\n );\n}\n\n/**\n * Compute a \"from date\" string (YYYY-MM-DD) for a recency-based temporal query.\n * For \"recent\" / \"lately\" returns 7 days ago; for today/yesterday the obvious window.\n */\nexport function recencyWindowFromPrompt(prompt: string, nowMs: number = Date.now()): string {\n const p = prompt.toLowerCase();\n const now = new Date(nowMs);\n let daysBack = 7; // default\n\n if (/\\btoday\\b/.test(p) || /\\bthis morning\\b/.test(p) || /\\bjust now\\b/.test(p) || /\\bearlier today\\b/.test(p)) {\n daysBack = 0; // fromDate = today → window [today, today]\n } else if (/\\byesterday\\b/.test(p) || /\\blast night\\b/.test(p)) {\n daysBack = 1; // fromDate = yesterday → window [yesterday, today]\n } else if (/\\bthis week\\b/.test(p)) {\n daysBack = 7;\n } else if (/\\blast week\\b/.test(p)) {\n daysBack = 14;\n } else if (/\\bthis month\\b/.test(p)) {\n daysBack = 31;\n } else if (/\\blast month\\b/.test(p)) {\n daysBack = 62;\n } else if (/\\bthis year\\b/.test(p)) {\n // From Jan 1 of current year\n const jan1 = new Date(now.getFullYear(), 0, 1);\n return jan1.toISOString().slice(0, 10);\n } else if (/\\blast year\\b/.test(p)) {\n const jan1LastYear = new Date(now.getFullYear() - 1, 0, 1);\n return jan1LastYear.toISOString().slice(0, 10);\n } else {\n // Try specific month references: \"in March\", \"during January\", \"since February\"\n const monthNames = [\n \"january\",\n \"february\",\n \"march\",\n \"april\",\n \"may\",\n \"june\",\n \"july\",\n \"august\",\n \"september\",\n \"october\",\n \"november\",\n \"december\",\n ];\n const monthMatch = p.match(\n /\\b(january|february|march|april|may|june|july|august|september|october|november|december)(?:\\s+(\\d{4}))?\\b/\n );\n if (monthMatch) {\n const monthIdx = monthNames.indexOf(monthMatch[1]);\n const year = monthMatch[2] ? Number.parseInt(monthMatch[2], 10) : now.getFullYear();\n // \"before <month>\" means everything prior to that month: use 2-year lookback\n // as fromDate so the window isn't unbounded. toDate is set to the month start\n // in recencyWindowBoundsFromPrompt.\n // IMPORTANT: when an explicit year is given, anchor the lookback to the named\n // month start (not to today). \"before January 2024\" should produce\n // fromDate ≈ 2022-01 — not today-730 days, which could land after 2024-01 and\n // invert the window for any explicitly past month within the 730-day horizon.\n if (/\\bbefore\\b/.test(p)) {\n if (monthMatch[2]) {\n // Explicit year: anchor fromDate 730 days before the named month start.\n const monthStart = new Date(year, monthIdx, 1);\n return new Date(monthStart.getTime() - 730 * 86_400_000).toISOString().slice(0, 10);\n }\n daysBack = 730;\n } else {\n const monthStart = new Date(year, monthIdx, 1);\n return monthStart.toISOString().slice(0, 10);\n }\n }\n\n // Try \"N weeks ago\"\n const weekMatch = p.match(/(\\d{1,5})\\s*weeks?\\s*ago/);\n if (weekMatch) {\n daysBack = Math.min(365, Number.parseInt(weekMatch[1], 10) * 7);\n } else {\n // Try \"N months ago\"\n const monthsAgoMatch = p.match(/(\\d{1,5})\\s*months?\\s*ago/);\n if (monthsAgoMatch) {\n daysBack = Math.min(730, Number.parseInt(monthsAgoMatch[1], 10) * 31);\n } else {\n const numMatch = p.match(/(\\d{1,5})\\s*days?\\s*ago/);\n if (numMatch) {\n daysBack = Math.min(365, Number.parseInt(numMatch[1], 10)); // no off-by-one: \"3 days ago\" → 3\n } else {\n const hrMatch = p.match(/(\\d{1,5})\\s*hours?\\s*ago/);\n if (hrMatch) {\n // Convert hours to days (ceiling); at least 1 day window\n daysBack = Math.max(1, Math.ceil(Number.parseInt(hrMatch[1], 10) / 24));\n }\n }\n }\n }\n\n // Try explicit date patterns: YYYY-MM-DD or MM/DD/YYYY\n const isoMatch = p.match(/(\\d{4})-(\\d{2})-(\\d{2})/);\n if (isoMatch) {\n return `${isoMatch[1]}-${isoMatch[2]}-${isoMatch[3]}`;\n }\n const usMatch = p.match(/(\\d{1,2})\\/(\\d{1,2})\\/(\\d{2,4})/);\n if (usMatch) {\n const year = usMatch[3].length === 2 ? 2000 + Number.parseInt(usMatch[3], 10) : Number.parseInt(usMatch[3], 10);\n return `${year}-${usMatch[1].padStart(2, \"0\")}-${usMatch[2].padStart(2, \"0\")}`;\n }\n\n // Try \"last Monday/Tuesday/etc\"\n const dayOfWeekMatch = p.match(/\\blast\\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\\b/);\n if (dayOfWeekMatch) {\n const dayNames = [\"sunday\", \"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\", \"saturday\"];\n const targetDay = dayNames.indexOf(dayOfWeekMatch[1]);\n const currentDay = now.getDay();\n daysBack = (currentDay - targetDay + 7) % 7 || 7; // at least 7 days back\n }\n }\n\n const from = new Date(nowMs - daysBack * 24 * 60 * 60 * 1000);\n return from.toISOString().slice(0, 10);\n}\n\n/**\n * Returns both the start and end of the temporal window implied by the prompt.\n *\n * `fromDate` is computed by delegating to `recencyWindowFromPrompt`, so the two\n * functions cannot diverge on the lower bound. `toDate` is computed independently\n * here using the same pattern-priority ordering, because the upper-bound arithmetic\n * differs (exclusive end vs. inclusive start) and would not be expressible as a\n * simple wrapper.\n *\n * Known divergence to be aware of: for \"N hours ago\", `recencyWindowFromPrompt`\n * uses `Math.max(1, Math.ceil(hours/24))` days back, so `fromDate` = yesterday for\n * sub-24h values. `toDate` here is always `tomorrow` when no explicit date is\n * specified (see the `toDaysBack === 0` branch), giving a 2-day window. This is\n * intentional — the window must cover both yesterday and today for recent hour-level\n * queries. Any fix to the hours formula in `recencyWindowFromPrompt` must be\n * manually reflected here.\n *\n * - `fromDate`: first day of the implied window (same as `recencyWindowFromPrompt`)\n * - `toDate`: first day NOT in the window (exclusive upper bound), so callers\n * should filter with `date >= fromDate && date < toDate`.\n */\nexport function recencyWindowBoundsFromPrompt(\n prompt: string,\n nowMs: number = Date.now()\n): { fromDate: string; toDate: string } {\n const fromDate = recencyWindowFromPrompt(prompt, nowMs);\n const p = prompt.toLowerCase();\n const now = new Date(nowMs);\n const today = now.toISOString().slice(0, 10);\n const tomorrow = new Date(nowMs + 86_400_000).toISOString().slice(0, 10);\n\n let toDate: string;\n\n if (/\\btoday\\b|\\bthis morning\\b|\\bjust now\\b|\\bearlier today\\b/.test(p)) {\n toDate = tomorrow; // exclusive: include all of today\n } else if (/\\byesterday\\b|\\blast night\\b/.test(p)) {\n toDate = today; // exclusive: include all of yesterday, stop before today\n } else if (/\\bthis week\\b|\\bthis month\\b|\\bthis year\\b/.test(p)) {\n toDate = tomorrow; // exclusive: include all of today\n } else if (/\\blast week\\b/.test(p)) {\n toDate = new Date(nowMs - 7 * 86_400_000).toISOString().slice(0, 10); // already exclusive\n } else if (/\\blast month\\b/.test(p)) {\n toDate = new Date(nowMs - 31 * 86_400_000).toISOString().slice(0, 10); // approximately exclusive\n } else if (/\\blast year\\b/.test(p)) {\n toDate = `${now.getFullYear()}-01-01`; // exclusive: stop at Jan 1 of current year\n } else {\n // Mirror the structure of recencyWindowFromPrompt's else branch:\n // month name → early set (highest priority), then ago patterns set a\n // working offset, then ISO/US/weekday patterns run AFTER ago patterns\n // and can override them — matching the priority ordering in recencyWindowFromPrompt.\n\n const monthNames = [\n \"january\",\n \"february\",\n \"march\",\n \"april\",\n \"may\",\n \"june\",\n \"july\",\n \"august\",\n \"september\",\n \"october\",\n \"november\",\n \"december\",\n ];\n const monthMatch = p.match(\n /\\b(january|february|march|april|may|june|july|august|september|october|november|december)(?:\\s+(\\d{4}))?\\b/\n );\n if (monthMatch) {\n // \"since <month>\" / \"after <month>\" — open-ended: everything from that month to now.\n // \"before <month>\" — closed upper bound: everything before that month starts.\n // Plain \"<month>\" — just that calendar month.\n const monthIdx = monthNames.indexOf(monthMatch[1]);\n const year = monthMatch[2] ? Number.parseInt(monthMatch[2], 10) : now.getFullYear();\n const isSinceOrAfter = /\\b(since|after)\\b/.test(p);\n const isBefore = /\\bbefore\\b/.test(p);\n if (isSinceOrAfter) {\n toDate = tomorrow;\n } else if (isBefore) {\n // \"before <month>\" means everything prior to that month.\n // toDate = first day of that month (exclusive upper bound).\n toDate = new Date(year, monthIdx, 1).toISOString().slice(0, 10);\n } else {\n // Closed window: first day of the NEXT month is the exclusive upper bound.\n toDate = new Date(year, monthIdx + 1, 1).toISOString().slice(0, 10);\n }\n } else {\n // Ago patterns set a working offset (recent edge of the N-ago window).\n // Same nesting order as recencyWindowFromPrompt.\n // Sentinel -1 means \"no ago pattern matched\" → toDate falls back to tomorrow.\n let toDaysBack = -1;\n const weekMatch = p.match(/(\\d{1,5})\\s*weeks?\\s*ago/);\n if (weekMatch) {\n toDaysBack = Math.max(0, Math.min(52, Number.parseInt(weekMatch[1], 10)) - 1) * 7;\n } else {\n const monthsAgoMatch = p.match(/(\\d{1,5})\\s*months?\\s*ago/);\n if (monthsAgoMatch) {\n toDaysBack = Math.max(0, Math.min(24, Number.parseInt(monthsAgoMatch[1], 10)) - 1) * 31;\n } else {\n const numMatch = p.match(/(\\d{1,5})\\s*days?\\s*ago/);\n if (numMatch) {\n // (N-1) mirrors the weeks/months ago formula: \"3 days ago\" → window [today-3, today-2]\n // N=1 → toDaysBack=0 → toDate=today (exclusive) → window [yesterday, today) = 1 day. ✓\n toDaysBack = Math.max(0, Math.min(365, Number.parseInt(numMatch[1], 10)) - 1);\n } else {\n const hrMatch = p.match(/(\\d{1,5})\\s*hours?\\s*ago/);\n if (hrMatch) {\n // Sub-day precision: keep sentinel -1 so toDate falls back to tomorrow,\n // which includes today. fromDate = yesterday (recencyWindowFromPrompt uses ceiling).\n toDaysBack = -1;\n }\n }\n }\n }\n\n // Explicit date/weekday patterns run AFTER ago patterns (same as recencyWindowFromPrompt)\n // and override the ago-derived offset when present.\n const isoMatch = p.match(/(\\d{4})-(\\d{2})-(\\d{2})/);\n if (isoMatch) {\n // +1 day: exclusive upper bound includes the named date\n const d = new Date(`${isoMatch[1]}-${isoMatch[2]}-${isoMatch[3]}T00:00:00Z`);\n toDate = new Date(d.getTime() + 86_400_000).toISOString().slice(0, 10);\n } else {\n const usMatch = p.match(/(\\d{1,2})\\/(\\d{1,2})\\/(\\d{2,4})/);\n if (usMatch) {\n const year =\n usMatch[3].length === 2 ? 2000 + Number.parseInt(usMatch[3], 10) : Number.parseInt(usMatch[3], 10);\n // +1 day: exclusive upper bound includes the named date\n const d = new Date(`${year}-${usMatch[1].padStart(2, \"0\")}-${usMatch[2].padStart(2, \"0\")}T00:00:00Z`);\n toDate = new Date(d.getTime() + 86_400_000).toISOString().slice(0, 10);\n } else {\n const dayOfWeekMatch = p.match(/\\blast\\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\\b/);\n if (dayOfWeekMatch) {\n const dayNames = [\"sunday\", \"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\", \"saturday\"];\n const targetDay = dayNames.indexOf(dayOfWeekMatch[1]);\n const currentDay = now.getDay();\n const daysBack = (currentDay - targetDay + 7) % 7 || 7;\n // +1 day: exclusive upper bound includes the named weekday\n toDate = new Date(nowMs - (daysBack - 1) * 86_400_000).toISOString().slice(0, 10);\n } else {\n // Use the ago-derived offset.\n // toDaysBack=-1 means no pattern matched (or hours-ago): use tomorrow so today\n // is included in the window. toDaysBack=0 means N=1 ago (e.g. \"1 day ago\"):\n // toDate = today (exclusive) correctly creates a 1-day window [yesterday, today).\n toDate = toDaysBack < 0 ? tomorrow : new Date(nowMs - toDaysBack * 86_400_000).toISOString().slice(0, 10);\n }\n }\n }\n }\n }\n\n // Guard: if toDate would precede fromDate (inverted window from conflicting keywords),\n // fall back to tomorrow (exclusive upper bound that covers today) so we never produce\n // an empty window. Using `today` is insufficient because `date < today` excludes today.\n if (toDate <= fromDate) toDate = tomorrow;\n\n return { fromDate, toDate };\n}\n"],"mappings":"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","names":[]}
@@ -1,140 +0,0 @@
1
- import {
2
- countRecallTokenOverlap,
3
- normalizeRecallTokens
4
- } from "./chunk-DT5TVLJE.js";
5
-
6
- // src/direct-answer.ts
7
- var FILTER_LABELS = {
8
- nonActiveStatus: "non-active-status",
9
- notTrustedZone: "not-trusted-zone",
10
- ineligibleTaxonomyBucket: "ineligible-taxonomy-bucket",
11
- belowImportanceFloor: "below-importance-floor",
12
- entityRefMismatch: "entity-ref-mismatch",
13
- belowTokenOverlapFloor: "below-token-overlap-floor"
14
- };
15
- function isDirectAnswerEligible(input) {
16
- const { query, candidates, config, queryEntityRefs } = input;
17
- if (!config.enabled) {
18
- return {
19
- eligible: false,
20
- reason: "disabled",
21
- narrative: "direct-answer tier is disabled",
22
- filteredBy: []
23
- };
24
- }
25
- const queryTokens = new Set(normalizeRecallTokens(query));
26
- if (queryTokens.size === 0) {
27
- return {
28
- eligible: false,
29
- reason: "empty-query",
30
- narrative: "query has no searchable tokens after normalization",
31
- filteredBy: []
32
- };
33
- }
34
- if (candidates.length === 0) {
35
- return {
36
- eligible: false,
37
- reason: "no-candidates",
38
- narrative: "no candidates supplied",
39
- filteredBy: []
40
- };
41
- }
42
- const filteredBy = [];
43
- let working = candidates;
44
- working = applyFilter(working, filteredBy, FILTER_LABELS.nonActiveStatus, (c) => {
45
- const status = c.memory.frontmatter.status ?? "active";
46
- return status === "active";
47
- });
48
- working = applyFilter(
49
- working,
50
- filteredBy,
51
- FILTER_LABELS.notTrustedZone,
52
- (c) => c.trustZone === "trusted"
53
- );
54
- working = applyFilter(
55
- working,
56
- filteredBy,
57
- FILTER_LABELS.ineligibleTaxonomyBucket,
58
- (c) => c.taxonomyBucket !== null && config.eligibleTaxonomyBuckets.includes(c.taxonomyBucket)
59
- );
60
- working = applyFilter(working, filteredBy, FILTER_LABELS.belowImportanceFloor, (c) => {
61
- if (c.memory.frontmatter.verificationState === "user_confirmed") return true;
62
- return c.importanceScore >= config.importanceFloor;
63
- });
64
- if (queryEntityRefs && queryEntityRefs.length > 0) {
65
- const normRefs = new Set(queryEntityRefs.map((r) => r.toLowerCase()));
66
- working = applyFilter(working, filteredBy, FILTER_LABELS.entityRefMismatch, (c) => {
67
- const ref = c.memory.frontmatter.entityRef;
68
- if (!ref) return true;
69
- return normRefs.has(ref.toLowerCase());
70
- });
71
- }
72
- if (working.length === 0) {
73
- return {
74
- eligible: false,
75
- reason: "no-eligible-candidates",
76
- narrative: "no candidates survived eligibility filters",
77
- filteredBy
78
- };
79
- }
80
- const scored = working.map((candidate) => {
81
- const searchable = `${candidate.memory.frontmatter.tags?.join(" ") ?? ""} ${candidate.memory.content}`.trim();
82
- const matches = countRecallTokenOverlap(queryTokens, searchable);
83
- return { candidate, tokenOverlap: matches / queryTokens.size };
84
- });
85
- const overlapSurvivors = scored.filter((s) => s.tokenOverlap >= config.tokenOverlapFloor);
86
- if (overlapSurvivors.length < scored.length) {
87
- filteredBy.push(FILTER_LABELS.belowTokenOverlapFloor);
88
- }
89
- if (overlapSurvivors.length === 0) {
90
- return {
91
- eligible: false,
92
- reason: "below-token-overlap-floor",
93
- narrative: `no candidate met token-overlap floor ${config.tokenOverlapFloor}`,
94
- filteredBy
95
- };
96
- }
97
- overlapSurvivors.sort(compareScored);
98
- if (overlapSurvivors.length >= 2) {
99
- const topScore = scoreFor(overlapSurvivors[0]);
100
- const secondScore = scoreFor(overlapSurvivors[1]);
101
- if (topScore - secondScore < config.ambiguityMargin) {
102
- return {
103
- eligible: false,
104
- reason: "ambiguous",
105
- narrative: `top two candidates within ambiguityMargin ${config.ambiguityMargin}`,
106
- filteredBy
107
- };
108
- }
109
- }
110
- const winner = overlapSurvivors[0];
111
- const bucket = winner.candidate.taxonomyBucket ?? "unknown";
112
- return {
113
- eligible: true,
114
- reason: "eligible",
115
- winner: winner.candidate,
116
- tokenOverlap: winner.tokenOverlap,
117
- narrative: `trusted ${bucket}, unambiguous, token-overlap ${winner.tokenOverlap.toFixed(2)}`,
118
- filteredBy
119
- };
120
- }
121
- function applyFilter(working, filteredBy, label, keep) {
122
- const before = working.length;
123
- const next = working.filter(keep);
124
- if (next.length < before) filteredBy.push(label);
125
- return next;
126
- }
127
- function scoreFor(s) {
128
- return s.candidate.matchScore ?? s.tokenOverlap;
129
- }
130
- function compareScored(a, b) {
131
- const diff = scoreFor(b) - scoreFor(a);
132
- if (diff !== 0) return diff;
133
- return a.candidate.memory.path.localeCompare(b.candidate.memory.path);
134
- }
135
-
136
- export {
137
- FILTER_LABELS,
138
- isDirectAnswerEligible
139
- };
140
- //# sourceMappingURL=chunk-Y4FHOFJ2.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/direct-answer.ts"],"sourcesContent":["/**\n * Direct-answer retrieval tier eligibility (issue #518 slice 2).\n *\n * This module is a pure decision layer. It takes a query, a set of\n * caller-resolved candidates (each already decorated with trust-zone,\n * taxonomy-bucket, and importance information), and the direct-answer\n * config, then returns an eligibility verdict.\n *\n * Keeping the module pure means:\n *\n * - Tests do not need a trust-zones store, taxonomy resolver, or importance\n * scorer on disk.\n * - Slice 3 (retrieval.ts wiring) is responsible for resolving those signals\n * before calling in; the wiring layer decides where candidates come from\n * (entity index, token prefilter, etc.). This module only decides\n * whether the surfaced candidates add up to a confident direct answer.\n *\n * Not wired into retrieval yet — see slice 3.\n */\n\nimport type { MemoryFile, MemoryStatus } from \"./types.js\";\nimport type { TrustZoneName } from \"./trust-zones.js\";\nimport {\n countRecallTokenOverlap,\n normalizeRecallTokens,\n} from \"./recall-tokenization.js\";\n\n/**\n * Caller-supplied candidate.\n *\n * `trustZone`, `taxonomyBucket`, and `importanceScore` are resolved outside\n * this module because each comes from a different subsystem. Passing them\n * as inputs keeps the function deterministic and easy to unit-test.\n *\n * `matchScore` is optional; if omitted, candidates are ranked by\n * token-overlap ratio. Callers that already computed a better ranking\n * score (e.g. BM25, vector similarity) can supply it to drive the\n * ambiguity check.\n */\nexport interface DirectAnswerCandidate {\n memory: MemoryFile;\n trustZone: TrustZoneName | null;\n taxonomyBucket: string | null;\n importanceScore: number;\n matchScore?: number;\n}\n\nexport interface DirectAnswerConfig {\n enabled: boolean;\n tokenOverlapFloor: number;\n importanceFloor: number;\n ambiguityMargin: number;\n eligibleTaxonomyBuckets: string[];\n}\n\nexport interface DirectAnswerInput {\n query: string;\n candidates: DirectAnswerCandidate[];\n config: DirectAnswerConfig;\n /**\n * Optional entity-ref hints resolved from the query upstream. When\n * supplied, a candidate with a set `entityRef` must match one of these\n * (case-insensitive) to remain eligible. Candidates without an\n * `entityRef` are allowed through regardless.\n */\n queryEntityRefs?: string[];\n}\n\nexport type DirectAnswerReason =\n | \"disabled\"\n | \"empty-query\"\n | \"no-candidates\"\n | \"no-eligible-candidates\"\n | \"below-token-overlap-floor\"\n | \"ambiguous\"\n | \"eligible\";\n\nexport interface DirectAnswerResult {\n eligible: boolean;\n reason: DirectAnswerReason;\n /** Winning candidate when eligible. */\n winner?: DirectAnswerCandidate;\n /** Computed token-overlap ratio (0..1) of the winner. */\n tokenOverlap?: number;\n /**\n * Human-readable summary suitable for\n * `RecallTierExplain.tierReason`.\n */\n narrative: string;\n /**\n * Filter labels that eliminated at least one candidate along the way.\n * Populated regardless of eligibility so the caller can surface the\n * narrowing steps in `RecallTierExplain.filteredBy`.\n */\n filteredBy: string[];\n}\n\n/** Filter labels — exported so callers and tests can match them structurally. */\nexport const FILTER_LABELS = {\n nonActiveStatus: \"non-active-status\",\n notTrustedZone: \"not-trusted-zone\",\n ineligibleTaxonomyBucket: \"ineligible-taxonomy-bucket\",\n belowImportanceFloor: \"below-importance-floor\",\n entityRefMismatch: \"entity-ref-mismatch\",\n belowTokenOverlapFloor: \"below-token-overlap-floor\",\n} as const;\n\ninterface ScoredCandidate {\n candidate: DirectAnswerCandidate;\n tokenOverlap: number;\n}\n\n/**\n * Determine whether a query can be served by the direct-answer tier.\n *\n * Decision ladder, in order:\n *\n * 1. config.enabled === false → \"disabled\"\n * 2. empty query tokens → \"empty-query\"\n * 3. empty candidates → \"no-candidates\"\n * 4. hard filters drop all candidates → \"no-eligible-candidates\"\n * 5. token-overlap floor drops all → \"below-token-overlap-floor\"\n * 6. top two candidates within ambiguityMargin → \"ambiguous\"\n * 7. otherwise → \"eligible\"\n */\nexport function isDirectAnswerEligible(\n input: DirectAnswerInput,\n): DirectAnswerResult {\n const { query, candidates, config, queryEntityRefs } = input;\n\n if (!config.enabled) {\n return {\n eligible: false,\n reason: \"disabled\",\n narrative: \"direct-answer tier is disabled\",\n filteredBy: [],\n };\n }\n\n const queryTokens = new Set(normalizeRecallTokens(query));\n if (queryTokens.size === 0) {\n return {\n eligible: false,\n reason: \"empty-query\",\n narrative: \"query has no searchable tokens after normalization\",\n filteredBy: [],\n };\n }\n\n if (candidates.length === 0) {\n return {\n eligible: false,\n reason: \"no-candidates\",\n narrative: \"no candidates supplied\",\n filteredBy: [],\n };\n }\n\n const filteredBy: string[] = [];\n let working: DirectAnswerCandidate[] = candidates;\n\n working = applyFilter(working, filteredBy, FILTER_LABELS.nonActiveStatus, (c) => {\n const status: MemoryStatus = c.memory.frontmatter.status ?? \"active\";\n return status === \"active\";\n });\n\n working = applyFilter(working, filteredBy, FILTER_LABELS.notTrustedZone, (c) =>\n c.trustZone === \"trusted\",\n );\n\n working = applyFilter(\n working,\n filteredBy,\n FILTER_LABELS.ineligibleTaxonomyBucket,\n (c) =>\n c.taxonomyBucket !== null &&\n config.eligibleTaxonomyBuckets.includes(c.taxonomyBucket),\n );\n\n working = applyFilter(working, filteredBy, FILTER_LABELS.belowImportanceFloor, (c) => {\n if (c.memory.frontmatter.verificationState === \"user_confirmed\") return true;\n return c.importanceScore >= config.importanceFloor;\n });\n\n if (queryEntityRefs && queryEntityRefs.length > 0) {\n const normRefs = new Set(queryEntityRefs.map((r) => r.toLowerCase()));\n working = applyFilter(working, filteredBy, FILTER_LABELS.entityRefMismatch, (c) => {\n const ref = c.memory.frontmatter.entityRef;\n if (!ref) return true;\n return normRefs.has(ref.toLowerCase());\n });\n }\n\n if (working.length === 0) {\n return {\n eligible: false,\n reason: \"no-eligible-candidates\",\n narrative: \"no candidates survived eligibility filters\",\n filteredBy,\n };\n }\n\n const scored: ScoredCandidate[] = working.map((candidate) => {\n const searchable =\n `${candidate.memory.frontmatter.tags?.join(\" \") ?? \"\"} ${candidate.memory.content}`.trim();\n const matches = countRecallTokenOverlap(queryTokens, searchable);\n return { candidate, tokenOverlap: matches / queryTokens.size };\n });\n\n const overlapSurvivors = scored.filter((s) => s.tokenOverlap >= config.tokenOverlapFloor);\n if (overlapSurvivors.length < scored.length) {\n filteredBy.push(FILTER_LABELS.belowTokenOverlapFloor);\n }\n\n if (overlapSurvivors.length === 0) {\n return {\n eligible: false,\n reason: \"below-token-overlap-floor\",\n narrative: `no candidate met token-overlap floor ${config.tokenOverlapFloor}`,\n filteredBy,\n };\n }\n\n overlapSurvivors.sort(compareScored);\n\n if (overlapSurvivors.length >= 2) {\n const topScore = scoreFor(overlapSurvivors[0]);\n const secondScore = scoreFor(overlapSurvivors[1]);\n if (topScore - secondScore < config.ambiguityMargin) {\n return {\n eligible: false,\n reason: \"ambiguous\",\n narrative: `top two candidates within ambiguityMargin ${config.ambiguityMargin}`,\n filteredBy,\n };\n }\n }\n\n const winner = overlapSurvivors[0];\n const bucket = winner.candidate.taxonomyBucket ?? \"unknown\";\n return {\n eligible: true,\n reason: \"eligible\",\n winner: winner.candidate,\n tokenOverlap: winner.tokenOverlap,\n narrative: `trusted ${bucket}, unambiguous, token-overlap ${winner.tokenOverlap.toFixed(2)}`,\n filteredBy,\n };\n}\n\nfunction applyFilter(\n working: DirectAnswerCandidate[],\n filteredBy: string[],\n label: string,\n keep: (c: DirectAnswerCandidate) => boolean,\n): DirectAnswerCandidate[] {\n const before = working.length;\n const next = working.filter(keep);\n if (next.length < before) filteredBy.push(label);\n return next;\n}\n\nfunction scoreFor(s: ScoredCandidate): number {\n return s.candidate.matchScore ?? s.tokenOverlap;\n}\n\nfunction compareScored(a: ScoredCandidate, b: ScoredCandidate): number {\n const diff = scoreFor(b) - scoreFor(a);\n if (diff !== 0) return diff;\n // Stable secondary key on path so the comparator returns 0 only for equal\n // entries (CLAUDE.md rule 19).\n return a.candidate.memory.path.localeCompare(b.candidate.memory.path);\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/causal-chain.ts"],"sourcesContent":["/**\n * causal-chain.ts — Cross-session causal chain persistence for CMC.\n *\n * Stitches causal trajectories across session boundaries by detecting\n * when a trajectory in one session follows up on a trajectory from\n * another session. Persists edges in a graph index for later retrieval.\n */\n\nimport path from \"node:path\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\nimport { listJsonFiles, readJsonFile } from \"./json-store.js\";\nimport { resolveCausalTrajectoryStoreDir, type CausalTrajectoryRecord } from \"./causal-trajectory.js\";\nimport { topicOverlapScore } from \"./boxes.js\";\nimport { countRecallTokenOverlap, normalizeRecallTokens } from \"./recall-tokenization.js\";\nimport {\n assertIsoRecordedAt,\n assertString,\n isRecord,\n recordStoreDay,\n} from \"./store-contract.js\";\nimport { log } from \"./logger.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CausalEdge {\n schemaVersion: 1;\n edgeId: string;\n fromTrajectoryId: string;\n toTrajectoryId: string;\n edgeType: \"follow_up_to_goal\" | \"retry\" | \"continuation\" | \"correction\";\n confidence: number;\n stitchMethod: \"lexical\" | \"entity\" | \"temporal\" | \"explicit\";\n createdAt: string;\n metadata?: Record<string, string>;\n}\n\nexport interface CausalChainIndex {\n outgoing: Record<string, string[]>;\n incoming: Record<string, string[]>;\n edges: Record<string, CausalEdge>;\n updatedAt: string;\n}\n\nexport interface StitchConfig {\n lookbackDays: number;\n minScore: number;\n maxEdgesPerTrajectory: number;\n}\n\nexport interface StitchCandidate {\n trajectory: CausalTrajectoryRecord;\n score: number;\n edgeType: CausalEdge[\"edgeType\"];\n stitchMethod: CausalEdge[\"stitchMethod\"];\n}\n\n// ─── Scoring Weights ─────────────────────────────────────────────────────────\n\nconst STITCH_WEIGHTS = {\n followUpToGoal: 4.0,\n outcomeToGoal: 2.0,\n entityOverlap: 3.0,\n tagOverlap: 1.5,\n temporalProximity: 1.0,\n} as const;\n\n// ─── Validation ──────────────────────────────────────────────────────────────\n\nexport function validateCausalEdge(raw: unknown): CausalEdge {\n if (!isRecord(raw)) throw new Error(\"CausalEdge must be an object\");\n if (raw.schemaVersion !== 1) throw new Error(\"CausalEdge.schemaVersion must be 1\");\n\n const edgeType = assertString(raw.edgeType, \"edgeType\");\n const validEdgeTypes = [\"follow_up_to_goal\", \"retry\", \"continuation\", \"correction\"];\n if (!validEdgeTypes.includes(edgeType)) {\n throw new Error(`CausalEdge.edgeType must be one of ${validEdgeTypes.join(\", \")}`);\n }\n\n const stitchMethod = assertString(raw.stitchMethod, \"stitchMethod\");\n const validMethods = [\"lexical\", \"entity\", \"temporal\", \"explicit\"];\n if (!validMethods.includes(stitchMethod)) {\n throw new Error(`CausalEdge.stitchMethod must be one of ${validMethods.join(\", \")}`);\n }\n\n const confidence = typeof raw.confidence === \"number\" ? raw.confidence : 0;\n if (confidence < 0 || confidence > 1) throw new Error(\"CausalEdge.confidence must be in [0, 1]\");\n\n return {\n schemaVersion: 1,\n edgeId: assertString(raw.edgeId, \"edgeId\"),\n fromTrajectoryId: assertString(raw.fromTrajectoryId, \"fromTrajectoryId\"),\n toTrajectoryId: assertString(raw.toTrajectoryId, \"toTrajectoryId\"),\n edgeType: edgeType as CausalEdge[\"edgeType\"],\n confidence: confidence,\n stitchMethod: stitchMethod as CausalEdge[\"stitchMethod\"],\n createdAt: assertIsoRecordedAt(assertString(raw.createdAt, \"createdAt\")),\n metadata: isRecord(raw.metadata)\n ? Object.fromEntries(\n Object.entries(raw.metadata).filter(\n (entry): entry is [string, string] => typeof entry[1] === \"string\",\n ),\n )\n : undefined,\n };\n}\n\n// ─── Stable Edge ID ──────────────────────────────────────────────────────────\n\nexport function makeEdgeId(fromId: string, toId: string): string {\n const digest = createHash(\"sha256\")\n .update(`${fromId}\\0${toId}`)\n .digest(\"hex\")\n .slice(0, 12);\n return `edge-${digest}`;\n}\n\n// ─── Storage Paths ───────────────────────────────────────────────────────────\n\nexport function resolveChainsDir(memoryDir: string, causalTrajectoryStoreDir?: string): string {\n const root = resolveCausalTrajectoryStoreDir(memoryDir, causalTrajectoryStoreDir);\n return path.join(root, \"chains\");\n}\n\nfunction chainIndexPath(chainsDir: string): string {\n return path.join(chainsDir, \"chain-index.json\");\n}\n\nfunction edgeFilePath(chainsDir: string, edge: CausalEdge): string {\n const day = recordStoreDay(edge.createdAt);\n return path.join(chainsDir, \"edges\", day, `${edge.edgeId}.json`);\n}\n\n// ─── Chain Index CRUD ────────────────────────────────────────────────────────\n\nconst chainMutationQueues = new Map<string, Promise<unknown>>();\n\nfunction enqueueChainMutation<T>(chainsDir: string, op: () => Promise<T>): Promise<T> {\n const key = path.resolve(chainsDir);\n const previous = chainMutationQueues.get(key) ?? Promise.resolve();\n const run = previous.catch(() => {}).then(op);\n const settled = run.catch(() => {});\n chainMutationQueues.set(key, settled);\n void settled.finally(() => {\n if (chainMutationQueues.get(key) === settled) {\n chainMutationQueues.delete(key);\n }\n });\n return run;\n}\n\nexport async function readChainIndex(chainsDir: string): Promise<CausalChainIndex> {\n try {\n const raw = JSON.parse(await readFile(chainIndexPath(chainsDir), \"utf8\"));\n return {\n outgoing: isRecord(raw.outgoing) ? raw.outgoing as Record<string, string[]> : {},\n incoming: isRecord(raw.incoming) ? raw.incoming as Record<string, string[]> : {},\n edges: isRecord(raw.edges) ? raw.edges as Record<string, CausalEdge> : {},\n updatedAt: typeof raw.updatedAt === \"string\" ? raw.updatedAt : new Date().toISOString(),\n };\n } catch {\n return { outgoing: {}, incoming: {}, edges: {}, updatedAt: new Date().toISOString() };\n }\n}\n\nexport async function writeChainIndex(chainsDir: string, index: CausalChainIndex): Promise<void> {\n await mkdir(chainsDir, { recursive: true });\n index.updatedAt = new Date().toISOString();\n await writeFile(chainIndexPath(chainsDir), JSON.stringify(index, null, 2), \"utf8\");\n}\n\nasync function persistEdge(chainsDir: string, edge: CausalEdge): Promise<string> {\n const filePath = edgeFilePath(chainsDir, edge);\n await mkdir(path.dirname(filePath), { recursive: true });\n await writeFile(filePath, JSON.stringify(edge, null, 2), \"utf8\");\n return filePath;\n}\n\nfunction addEdgeToIndex(index: CausalChainIndex, edge: CausalEdge): void {\n if (!index.outgoing[edge.fromTrajectoryId]) {\n index.outgoing[edge.fromTrajectoryId] = [];\n }\n if (!index.outgoing[edge.fromTrajectoryId].includes(edge.edgeId)) {\n index.outgoing[edge.fromTrajectoryId].push(edge.edgeId);\n }\n\n if (!index.incoming[edge.toTrajectoryId]) {\n index.incoming[edge.toTrajectoryId] = [];\n }\n if (!index.incoming[edge.toTrajectoryId].includes(edge.edgeId)) {\n index.incoming[edge.toTrajectoryId].push(edge.edgeId);\n }\n\n index.edges[edge.edgeId] = edge;\n}\n\n// ─── Stitching Algorithm ─────────────────────────────────────────────────────\n\n/**\n * Score how strongly a candidate trajectory from another session\n * relates to the new trajectory being recorded.\n */\nexport function scoreStitchCandidate(\n newTrajectory: CausalTrajectoryRecord,\n candidate: CausalTrajectoryRecord,\n): StitchCandidate {\n let score = 0;\n let dominantMethod: CausalEdge[\"stitchMethod\"] = \"lexical\";\n let maxComponent = 0;\n\n // 1. Follow-up → Goal match (token overlap)\n const newFollowUpTokens = new Set(\n normalizeRecallTokens(newTrajectory.followUpSummary ?? \"\", []),\n );\n const candidateGoalTokens = normalizeRecallTokens(candidate.goal, []);\n if (newFollowUpTokens.size > 0 && candidateGoalTokens.length > 0) {\n const overlap = countRecallTokenOverlap(newFollowUpTokens, candidate.goal, []);\n const normalized = overlap / Math.max(newFollowUpTokens.size, candidateGoalTokens.length);\n const component = normalized * STITCH_WEIGHTS.followUpToGoal;\n score += component;\n if (component > maxComponent) {\n maxComponent = component;\n dominantMethod = \"lexical\";\n }\n }\n\n // 1b. Candidate follow-up → New goal match (reverse direction)\n const candidateFollowUpTokens = new Set(\n normalizeRecallTokens(candidate.followUpSummary ?? \"\", []),\n );\n const newGoalTokens = normalizeRecallTokens(newTrajectory.goal, []);\n if (candidateFollowUpTokens.size > 0 && newGoalTokens.length > 0) {\n const overlap = countRecallTokenOverlap(candidateFollowUpTokens, newTrajectory.goal, []);\n const normalized = overlap / Math.max(candidateFollowUpTokens.size, newGoalTokens.length);\n const component = normalized * 3.0;\n score += component;\n if (component > maxComponent) {\n maxComponent = component;\n dominantMethod = \"lexical\";\n }\n }\n\n // 2. Outcome → Goal match\n const newOutcomeTokens = new Set(\n normalizeRecallTokens(newTrajectory.outcomeSummary, []),\n );\n if (newOutcomeTokens.size > 0) {\n const overlap = countRecallTokenOverlap(newOutcomeTokens, candidate.goal, []);\n const normalized = overlap / Math.max(newOutcomeTokens.size, candidateGoalTokens.length || 1);\n const component = normalized * STITCH_WEIGHTS.outcomeToGoal;\n score += component;\n if (component > maxComponent) {\n maxComponent = component;\n dominantMethod = \"lexical\";\n }\n }\n\n // 3. Entity overlap (Jaccard)\n const newEntities = newTrajectory.entityRefs ?? [];\n const candidateEntities = candidate.entityRefs ?? [];\n if (newEntities.length > 0 && candidateEntities.length > 0) {\n const entityJaccard = topicOverlapScore(newEntities, candidateEntities);\n const component = entityJaccard * STITCH_WEIGHTS.entityOverlap;\n score += component;\n if (component > maxComponent) {\n maxComponent = component;\n dominantMethod = \"entity\";\n }\n }\n\n // 4. Tag overlap (Jaccard)\n const newTags = newTrajectory.tags ?? [];\n const candidateTags = candidate.tags ?? [];\n if (newTags.length > 0 && candidateTags.length > 0) {\n const tagJaccard = topicOverlapScore(newTags, candidateTags);\n const component = tagJaccard * STITCH_WEIGHTS.tagOverlap;\n score += component;\n if (component > maxComponent) {\n maxComponent = component;\n dominantMethod = \"lexical\";\n }\n }\n\n // 5. Temporal proximity: 1/(1 + gapHours/24)\n const newMs = Date.parse(newTrajectory.recordedAt);\n const candidateMs = Date.parse(candidate.recordedAt);\n if (Number.isFinite(newMs) && Number.isFinite(candidateMs)) {\n const gapHours = Math.abs(newMs - candidateMs) / 3_600_000;\n const proximity = 1 / (1 + gapHours / 24);\n const component = proximity * STITCH_WEIGHTS.temporalProximity;\n score += component;\n if (component > maxComponent) {\n maxComponent = component;\n dominantMethod = \"temporal\";\n }\n }\n\n // Determine edge type by heuristic\n let edgeType: CausalEdge[\"edgeType\"] = \"continuation\";\n const goalTokens = new Set(normalizeRecallTokens(newTrajectory.goal, []));\n const candidateGoalSet = new Set(candidateGoalTokens);\n const goalOverlap = [...goalTokens].filter((t) => candidateGoalSet.has(t)).length;\n const goalSimilarity = goalTokens.size > 0 ? goalOverlap / goalTokens.size : 0;\n\n if (goalSimilarity > 0.7 && candidate.outcomeKind === \"failure\") {\n edgeType = \"retry\";\n } else if (goalSimilarity > 0.7 && newTrajectory.outcomeKind !== candidate.outcomeKind) {\n edgeType = \"correction\";\n } else if (newFollowUpTokens.size > 0 && candidateGoalTokens.length > 0) {\n const followUpGoalOverlap = countRecallTokenOverlap(newFollowUpTokens, candidate.goal, []);\n if (followUpGoalOverlap > 0) {\n edgeType = \"follow_up_to_goal\";\n }\n }\n\n return { trajectory: candidate, score, edgeType, stitchMethod: dominantMethod };\n}\n\n/**\n * Read trajectory records from the causal-trajectories store within\n * the lookback window, excluding the current session.\n */\nasync function readRecentTrajectories(\n memoryDir: string,\n causalTrajectoryStoreDir: string | undefined,\n currentSessionKey: string,\n lookbackDays: number,\n): Promise<CausalTrajectoryRecord[]> {\n const root = resolveCausalTrajectoryStoreDir(memoryDir, causalTrajectoryStoreDir);\n const trajectoriesDir = path.join(root, \"trajectories\");\n\n const files = await listJsonFiles(trajectoriesDir).catch(() => [] as string[]);\n if (files.length === 0) return [];\n\n const cutoff = Date.now() - lookbackDays * 86_400_000;\n const results: CausalTrajectoryRecord[] = [];\n\n for (const filePath of files) {\n try {\n const raw = await readJsonFile(filePath);\n if (!isRecord(raw)) continue;\n const sessionKey = typeof raw.sessionKey === \"string\" ? raw.sessionKey : \"\";\n if (sessionKey === currentSessionKey) continue;\n const recordedAt = typeof raw.recordedAt === \"string\" ? raw.recordedAt : \"\";\n const ms = Date.parse(recordedAt);\n if (!Number.isFinite(ms) || ms < cutoff) continue;\n results.push(raw as unknown as CausalTrajectoryRecord);\n } catch {\n // skip invalid files\n }\n }\n\n return results;\n}\n\n/**\n * After a new trajectory is recorded, attempt to stitch it to\n * existing trajectories from other sessions.\n */\nexport async function stitchCausalChain(options: {\n memoryDir: string;\n causalTrajectoryStoreDir?: string;\n newTrajectory: CausalTrajectoryRecord;\n config: StitchConfig;\n}): Promise<CausalEdge[]> {\n const { memoryDir, causalTrajectoryStoreDir, newTrajectory, config: stitchConfig } = options;\n const chainsDir = resolveChainsDir(memoryDir, causalTrajectoryStoreDir);\n\n const candidates = await readRecentTrajectories(\n memoryDir,\n causalTrajectoryStoreDir,\n newTrajectory.sessionKey,\n stitchConfig.lookbackDays,\n );\n\n if (candidates.length === 0) return [];\n\n const scored = candidates\n .map((c) => scoreStitchCandidate(newTrajectory, c))\n .filter((s) => s.score >= stitchConfig.minScore)\n .sort((a, b) => b.score - a.score)\n .slice(0, stitchConfig.maxEdgesPerTrajectory);\n\n if (scored.length === 0) return [];\n\n return enqueueChainMutation(chainsDir, async () => {\n const index = await readChainIndex(chainsDir);\n const newEdges: CausalEdge[] = [];\n\n for (const candidate of scored) {\n const edgeId = makeEdgeId(candidate.trajectory.trajectoryId, newTrajectory.trajectoryId);\n\n // Skip if edge already exists\n if (index.edges[edgeId]) continue;\n\n const edge: CausalEdge = {\n schemaVersion: 1,\n edgeId,\n fromTrajectoryId: candidate.trajectory.trajectoryId,\n toTrajectoryId: newTrajectory.trajectoryId,\n edgeType: candidate.edgeType,\n confidence: Math.min(1, candidate.score / 10),\n stitchMethod: candidate.stitchMethod,\n createdAt: new Date().toISOString(),\n };\n\n addEdgeToIndex(index, edge);\n await persistEdge(chainsDir, edge);\n newEdges.push(edge);\n }\n\n if (newEdges.length > 0) {\n await writeChainIndex(chainsDir, index);\n log.debug(`[cmc] stitched ${newEdges.length} causal edge(s) for trajectory ${newTrajectory.trajectoryId}`);\n }\n\n return newEdges;\n });\n}\n"],"mappings":"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","names":[]}