@remnic/core 9.6.18 → 9.6.19

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 (90) hide show
  1. package/dist/access-admin-ops-surface.js +9 -9
  2. package/dist/access-boundary.js +10 -10
  3. package/dist/access-cli.js +37 -36
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.js +16 -16
  6. package/dist/access-identity-continuity-surface.js +9 -9
  7. package/dist/access-lcm-surface.js +9 -9
  8. package/dist/access-mcp.js +15 -15
  9. package/dist/access-observe-write-surface.js +9 -9
  10. package/dist/access-operations-batch.js +12 -12
  11. package/dist/access-operations.js +14 -14
  12. package/dist/access-recall-surface.js +9 -9
  13. package/dist/access-schema.js +5 -5
  14. package/dist/access-service.js +9 -9
  15. package/dist/{capsule-crypto-RAARDY3M.js → capsule-crypto-CZJSLEFG.js} +2 -2
  16. package/dist/{chunk-EEIROWEG.js → chunk-2BFLDH5F.js} +2 -2
  17. package/dist/{chunk-UPSAT4PY.js → chunk-2MR3MFQB.js} +2 -2
  18. package/dist/{chunk-LNQUAJNC.js → chunk-437S3G37.js} +1 -1
  19. package/dist/{chunk-JZKR6ZAV.js → chunk-4Q2XWAHZ.js} +2 -2
  20. package/dist/{chunk-HR4NACR7.js → chunk-A2OFRQ5B.js} +5 -5
  21. package/dist/{chunk-D3YEDDCC.js → chunk-ARDRBJHD.js} +4 -4
  22. package/dist/{chunk-U4CWMKN6.js → chunk-B4LPRAWL.js} +2 -2
  23. package/dist/{chunk-L6NW2XSU.js → chunk-D3D677BV.js} +4 -4
  24. package/dist/{chunk-7WSLR2CU.js → chunk-DE6ENYLG.js} +2 -2
  25. package/dist/{chunk-DEG5ULFJ.js → chunk-DQ2CJYUM.js} +2 -2
  26. package/dist/{chunk-KTULX4YO.js → chunk-FLVL4L3L.js} +4 -4
  27. package/dist/{chunk-KJ3QGI63.js → chunk-GXLFDLX2.js} +6 -6
  28. package/dist/{chunk-MGN7VHWQ.js → chunk-HHAA35A3.js} +120 -7
  29. package/dist/chunk-HHAA35A3.js.map +1 -0
  30. package/dist/chunk-I6WYQSX4.js +99 -0
  31. package/dist/chunk-I6WYQSX4.js.map +1 -0
  32. package/dist/{chunk-JCEFF346.js → chunk-KEY3FEMP.js} +18 -18
  33. package/dist/{chunk-PYHOAXTI.js → chunk-LRGSLV7M.js} +4 -4
  34. package/dist/{chunk-CYWCKFCL.js → chunk-M5QKGHCR.js} +5 -5
  35. package/dist/{chunk-RSS3CZV7.js → chunk-MRQN5Q3I.js} +2 -2
  36. package/dist/{chunk-HLN7RROI.js → chunk-SFHI7JBE.js} +4 -4
  37. package/dist/{chunk-5TYS6NTK.js → chunk-XMNVHYI6.js} +84 -35
  38. package/dist/chunk-XMNVHYI6.js.map +1 -0
  39. package/dist/{chunk-ZZYLM6KQ.js → chunk-YDF4HL3W.js} +4 -4
  40. package/dist/cli.js +25 -25
  41. package/dist/contradiction/index.js +4 -4
  42. package/dist/{first-start-migration-VBJQL6OI.js → first-start-migration-4I3VDSYS.js} +4 -4
  43. package/dist/index.js +46 -45
  44. package/dist/lcm/index.js +3 -3
  45. package/dist/namespaces/migrate.js +4 -4
  46. package/dist/namespaces/search.js +3 -3
  47. package/dist/operator-toolkit.js +5 -5
  48. package/dist/orchestrator.js +39 -38
  49. package/dist/retrieval-agents.js +2 -2
  50. package/dist/search/factory.js +2 -2
  51. package/dist/search/index.js +4 -4
  52. package/dist/temporal-index.d.ts +42 -7
  53. package/dist/temporal-index.js +3 -1
  54. package/dist/temporal-timeline-recall.d.ts +24 -0
  55. package/dist/temporal-timeline-recall.js +8 -0
  56. package/dist/temporal-timeline-recall.js.map +1 -0
  57. package/dist/transfer/backup.js +3 -3
  58. package/dist/transfer/capsule-export.js +4 -4
  59. package/dist/transfer/capsule-import.js +3 -3
  60. package/dist/transfer/import-sqlite.js +2 -2
  61. package/package.json +2 -2
  62. package/src/lifecycle/tombstones.test.ts +78 -5
  63. package/src/orchestration/persistence-index.ts +15 -2
  64. package/src/orchestration/recall-internal.ts +77 -35
  65. package/src/temporal-index.test.ts +157 -0
  66. package/src/temporal-index.ts +211 -7
  67. package/src/temporal-timeline-recall.test.ts +70 -0
  68. package/src/temporal-timeline-recall.ts +111 -0
  69. package/dist/chunk-5TYS6NTK.js.map +0 -1
  70. package/dist/chunk-MGN7VHWQ.js.map +0 -1
  71. /package/dist/{capsule-crypto-RAARDY3M.js.map → capsule-crypto-CZJSLEFG.js.map} +0 -0
  72. /package/dist/{chunk-EEIROWEG.js.map → chunk-2BFLDH5F.js.map} +0 -0
  73. /package/dist/{chunk-UPSAT4PY.js.map → chunk-2MR3MFQB.js.map} +0 -0
  74. /package/dist/{chunk-LNQUAJNC.js.map → chunk-437S3G37.js.map} +0 -0
  75. /package/dist/{chunk-JZKR6ZAV.js.map → chunk-4Q2XWAHZ.js.map} +0 -0
  76. /package/dist/{chunk-HR4NACR7.js.map → chunk-A2OFRQ5B.js.map} +0 -0
  77. /package/dist/{chunk-D3YEDDCC.js.map → chunk-ARDRBJHD.js.map} +0 -0
  78. /package/dist/{chunk-U4CWMKN6.js.map → chunk-B4LPRAWL.js.map} +0 -0
  79. /package/dist/{chunk-L6NW2XSU.js.map → chunk-D3D677BV.js.map} +0 -0
  80. /package/dist/{chunk-7WSLR2CU.js.map → chunk-DE6ENYLG.js.map} +0 -0
  81. /package/dist/{chunk-DEG5ULFJ.js.map → chunk-DQ2CJYUM.js.map} +0 -0
  82. /package/dist/{chunk-KTULX4YO.js.map → chunk-FLVL4L3L.js.map} +0 -0
  83. /package/dist/{chunk-KJ3QGI63.js.map → chunk-GXLFDLX2.js.map} +0 -0
  84. /package/dist/{chunk-JCEFF346.js.map → chunk-KEY3FEMP.js.map} +0 -0
  85. /package/dist/{chunk-PYHOAXTI.js.map → chunk-LRGSLV7M.js.map} +0 -0
  86. /package/dist/{chunk-CYWCKFCL.js.map → chunk-M5QKGHCR.js.map} +0 -0
  87. /package/dist/{chunk-RSS3CZV7.js.map → chunk-MRQN5Q3I.js.map} +0 -0
  88. /package/dist/{chunk-HLN7RROI.js.map → chunk-SFHI7JBE.js.map} +0 -0
  89. /package/dist/{chunk-ZZYLM6KQ.js.map → chunk-YDF4HL3W.js.map} +0 -0
  90. /package/dist/{first-start-migration-VBJQL6OI.js.map → first-start-migration-4I3VDSYS.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createSearchBackend
3
- } from "./chunk-HLN7RROI.js";
3
+ } from "./chunk-SFHI7JBE.js";
4
4
  import {
5
5
  namespaceIdentityToken,
6
6
  normalizeNamespaceIdentity
@@ -404,4 +404,4 @@ export {
404
404
  namespaceCollectionName,
405
405
  NamespaceSearchRouter
406
406
  };
407
- //# sourceMappingURL=chunk-DEG5ULFJ.js.map
407
+ //# sourceMappingURL=chunk-DQ2CJYUM.js.map
@@ -1,12 +1,12 @@
1
- import {
2
- encryptCapsuleFile
3
- } from "./chunk-LNQUAJNC.js";
4
1
  import {
5
2
  CAPSULE_ID_PATTERN,
6
3
  CapsuleBlockSchema,
7
4
  ExportBundleV2Schema,
8
5
  ExportManifestV2Schema
9
6
  } from "./chunk-WEHSQBFR.js";
7
+ import {
8
+ encryptCapsuleFile
9
+ } from "./chunk-437S3G37.js";
10
10
  import {
11
11
  CAPSULE_SCHEMA_VERSION,
12
12
  EXPORT_FORMAT
@@ -261,4 +261,4 @@ export {
261
261
  exportCapsule,
262
262
  isValidCapsuleSince
263
263
  };
264
- //# sourceMappingURL=chunk-KTULX4YO.js.map
264
+ //# sourceMappingURL=chunk-FLVL4L3L.js.map
@@ -4,13 +4,13 @@ import {
4
4
  import {
5
5
  normalizeDreamsStatusWindowHours
6
6
  } from "./chunk-YF6FMLBT.js";
7
+ import {
8
+ runContradictionScan
9
+ } from "./chunk-PRTA6444.js";
7
10
  import {
8
11
  executeResolution,
9
12
  isValidResolutionVerb
10
13
  } from "./chunk-KQDNAXOM.js";
11
- import {
12
- runContradictionScan
13
- } from "./chunk-PRTA6444.js";
14
14
  import {
15
15
  isDefaultReviewNamespace,
16
16
  listPairs,
@@ -26,14 +26,14 @@ import {
26
26
  } from "./chunk-T4WDJPEZ.js";
27
27
  import {
28
28
  defineOperation
29
- } from "./chunk-RSS3CZV7.js";
29
+ } from "./chunk-MRQN5Q3I.js";
30
30
  import {
31
31
  enforceNamespaceAllowList,
32
32
  tokenCapabilityStore
33
33
  } from "./chunk-D7FOKIY5.js";
34
34
  import {
35
35
  EngramAccessInputError
36
- } from "./chunk-ZZYLM6KQ.js";
36
+ } from "./chunk-YDF4HL3W.js";
37
37
  import {
38
38
  projectTagProjectId
39
39
  } from "./chunk-BAVVI5GP.js";
@@ -453,4 +453,4 @@ defineOperation({ name: "graph_events", description: "SSE graph events.", schema
453
453
  defineOperation({ name: "chat_message", description: "Chat message.", schema: strictSchema({ message: S.str, chatSessionId: S.str }), handler: async (_i, _ctx) => ({ result: { ok: true } }) });
454
454
  defineOperation({ name: "chat_events", description: "SSE chat events.", schema: strictSchema({}), handler: async (_i, _ctx) => ({ result: { stream: "sse" } }) });
455
455
  defineOperation({ name: "adapters_status", description: "Adapter registry metadata (issue #1850 round 5). Marker op: the HTTP transport builds the response from its own adapterRegistry, but the op still must be registered so it appears in listRegisteredOperations and enforceTokenOp can gate it for scoped tokens.", schema: strictSchema({}), handler: async (_i, _ctx) => ({ result: { ok: true } }) });
456
- //# sourceMappingURL=chunk-KJ3QGI63.js.map
456
+ //# sourceMappingURL=chunk-GXLFDLX2.js.map
@@ -3,7 +3,7 @@ import { execFileSync } from "child_process";
3
3
  import * as crypto from "crypto";
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
6
- var INDEX_VERSION = 1;
6
+ var INDEX_VERSION = 2;
7
7
  var TEMPORAL_INDEX_FILE = "index_time.json";
8
8
  var TAG_INDEX_FILE = "index_tags.json";
9
9
  var TAG_INDEX_VERSION = 2;
@@ -185,10 +185,25 @@ function writeJsonAtomic(filePath, data) {
185
185
  }
186
186
  }
187
187
  }
188
+ function emptyTemporalIndex() {
189
+ return { version: INDEX_VERSION, dates: {}, events: {} };
190
+ }
191
+ function isRecord(value) {
192
+ return typeof value === "object" && value !== null && !Array.isArray(value);
193
+ }
194
+ function normalizeTemporalIndex(raw) {
195
+ if (!isRecord(raw)) return emptyTemporalIndex();
196
+ return {
197
+ version: typeof raw.version === "number" ? raw.version : 0,
198
+ dates: isRecord(raw.dates) ? raw.dates : {},
199
+ events: isRecord(raw.events) ? raw.events : {},
200
+ ...typeof raw.lastRebuildAt === "string" ? { lastRebuildAt: raw.lastRebuildAt } : {}
201
+ };
202
+ }
188
203
  function updateTemporalIndex(memoryDir, update) {
189
204
  const indexPath = temporalIndexPath(memoryDir);
190
205
  withIndexFileLock(indexPath, () => {
191
- const index = readJsonSafe(indexPath, { version: INDEX_VERSION, dates: {} });
206
+ const index = normalizeTemporalIndex(readJsonSafe(indexPath, emptyTemporalIndex()));
192
207
  update(index);
193
208
  writeJsonAtomic(indexPath, index);
194
209
  });
@@ -210,6 +225,49 @@ function isoDateFromTimestamp(isoString) {
210
225
  }
211
226
  return isoString.slice(0, 10);
212
227
  }
228
+ function normalizedIsoTimestamp(value) {
229
+ if (typeof value !== "string" || value.trim().length === 0) return void 0;
230
+ const ms = Date.parse(value);
231
+ if (!Number.isFinite(ms)) return void 0;
232
+ try {
233
+ return new Date(ms).toISOString();
234
+ } catch {
235
+ return void 0;
236
+ }
237
+ }
238
+ function temporalEventFromEntry(entry) {
239
+ const createdAt = normalizedIsoTimestamp(entry.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
240
+ const eventAt = normalizedIsoTimestamp(entry.validAt) ?? createdAt;
241
+ const observedAt = normalizedIsoTimestamp(entry.observedAt);
242
+ const validUntil = normalizedIsoTimestamp(entry.validUntil);
243
+ const sessionKey = typeof entry.sessionKey === "string" && entry.sessionKey.trim().length > 0 ? entry.sessionKey.replace(/[\u0000-\u001f\u007f]+/g, " ").trim() : void 0;
244
+ const searchTokenHashes = temporalSearchTokenHashes(entry.searchText);
245
+ return {
246
+ path: entry.path,
247
+ eventAt,
248
+ ...observedAt ? { observedAt } : {},
249
+ ...sessionKey ? { sessionKey } : {},
250
+ ...validUntil ? { validUntil } : {},
251
+ ...searchTokenHashes.length > 0 ? { searchTokenHashes } : {}
252
+ };
253
+ }
254
+ function temporalSearchTokens(value) {
255
+ if (!value) return [];
256
+ const tokens = value.toLowerCase().match(/[\p{L}\p{N}][\p{L}\p{N}'_-]*/gu)?.filter((token) => token.length > 1) ?? [];
257
+ return [...new Set(tokens)].slice(0, 128);
258
+ }
259
+ function temporalSearchTokenHashes(value) {
260
+ return temporalSearchTokens(value).map(
261
+ (token) => crypto.createHash("sha256").update(token).digest("hex")
262
+ );
263
+ }
264
+ function compareTemporalIndexEvents(left, right) {
265
+ const byEvent = left.eventAt.localeCompare(right.eventAt);
266
+ if (byEvent !== 0) return byEvent;
267
+ const byObservation = (left.observedAt ?? "").localeCompare(right.observedAt ?? "");
268
+ if (byObservation !== 0) return byObservation;
269
+ return left.path.localeCompare(right.path);
270
+ }
213
271
  function addPathToSet(record, key, p) {
214
272
  if (!record[key]) {
215
273
  record[key] = [];
@@ -435,13 +493,20 @@ function promptContainsAlias(prompt, alias) {
435
493
  const normalizedPrompt = ` ${normalizeAliasKey(prompt)} `;
436
494
  return normalizedPrompt.includes(` ${phrase} `);
437
495
  }
438
- function indexMemory(memoryDir, memoryPath, createdAt, tags) {
496
+ function indexMemory(memoryDir, memoryPath, createdAt, tags, temporal = {}) {
439
497
  try {
440
498
  ensureStateDir(memoryDir);
441
499
  const dateKey = isoDateFromTimestamp(createdAt);
442
500
  updateTemporalIndex(memoryDir, (index) => {
501
+ index.version = INDEX_VERSION;
443
502
  removePathFromAllSets(index.dates, memoryPath);
444
503
  addPathToSet(index.dates, dateKey, memoryPath);
504
+ index.events[memoryPath] = temporalEventFromEntry({
505
+ path: memoryPath,
506
+ createdAt,
507
+ tags,
508
+ ...temporal
509
+ });
445
510
  });
446
511
  updateTagIndex(memoryDir, (index) => {
447
512
  removePathFromAllTagEntries(index, memoryPath);
@@ -460,6 +525,7 @@ function deindexMemory(memoryDir, memoryPath, createdAt, tags) {
460
525
  const dateKey = isoDateFromTimestamp(createdAt);
461
526
  updateTemporalIndex(memoryDir, (index) => {
462
527
  removePathFromSet(index.dates, dateKey, memoryPath);
528
+ delete index.events[memoryPath];
463
529
  });
464
530
  updateTagIndex(memoryDir, (index) => {
465
531
  for (const tag of tags) {
@@ -478,6 +544,7 @@ function clearIndexes(memoryDir) {
478
544
  index.version = INDEX_VERSION;
479
545
  index.lastRebuildAt = void 0;
480
546
  index.dates = {};
547
+ index.events = {};
481
548
  });
482
549
  updateTagIndex(memoryDir, (index) => {
483
550
  index.version = TAG_INDEX_VERSION;
@@ -490,7 +557,9 @@ function clearIndexes(memoryDir) {
490
557
  }
491
558
  function indexesExist(memoryDir) {
492
559
  try {
493
- return fs.existsSync(temporalIndexPath(memoryDir)) && fs.existsSync(tagIndexPath(memoryDir));
560
+ if (!fs.existsSync(tagIndexPath(memoryDir))) return false;
561
+ const raw = JSON.parse(fs.readFileSync(temporalIndexPath(memoryDir), "utf8"));
562
+ return isRecord(raw) && raw.version === INDEX_VERSION && isRecord(raw.dates) && isRecord(raw.events);
494
563
  } catch {
495
564
  return false;
496
565
  }
@@ -500,10 +569,12 @@ function indexMemoriesBatch(memoryDir, entries) {
500
569
  try {
501
570
  ensureStateDir(memoryDir);
502
571
  updateTemporalIndex(memoryDir, (index) => {
572
+ index.version = INDEX_VERSION;
503
573
  for (const entry of entries) {
504
574
  const dateKey = isoDateFromTimestamp(entry.createdAt);
505
575
  removePathFromAllSets(index.dates, entry.path);
506
576
  addPathToSet(index.dates, dateKey, entry.path);
577
+ index.events[entry.path] = temporalEventFromEntry(entry);
507
578
  }
508
579
  });
509
580
  updateTagIndex(memoryDir, (index) => {
@@ -530,9 +601,9 @@ async function queryByDateRangeAsync(memoryDir, fromDate, toDate) {
530
601
  }
531
602
  let tIndex;
532
603
  try {
533
- tIndex = JSON.parse(raw);
604
+ tIndex = normalizeTemporalIndex(JSON.parse(raw));
534
605
  } catch {
535
- tIndex = { version: INDEX_VERSION, dates: {} };
606
+ tIndex = emptyTemporalIndex();
536
607
  }
537
608
  const end = toDate ?? new Date(Date.now() + 864e5).toISOString().slice(0, 10);
538
609
  const results = /* @__PURE__ */ new Set();
@@ -548,6 +619,47 @@ async function queryByDateRangeAsync(memoryDir, fromDate, toDate) {
548
619
  return null;
549
620
  }
550
621
  }
622
+ async function queryTemporalTimelineAsync(memoryDir, options = {}) {
623
+ try {
624
+ const raw = await fs.promises.readFile(temporalIndexPath(memoryDir), "utf8");
625
+ const parsed = JSON.parse(raw);
626
+ if (!isRecord(parsed) || parsed.version !== INDEX_VERSION || !isRecord(parsed.dates) || !isRecord(parsed.events)) return null;
627
+ const index = normalizeTemporalIndex(parsed);
628
+ const chronology = Object.values(index.events).filter(
629
+ (event) => typeof event?.path === "string" && event.path.length > 0 && normalizedIsoTimestamp(event.eventAt) !== void 0
630
+ ).map((event) => ({
631
+ path: event.path,
632
+ eventAt: normalizedIsoTimestamp(event.eventAt),
633
+ ...normalizedIsoTimestamp(event.observedAt) ? { observedAt: normalizedIsoTimestamp(event.observedAt) } : {},
634
+ ...typeof event.sessionKey === "string" && event.sessionKey.trim().length > 0 ? { sessionKey: event.sessionKey.trim() } : {},
635
+ ...normalizedIsoTimestamp(event.validUntil) ? { validUntil: normalizedIsoTimestamp(event.validUntil) } : {},
636
+ ...Array.isArray(event.searchTokenHashes) ? { searchTokenHashes: event.searchTokenHashes.filter((hash) => typeof hash === "string") } : {}
637
+ })).sort(compareTemporalIndexEvents);
638
+ const requestedLimit = typeof options.limit === "number" && Number.isFinite(options.limit) ? Math.max(0, Math.floor(options.limit)) : chronology.length;
639
+ if (requestedLimit === 0) return [];
640
+ if (chronology.length <= requestedLimit) return chronology;
641
+ const queryHashes = new Set(temporalSearchTokenHashes(options.query));
642
+ const scored = chronology.map((event) => ({
643
+ event,
644
+ score: (event.searchTokenHashes ?? []).reduce(
645
+ (sum, hash) => sum + (queryHashes.has(hash) ? 1 : 0),
646
+ 0
647
+ )
648
+ }));
649
+ if (scored.some(({ score }) => score > 0)) {
650
+ return scored.filter(({ score }) => score > 0).sort((left, right) => right.score - left.score || compareTemporalIndexEvents(left.event, right.event)).slice(0, requestedLimit).map(({ event }) => event).sort(compareTemporalIndexEvents);
651
+ }
652
+ const earlyCount = Math.ceil(requestedLimit / 2);
653
+ const lateCount = requestedLimit - earlyCount;
654
+ const selected = [
655
+ ...chronology.slice(0, earlyCount),
656
+ ...lateCount > 0 ? chronology.slice(-lateCount) : []
657
+ ];
658
+ return selected.sort(compareTemporalIndexEvents);
659
+ } catch {
660
+ return null;
661
+ }
662
+ }
551
663
  async function queryByTagsAsync(memoryDir, tags) {
552
664
  if (tags.length === 0) return null;
553
665
  try {
@@ -829,6 +941,7 @@ export {
829
941
  indexesExist,
830
942
  indexMemoriesBatch,
831
943
  queryByDateRangeAsync,
944
+ queryTemporalTimelineAsync,
832
945
  queryByTagsAsync,
833
946
  extractTagsFromPrompt,
834
947
  resolvePromptTagPrefilterAsync,
@@ -836,4 +949,4 @@ export {
836
949
  recencyWindowFromPrompt,
837
950
  recencyWindowBoundsFromPrompt
838
951
  };
839
- //# sourceMappingURL=chunk-MGN7VHWQ.js.map
952
+ //# sourceMappingURL=chunk-HHAA35A3.js.map
@@ -0,0 +1 @@
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 * Per-memory event-time rows used to reconstruct one chronology across\n * source sessions. Keyed by path so an update replaces the prior row.\n */\n events: Record<string, TemporalIndexEvent>;\n}\n\nexport interface TemporalIndexEvent {\n path: string;\n /** Event/valid time used for chronology. */\n eventAt: string;\n /** Ingest/source-observation time, when known. */\n observedAt?: string;\n /** Source session provenance, when the extractor recorded it. */\n sessionKey?: string;\n /** Exclusive validity end, when the event is bounded. */\n validUntil?: string;\n /** SHA-256 lexical fingerprints for bounded query-aware candidate selection. */\n searchTokenHashes?: string[];\n}\n\nexport interface TemporalIndexEntry {\n path: string;\n createdAt: string;\n tags: string[];\n validAt?: string;\n observedAt?: string;\n sessionKey?: string;\n validUntil?: string;\n /** Used only to derive token hashes; raw text is never persisted in the index. */\n searchText?: 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 = 2;\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 fs.mkdirSync(dir, { recursive: true });\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 lockIsFresh(lockInfo: fs.Stats, owner: IndexLockOwner | null): boolean {\n const ownerCreatedAtMs =\n typeof owner?.createdAt === \"string\" && owner.createdAt.length > 0 ? Date.parse(owner.createdAt) : Number.NaN;\n const referenceMs = Number.isFinite(ownerCreatedAtMs) ? ownerCreatedAtMs : lockInfo.mtimeMs;\n return Date.now() - referenceMs < INDEX_LOCK_STALE_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) {\n if (lockOwnerIsRunning(owner)) return \"wait\";\n }\n if (owner === null && lockIsFresh(info, null)) return \"wait\";\n fs.rmSync(lockDir, { recursive: true, force: true });\n return \"removed\";\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === \"ENOENT\") return \"removed\";\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 === \"ENOENT\") {\n try {\n fs.mkdirSync(path.dirname(lockDir), { recursive: true });\n } catch {\n return;\n }\n sleepSync(INDEX_LOCK_POLL_MS);\n continue;\n }\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 emptyTemporalIndex(): TemporalIndex {\n return { version: INDEX_VERSION, dates: {}, events: {} };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction normalizeTemporalIndex(raw: TemporalIndex | null | undefined): TemporalIndex {\n if (!isRecord(raw)) return emptyTemporalIndex();\n return {\n version: typeof raw.version === \"number\" ? raw.version : 0,\n dates: isRecord(raw.dates) ? raw.dates as Record<string, string[]> : {},\n events: isRecord(raw.events) ? raw.events as Record<string, TemporalIndexEvent> : {},\n ...(typeof raw.lastRebuildAt === \"string\" ? { lastRebuildAt: raw.lastRebuildAt } : {}),\n };\n}\n\nfunction updateTemporalIndex(memoryDir: string, update: (index: TemporalIndex) => void): void {\n const indexPath = temporalIndexPath(memoryDir);\n withIndexFileLock(indexPath, () => {\n const index = normalizeTemporalIndex(readJsonSafe<TemporalIndex>(indexPath, emptyTemporalIndex()));\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 normalizedIsoTimestamp(value: string | undefined): string | undefined {\n if (typeof value !== \"string\" || value.trim().length === 0) return undefined;\n const ms = Date.parse(value);\n if (!Number.isFinite(ms)) return undefined;\n try {\n return new Date(ms).toISOString();\n } catch {\n return undefined;\n }\n}\n\nfunction temporalEventFromEntry(entry: TemporalIndexEntry): TemporalIndexEvent {\n const createdAt = normalizedIsoTimestamp(entry.createdAt) ?? new Date(0).toISOString();\n const eventAt = normalizedIsoTimestamp(entry.validAt) ?? createdAt;\n const observedAt = normalizedIsoTimestamp(entry.observedAt);\n const validUntil = normalizedIsoTimestamp(entry.validUntil);\n const sessionKey = typeof entry.sessionKey === \"string\" && entry.sessionKey.trim().length > 0\n ? entry.sessionKey.replace(/[\\u0000-\\u001f\\u007f]+/g, \" \").trim()\n : undefined;\n const searchTokenHashes = temporalSearchTokenHashes(entry.searchText);\n return {\n path: entry.path,\n eventAt,\n ...(observedAt ? { observedAt } : {}),\n ...(sessionKey ? { sessionKey } : {}),\n ...(validUntil ? { validUntil } : {}),\n ...(searchTokenHashes.length > 0 ? { searchTokenHashes } : {}),\n };\n}\n\nfunction temporalSearchTokens(value: string | undefined): string[] {\n if (!value) return [];\n const tokens = value\n .toLowerCase()\n .match(/[\\p{L}\\p{N}][\\p{L}\\p{N}'_-]*/gu)\n ?.filter((token) => token.length > 1) ?? [];\n return [...new Set(tokens)].slice(0, 128);\n}\n\nfunction temporalSearchTokenHashes(value: string | undefined): string[] {\n return temporalSearchTokens(value).map((token) =>\n crypto.createHash(\"sha256\").update(token).digest(\"hex\"),\n );\n}\n\nfunction compareTemporalIndexEvents(\n left: TemporalIndexEvent,\n right: TemporalIndexEvent,\n): number {\n const byEvent = left.eventAt.localeCompare(right.eventAt);\n if (byEvent !== 0) return byEvent;\n const byObservation = (left.observedAt ?? \"\").localeCompare(right.observedAt ?? \"\");\n if (byObservation !== 0) return byObservation;\n return left.path.localeCompare(right.path);\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 removePathFromAllSets(record: Record<string, string[]>, p: string): void {\n for (const key of Object.keys(record)) {\n removePathFromSet(record, key, p);\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 pruneTagAliases(index);\n }\n}\n\nfunction removePathFromAllTagEntries(index: TagIndex, memoryPath: string): void {\n for (const [canonical, nodeOrPaths] of Object.entries(index.tags)) {\n if (Array.isArray(nodeOrPaths)) {\n const paths = nodeOrPaths.filter((value) => value !== memoryPath);\n if (paths.length === 0) {\n delete index.tags[canonical];\n } else {\n index.tags[canonical] = paths;\n }\n continue;\n }\n\n nodeOrPaths.paths = nodeOrPaths.paths.filter((value) => value !== memoryPath);\n if (nodeOrPaths.paths.length === 0) {\n delete index.tags[canonical];\n }\n }\n pruneTagAliases(index);\n}\n\nfunction pruneTagAliases(index: TagIndex): void {\n if (!index.aliases) return;\n for (const [alias, canonicals] of Object.entries(index.aliases)) {\n const filtered = [...new Set(canonicals.map(normalizeCanonicalTag))].filter(\n (canonical) => canonical.length > 0 && index.tags[canonical] !== undefined\n );\n if (filtered.length === 0) {\n delete index.aliases[alias];\n } else {\n index.aliases[alias] = filtered;\n }\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(\n memoryDir: string,\n memoryPath: string,\n createdAt: string,\n tags: string[],\n temporal: Omit<Partial<TemporalIndexEntry>, \"path\" | \"createdAt\" | \"tags\"> = {},\n): void {\n try {\n ensureStateDir(memoryDir);\n\n const dateKey = isoDateFromTimestamp(createdAt);\n updateTemporalIndex(memoryDir, (index) => {\n index.version = INDEX_VERSION;\n removePathFromAllSets(index.dates, memoryPath);\n addPathToSet(index.dates, dateKey, memoryPath);\n index.events[memoryPath] = temporalEventFromEntry({\n path: memoryPath,\n createdAt,\n tags,\n ...temporal,\n });\n });\n\n updateTagIndex(memoryDir, (index) => {\n removePathFromAllTagEntries(index, memoryPath);\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 delete index.events[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 index.events = {};\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 if (!fs.existsSync(tagIndexPath(memoryDir))) return false;\n const raw = JSON.parse(fs.readFileSync(temporalIndexPath(memoryDir), \"utf8\")) as unknown;\n return isRecord(raw) && raw.version === INDEX_VERSION && isRecord(raw.dates) && isRecord(raw.events);\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: TemporalIndexEntry[]\n): void {\n if (entries.length === 0) return;\n try {\n ensureStateDir(memoryDir);\n\n updateTemporalIndex(memoryDir, (index) => {\n index.version = INDEX_VERSION;\n for (const entry of entries) {\n const dateKey = isoDateFromTimestamp(entry.createdAt);\n removePathFromAllSets(index.dates, entry.path);\n addPathToSet(index.dates, dateKey, entry.path);\n index.events[entry.path] = temporalEventFromEntry(entry);\n }\n });\n\n updateTagIndex(memoryDir, (index) => {\n for (const entry of entries) {\n removePathFromAllTagEntries(index, entry.path);\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 = normalizeTemporalIndex(JSON.parse(raw) as TemporalIndex);\n } catch {\n tIndex = emptyTemporalIndex();\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 * Return the persisted event-time rows as one deterministic cross-session\n * chronology. Equal event times are ordered by observation time, then path.\n * Returns `null` when the index is missing, corrupt, or from an older schema;\n * callers can then fail open to the pre-index recall path.\n */\nexport async function queryTemporalTimelineAsync(\n memoryDir: string,\n options: { query?: string; limit?: number } = {},\n): Promise<TemporalIndexEvent[] | null> {\n try {\n const raw = await fs.promises.readFile(temporalIndexPath(memoryDir), \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n if (\n !isRecord(parsed) ||\n parsed.version !== INDEX_VERSION ||\n !isRecord(parsed.dates) ||\n !isRecord(parsed.events)\n ) return null;\n const index = normalizeTemporalIndex(parsed as unknown as TemporalIndex);\n const chronology = Object.values(index.events)\n .filter((event) =>\n typeof event?.path === \"string\" &&\n event.path.length > 0 &&\n normalizedIsoTimestamp(event.eventAt) !== undefined\n )\n .map((event) => ({\n path: event.path,\n eventAt: normalizedIsoTimestamp(event.eventAt)!,\n ...(normalizedIsoTimestamp(event.observedAt)\n ? { observedAt: normalizedIsoTimestamp(event.observedAt) }\n : {}),\n ...(typeof event.sessionKey === \"string\" && event.sessionKey.trim().length > 0\n ? { sessionKey: event.sessionKey.trim() }\n : {}),\n ...(normalizedIsoTimestamp(event.validUntil)\n ? { validUntil: normalizedIsoTimestamp(event.validUntil) }\n : {}),\n ...(Array.isArray(event.searchTokenHashes)\n ? { searchTokenHashes: event.searchTokenHashes.filter((hash) => typeof hash === \"string\") }\n : {}),\n }))\n .sort(compareTemporalIndexEvents);\n const requestedLimit = typeof options.limit === \"number\" && Number.isFinite(options.limit)\n ? Math.max(0, Math.floor(options.limit))\n : chronology.length;\n if (requestedLimit === 0) return [];\n if (chronology.length <= requestedLimit) return chronology;\n\n const queryHashes = new Set(temporalSearchTokenHashes(options.query));\n const scored = chronology.map((event) => ({\n event,\n score: (event.searchTokenHashes ?? []).reduce(\n (sum, hash) => sum + (queryHashes.has(hash) ? 1 : 0),\n 0,\n ),\n }));\n if (scored.some(({ score }) => score > 0)) {\n return scored\n .filter(({ score }) => score > 0)\n .sort((left, right) => right.score - left.score || compareTemporalIndexEvents(left.event, right.event))\n .slice(0, requestedLimit)\n .map(({ event }) => event)\n .sort(compareTemporalIndexEvents);\n }\n\n // No lexical match (legacy rows or generic \"first/last\" query): retain\n // both chronology edges so earliest and latest questions remain answerable.\n const earlyCount = Math.ceil(requestedLimit / 2);\n const lateCount = requestedLimit - earlyCount;\n const selected = [\n ...chronology.slice(0, earlyCount),\n ...(lateCount > 0 ? chronology.slice(-lateCount) : []),\n ];\n return selected.sort(compareTemporalIndexEvents);\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 return null;\n }\n\n return queryByTagsFromIndex(gIndex, tags);\n } catch {\n return null;\n }\n}\n\nfunction queryByTagsFromIndex(index: TagIndex, tags: string[]): Set<string> {\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;\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 if (matched.size === 0) {\n return {\n matchedTags: [],\n expandedTags: [],\n paths: null,\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":[]}
@@ -0,0 +1,99 @@
1
+ // src/temporal-timeline-recall.ts
2
+ var QUERY_STOPWORDS = /* @__PURE__ */ new Set([
3
+ "a",
4
+ "an",
5
+ "and",
6
+ "did",
7
+ "do",
8
+ "first",
9
+ "happened",
10
+ "how",
11
+ "i",
12
+ "in",
13
+ "is",
14
+ "it",
15
+ "last",
16
+ "many",
17
+ "most",
18
+ "my",
19
+ "of",
20
+ "on",
21
+ "or",
22
+ "recent",
23
+ "recently",
24
+ "the",
25
+ "to",
26
+ "was",
27
+ "what",
28
+ "when",
29
+ "which",
30
+ "who",
31
+ "with"
32
+ ]);
33
+ function queryTerms(query) {
34
+ return [...new Set(
35
+ query.toLowerCase().match(/[\p{L}\p{N}][\p{L}\p{N}'_-]*/gu)?.filter((term) => term.length > 1 && !QUERY_STOPWORDS.has(term)) ?? []
36
+ )];
37
+ }
38
+ function relevanceScore(item, terms) {
39
+ if (terms.length === 0) return 1;
40
+ const haystack = `${item.memory.content} ${(item.memory.frontmatter.tags ?? []).join(" ")} ${item.memory.frontmatter.entityRef ?? ""}`.toLowerCase();
41
+ let score = 0;
42
+ for (const term of terms) {
43
+ if (haystack.includes(term)) score += 1;
44
+ }
45
+ return score;
46
+ }
47
+ function compareChronology(left, right) {
48
+ const eventOrder = left.eventAt.localeCompare(right.eventAt);
49
+ if (eventOrder !== 0) return eventOrder;
50
+ const observedOrder = (left.observedAt ?? "").localeCompare(right.observedAt ?? "");
51
+ if (observedOrder !== 0) return observedOrder;
52
+ return left.memory.frontmatter.id.localeCompare(right.memory.frontmatter.id);
53
+ }
54
+ function clip(text, maxChars) {
55
+ if (text.length <= maxChars) return text;
56
+ if (maxChars <= 1) return text.slice(0, maxChars);
57
+ return `${text.slice(0, maxChars - 1).trimEnd()}\u2026`;
58
+ }
59
+ function buildTemporalTimelineRecallSection(options) {
60
+ const maxChars = Number.isFinite(options.maxChars) ? Math.floor(options.maxChars) : 0;
61
+ const maxItems = Number.isFinite(options.maxItems) ? Math.floor(options.maxItems) : 0;
62
+ if (maxChars <= 0 || maxItems <= 0 || options.items.length === 0) return "";
63
+ const terms = queryTerms(options.query);
64
+ const uniqueById = /* @__PURE__ */ new Map();
65
+ for (const item of options.items) {
66
+ const id = item.memory.frontmatter.id;
67
+ const current = uniqueById.get(id);
68
+ if (!current || compareChronology(item, current) < 0) uniqueById.set(id, item);
69
+ }
70
+ const scored = [...uniqueById.values()].map((item) => ({
71
+ item,
72
+ score: relevanceScore(item, terms)
73
+ }));
74
+ const relevant = scored.some(({ score }) => score > 0) ? scored.filter(({ score }) => score > 0) : scored;
75
+ const selected = relevant.sort((left, right) => {
76
+ if (right.score !== left.score) return right.score - left.score;
77
+ return compareChronology(left.item, right.item);
78
+ }).slice(0, maxItems).map(({ item }) => item).sort(compareChronology);
79
+ const title = options.title ?? "Cross-session temporal timeline";
80
+ const lines = [
81
+ `## ${title}`,
82
+ "Ordered by event time across source sessions; event time is distinct from ingest time."
83
+ ];
84
+ for (const item of selected) {
85
+ const provenance = [
86
+ `event=${item.eventAt}`,
87
+ item.validUntil ? `until<${item.validUntil}` : "",
88
+ item.observedAt ? `observed=${item.observedAt}` : "",
89
+ item.sessionKey ? `session=${item.sessionKey}` : ""
90
+ ].filter(Boolean).join(", ");
91
+ lines.push(`- [${provenance}] ${item.memory.content.trim()}`);
92
+ }
93
+ return clip(lines.join("\n"), maxChars);
94
+ }
95
+
96
+ export {
97
+ buildTemporalTimelineRecallSection
98
+ };
99
+ //# sourceMappingURL=chunk-I6WYQSX4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/temporal-timeline-recall.ts"],"sourcesContent":["import type { MemoryFile } from \"./types.js\";\n\nexport interface TemporalTimelineRecallItem {\n memory: MemoryFile;\n eventAt: string;\n observedAt?: string;\n sessionKey?: string;\n validUntil?: string;\n}\n\nexport interface TemporalTimelineRecallOptions {\n items: readonly TemporalTimelineRecallItem[];\n query: string;\n maxChars: number;\n maxItems: number;\n title?: string;\n}\n\nconst QUERY_STOPWORDS = new Set([\n \"a\", \"an\", \"and\", \"did\", \"do\", \"first\", \"happened\", \"how\", \"i\", \"in\",\n \"is\", \"it\", \"last\", \"many\", \"most\", \"my\", \"of\", \"on\", \"or\", \"recent\",\n \"recently\", \"the\", \"to\", \"was\", \"what\", \"when\", \"which\", \"who\", \"with\",\n]);\n\nfunction queryTerms(query: string): string[] {\n return [...new Set(\n query\n .toLowerCase()\n .match(/[\\p{L}\\p{N}][\\p{L}\\p{N}'_-]*/gu)\n ?.filter((term) => term.length > 1 && !QUERY_STOPWORDS.has(term)) ?? [],\n )];\n}\n\nfunction relevanceScore(item: TemporalTimelineRecallItem, terms: readonly string[]): number {\n if (terms.length === 0) return 1;\n const haystack = `${item.memory.content} ${(item.memory.frontmatter.tags ?? []).join(\" \")} ${item.memory.frontmatter.entityRef ?? \"\"}`.toLowerCase();\n let score = 0;\n for (const term of terms) {\n if (haystack.includes(term)) score += 1;\n }\n return score;\n}\n\nfunction compareChronology(\n left: TemporalTimelineRecallItem,\n right: TemporalTimelineRecallItem,\n): number {\n const eventOrder = left.eventAt.localeCompare(right.eventAt);\n if (eventOrder !== 0) return eventOrder;\n const observedOrder = (left.observedAt ?? \"\").localeCompare(right.observedAt ?? \"\");\n if (observedOrder !== 0) return observedOrder;\n return left.memory.frontmatter.id.localeCompare(right.memory.frontmatter.id);\n}\n\nfunction clip(text: string, maxChars: number): string {\n if (text.length <= maxChars) return text;\n if (maxChars <= 1) return text.slice(0, maxChars);\n return `${text.slice(0, maxChars - 1).trimEnd()}…`;\n}\n\n/**\n * Build a query-focused timeline from ingest-indexed memory primitives.\n * Selection is relevance-first; presentation is always event-time ordered.\n */\nexport function buildTemporalTimelineRecallSection(\n options: TemporalTimelineRecallOptions,\n): string {\n const maxChars = Number.isFinite(options.maxChars) ? Math.floor(options.maxChars) : 0;\n const maxItems = Number.isFinite(options.maxItems) ? Math.floor(options.maxItems) : 0;\n if (maxChars <= 0 || maxItems <= 0 || options.items.length === 0) return \"\";\n\n const terms = queryTerms(options.query);\n const uniqueById = new Map<string, TemporalTimelineRecallItem>();\n for (const item of options.items) {\n const id = item.memory.frontmatter.id;\n const current = uniqueById.get(id);\n if (!current || compareChronology(item, current) < 0) uniqueById.set(id, item);\n }\n\n const scored = [...uniqueById.values()].map((item) => ({\n item,\n score: relevanceScore(item, terms),\n }));\n const relevant = scored.some(({ score }) => score > 0)\n ? scored.filter(({ score }) => score > 0)\n : scored;\n const selected = relevant\n .sort((left, right) => {\n if (right.score !== left.score) return right.score - left.score;\n return compareChronology(left.item, right.item);\n })\n .slice(0, maxItems)\n .map(({ item }) => item)\n .sort(compareChronology);\n\n const title = options.title ?? \"Cross-session temporal timeline\";\n const lines = [\n `## ${title}`,\n \"Ordered by event time across source sessions; event time is distinct from ingest time.\",\n ];\n for (const item of selected) {\n const provenance = [\n `event=${item.eventAt}`,\n item.validUntil ? `until<${item.validUntil}` : \"\",\n item.observedAt ? `observed=${item.observedAt}` : \"\",\n item.sessionKey ? `session=${item.sessionKey}` : \"\",\n ].filter(Boolean).join(\", \");\n lines.push(`- [${provenance}] ${item.memory.content.trim()}`);\n }\n return clip(lines.join(\"\\n\"), maxChars);\n}\n"],"mappings":"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","names":[]}