@remnic/core 9.3.612 → 9.3.614

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 (374) hide show
  1. package/dist/access-cli.js +58 -57
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +4 -2
  4. package/dist/access-http.js +22 -22
  5. package/dist/access-mcp.d.ts +9 -2
  6. package/dist/access-mcp.js +19 -19
  7. package/dist/access-schema.d.ts +12 -12
  8. package/dist/access-schema.js +3 -3
  9. package/dist/{access-service-D2J9dh_9.d.ts → access-service-DGG_2xPK.d.ts} +1 -1
  10. package/dist/access-service.d.ts +2 -2
  11. package/dist/access-service.js +16 -16
  12. package/dist/active-recall.js +20 -3
  13. package/dist/active-recall.js.map +1 -1
  14. package/dist/adapters/index.js +4 -4
  15. package/dist/adapters/registry.js +2 -2
  16. package/dist/behavior-learner.js +2 -3
  17. package/dist/behavior-learner.js.map +1 -1
  18. package/dist/bootstrap.d.ts +1 -1
  19. package/dist/briefing.js +3 -3
  20. package/dist/buffer.d.ts +1 -1
  21. package/dist/buffer.js +1 -1
  22. package/dist/calibration.d.ts +5 -2
  23. package/dist/calibration.js +7 -5
  24. package/dist/calibration.js.map +1 -1
  25. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
  26. package/dist/causal-consolidation.d.ts +8 -2
  27. package/dist/causal-consolidation.js +13 -11
  28. package/dist/causal-consolidation.js.map +1 -1
  29. package/dist/{chunk-3BP57I6J.js → chunk-2F6NP3NT.js} +2 -1
  30. package/dist/{chunk-3BP57I6J.js.map → chunk-2F6NP3NT.js.map} +1 -1
  31. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  32. package/dist/{chunk-HSVJGWYS.js → chunk-2ROPI5OE.js} +2 -2
  33. package/dist/{chunk-C4SQJZAF.js → chunk-2SGJY2UY.js} +6 -3
  34. package/dist/chunk-2SGJY2UY.js.map +1 -0
  35. package/dist/{chunk-ZDTVJXIP.js → chunk-3MAONBX3.js} +13 -5
  36. package/dist/chunk-3MAONBX3.js.map +1 -0
  37. package/dist/{chunk-G3Z3QEF5.js → chunk-3PY7VHV7.js} +2 -2
  38. package/dist/chunk-3PY7VHV7.js.map +1 -0
  39. package/dist/{chunk-CF3ZF2YU.js → chunk-3QSU4NFF.js} +3 -3
  40. package/dist/{chunk-AJA46VX5.js → chunk-3T74IZB3.js} +11 -2
  41. package/dist/chunk-3T74IZB3.js.map +1 -0
  42. package/dist/{chunk-KVEVLBKC.js → chunk-4HFJQCJZ.js} +13 -8
  43. package/dist/chunk-4HFJQCJZ.js.map +1 -0
  44. package/dist/{chunk-KGK2QKWL.js → chunk-4R4KTDIE.js} +1 -1
  45. package/dist/chunk-4R4KTDIE.js.map +1 -0
  46. package/dist/{chunk-OI27U2HT.js → chunk-5BTCT236.js} +2 -2
  47. package/dist/{chunk-CO7ZO4TU.js → chunk-5VDJMYTF.js} +2 -2
  48. package/dist/{chunk-BFBF3XEF.js → chunk-6BDVBBBY.js} +33 -25
  49. package/dist/{chunk-BFBF3XEF.js.map → chunk-6BDVBBBY.js.map} +1 -1
  50. package/dist/{chunk-EAZGEEG2.js → chunk-6L46YAEZ.js} +45 -9
  51. package/dist/chunk-6L46YAEZ.js.map +1 -0
  52. package/dist/{chunk-YFS5OEKO.js → chunk-7MLB4NCL.js} +2 -2
  53. package/dist/{chunk-IOTENEVL.js → chunk-7YQFWOF7.js} +57 -50
  54. package/dist/chunk-7YQFWOF7.js.map +1 -0
  55. package/dist/{chunk-2QANQKSQ.js → chunk-ADNZVFXG.js} +15 -15
  56. package/dist/{chunk-LZ3VEOU5.js → chunk-AL4RAJL5.js} +22 -5
  57. package/dist/chunk-AL4RAJL5.js.map +1 -0
  58. package/dist/{chunk-557IAFPD.js → chunk-APRRL26Q.js} +2 -2
  59. package/dist/{chunk-QDDHYAKV.js → chunk-AZDOWD2L.js} +2 -2
  60. package/dist/{chunk-TH67Q46T.js → chunk-B6FDZPCF.js} +17 -9
  61. package/dist/chunk-B6FDZPCF.js.map +1 -0
  62. package/dist/{chunk-MLT75J5S.js → chunk-B6SU7YSE.js} +3 -3
  63. package/dist/{chunk-FXKPZ3H6.js → chunk-BPSGLMQ4.js} +2 -2
  64. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  65. package/dist/{chunk-NOMEVTUD.js → chunk-C6C7XVKG.js} +5 -4
  66. package/dist/chunk-C6C7XVKG.js.map +1 -0
  67. package/dist/{chunk-XKIQZXUB.js → chunk-CI7RKSRE.js} +7 -1
  68. package/dist/chunk-CI7RKSRE.js.map +1 -0
  69. package/dist/{chunk-IK34DVAC.js → chunk-CIOMS6DI.js} +2 -2
  70. package/dist/{chunk-2I5JGH3M.js → chunk-CYEPCZN5.js} +2 -2
  71. package/dist/{chunk-2I5JGH3M.js.map → chunk-CYEPCZN5.js.map} +1 -1
  72. package/dist/{chunk-JHMFYY7L.js → chunk-DCGT4FPP.js} +13 -5
  73. package/dist/chunk-DCGT4FPP.js.map +1 -0
  74. package/dist/{chunk-7DZRO2DC.js → chunk-DEPRLVLK.js} +2 -2
  75. package/dist/{chunk-CSKLPDN6.js → chunk-DEVUWMME.js} +52 -19
  76. package/dist/chunk-DEVUWMME.js.map +1 -0
  77. package/dist/{chunk-DHGSZ3UD.js → chunk-DGNQRNLL.js} +2 -2
  78. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  79. package/dist/chunk-FAV25DUZ.js +12 -0
  80. package/dist/chunk-FAV25DUZ.js.map +1 -0
  81. package/dist/{chunk-ETUPBUHB.js → chunk-GDASG7NC.js} +2 -2
  82. package/dist/{chunk-L227SKTB.js → chunk-GDB4J2H3.js} +17 -1
  83. package/dist/chunk-GDB4J2H3.js.map +1 -0
  84. package/dist/{chunk-IP73YCZP.js → chunk-GLPBYIXN.js} +4 -2
  85. package/dist/chunk-GLPBYIXN.js.map +1 -0
  86. package/dist/{chunk-4HP7HIE3.js → chunk-HP5FMB6L.js} +2 -2
  87. package/dist/{chunk-EVZFIAPG.js → chunk-IBTZEBUD.js} +23 -10
  88. package/dist/chunk-IBTZEBUD.js.map +1 -0
  89. package/dist/{chunk-DOX2CG6Y.js → chunk-IEUU7O4F.js} +2 -2
  90. package/dist/{chunk-JNANKJLN.js → chunk-JOASJWQR.js} +2 -2
  91. package/dist/chunk-JOASJWQR.js.map +1 -0
  92. package/dist/{chunk-WSGF57U2.js → chunk-JQDZQ4TB.js} +2 -2
  93. package/dist/{chunk-HINSGUA7.js → chunk-KBL3JJR6.js} +9 -13
  94. package/dist/chunk-KBL3JJR6.js.map +1 -0
  95. package/dist/{chunk-W7L6HXUC.js → chunk-LXOM6IQU.js} +2 -2
  96. package/dist/{chunk-G6R5UD3Q.js → chunk-MGN7VHWQ.js} +42 -1
  97. package/dist/{chunk-G6R5UD3Q.js.map → chunk-MGN7VHWQ.js.map} +1 -1
  98. package/dist/{chunk-DLJ4IR6M.js → chunk-MHQC2WU2.js} +2 -2
  99. package/dist/chunk-MHQC2WU2.js.map +1 -0
  100. package/dist/{chunk-6JGNHWCI.js → chunk-OBIRVF36.js} +3 -3
  101. package/dist/{chunk-CHCA44C3.js → chunk-ODPLEWB6.js} +3 -3
  102. package/dist/chunk-ODPLEWB6.js.map +1 -0
  103. package/dist/{chunk-HENLZHIT.js → chunk-OIF36KGD.js} +7 -4
  104. package/dist/chunk-OIF36KGD.js.map +1 -0
  105. package/dist/{chunk-GUPISBV2.js → chunk-PP2JH3GP.js} +2 -2
  106. package/dist/{chunk-OXJBNGBK.js → chunk-PSUB67YB.js} +2 -2
  107. package/dist/{chunk-UWY7GIVS.js → chunk-PYIFUBRK.js} +45 -13
  108. package/dist/chunk-PYIFUBRK.js.map +1 -0
  109. package/dist/{chunk-KIB7SDIJ.js → chunk-Q6YIJGXJ.js} +2 -2
  110. package/dist/{chunk-PPPZY2EU.js → chunk-QEMCQFDW.js} +2 -2
  111. package/dist/{chunk-ZT3EGNLR.js → chunk-QPD426WT.js} +2 -2
  112. package/dist/{chunk-RLV3PQGH.js → chunk-QVO4YOB7.js} +6 -6
  113. package/dist/{chunk-GMAG2HS4.js → chunk-RG3LBSGH.js} +46 -9
  114. package/dist/chunk-RG3LBSGH.js.map +1 -0
  115. package/dist/{chunk-XSWKORGM.js → chunk-S53OYO3F.js} +3 -1
  116. package/dist/chunk-S53OYO3F.js.map +1 -0
  117. package/dist/{chunk-YCN4BVDK.js → chunk-SCPFRKIT.js} +4 -2
  118. package/dist/chunk-SCPFRKIT.js.map +1 -0
  119. package/dist/{chunk-HJNQQICM.js → chunk-T5XWMMU2.js} +107 -50
  120. package/dist/chunk-T5XWMMU2.js.map +1 -0
  121. package/dist/{chunk-NZPF2SYV.js → chunk-T7N6KQGS.js} +138 -5
  122. package/dist/chunk-T7N6KQGS.js.map +1 -0
  123. package/dist/{chunk-VJXSUAO7.js → chunk-TNOWU6RP.js} +13 -10
  124. package/dist/chunk-TNOWU6RP.js.map +1 -0
  125. package/dist/{chunk-PCI747N2.js → chunk-TZVQQTG4.js} +48 -19
  126. package/dist/chunk-TZVQQTG4.js.map +1 -0
  127. package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
  128. package/dist/{chunk-IK7DCC5H.js → chunk-VMGLYN42.js} +2 -2
  129. package/dist/{chunk-5RPTH6AU.js → chunk-VPGUMLBA.js} +8 -7
  130. package/dist/chunk-VPGUMLBA.js.map +1 -0
  131. package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
  132. package/dist/chunk-WB3LYXC5.js.map +1 -0
  133. package/dist/{chunk-NSKYFGDL.js → chunk-X4QQB7O6.js} +2 -2
  134. package/dist/{chunk-HPWVAEET.js → chunk-X6IRLNOO.js} +3 -7
  135. package/dist/chunk-X6IRLNOO.js.map +1 -0
  136. package/dist/{chunk-46GJIW5M.js → chunk-XAZOWLW4.js} +5 -5
  137. package/dist/{chunk-46GJIW5M.js.map → chunk-XAZOWLW4.js.map} +1 -1
  138. package/dist/{chunk-XPSVGJYA.js → chunk-YRMKDTKF.js} +12 -9
  139. package/dist/chunk-YRMKDTKF.js.map +1 -0
  140. package/dist/{chunk-6ZZP4EJF.js → chunk-ZJR7VG5L.js} +3 -3
  141. package/dist/{chunk-6ZZP4EJF.js.map → chunk-ZJR7VG5L.js.map} +1 -1
  142. package/dist/{cli-OrfKXNU4.d.ts → cli-DWeu7eTY.d.ts} +6 -2
  143. package/dist/cli.d.ts +3 -3
  144. package/dist/cli.js +60 -59
  145. package/dist/compounding/engine.js +3 -3
  146. package/dist/compounding/preference-consolidator.js +39 -11
  147. package/dist/compounding/preference-consolidator.js.map +1 -1
  148. package/dist/config.js +1 -1
  149. package/dist/connectors/codex-materialize-runner.js +3 -3
  150. package/dist/connectors/index.js +3 -3
  151. package/dist/consolidation-provenance-check.js +1 -1
  152. package/dist/contradiction/index.js +4 -4
  153. package/dist/conversation-index/backend.js +2 -2
  154. package/dist/conversation-index/indexer.js +1 -1
  155. package/dist/cross-namespace-budget.js +1 -1
  156. package/dist/enrichment/index.js +1 -1
  157. package/dist/entity-retrieval.js +3 -3
  158. package/dist/evals.js +1 -1
  159. package/dist/explicit-capture.d.ts +1 -1
  160. package/dist/extraction-judge.js +8 -1
  161. package/dist/extraction.js +2 -2
  162. package/dist/fallback-llm.d.ts +23 -6
  163. package/dist/fallback-llm.js +5 -3
  164. package/dist/{first-start-migration-GYJWIH36.js → first-start-migration-FF7YFGRP.js} +6 -6
  165. package/dist/index.d.ts +3 -3
  166. package/dist/index.js +94 -93
  167. package/dist/index.js.map +1 -1
  168. package/dist/lcm/archive.js +2 -2
  169. package/dist/lcm/engine.js +5 -5
  170. package/dist/lcm/index.js +7 -7
  171. package/dist/lcm/summarizer.js +3 -3
  172. package/dist/maintenance/memory-governance-cron.d.ts +6 -4
  173. package/dist/maintenance/memory-governance-cron.js +1 -1
  174. package/dist/maintenance/memory-governance.js +3 -3
  175. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  176. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  177. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  178. package/dist/mcp-memory-inspector-app.js +1 -1
  179. package/dist/migrate/from-engram.js +1 -1
  180. package/dist/namespaces/migrate.js +16 -15
  181. package/dist/namespaces/search.js +12 -11
  182. package/dist/namespaces/storage.js +3 -3
  183. package/dist/network/webdav.d.ts +2 -0
  184. package/dist/network/webdav.js +1 -1
  185. package/dist/objective-state-writers.js +2 -2
  186. package/dist/operator-toolkit.d.ts +3 -1
  187. package/dist/operator-toolkit.js +21 -20
  188. package/dist/{orchestrator-DTRQG75J.d.ts → orchestrator-CqWOjfgl.d.ts} +46 -3
  189. package/dist/orchestrator.d.ts +1 -1
  190. package/dist/orchestrator.js +47 -44
  191. package/dist/patterns-cli.js +1 -1
  192. package/dist/qmd-recall-cache.d.ts +2 -0
  193. package/dist/qmd-recall-cache.js +1 -1
  194. package/dist/qmd.d.ts +37 -2
  195. package/dist/qmd.js +4 -1
  196. package/dist/recall-explain-renderer.js +3 -3
  197. package/dist/recall-planner-llm.d.ts +57 -0
  198. package/dist/recall-planner-llm.js +167 -0
  199. package/dist/recall-planner-llm.js.map +1 -0
  200. package/dist/recall-xray-cli.js +4 -4
  201. package/dist/recall-xray-renderer.js +3 -3
  202. package/dist/recall-xray.js +2 -2
  203. package/dist/resume-bundles.js +2 -2
  204. package/dist/retrieval-agents.js +2 -2
  205. package/dist/routing/store.js +1 -1
  206. package/dist/search/factory.js +11 -10
  207. package/dist/search/index.js +11 -10
  208. package/dist/search/lancedb-backend.d.ts +1 -1
  209. package/dist/search/lancedb-backend.js +3 -2
  210. package/dist/search/meilisearch-backend.d.ts +1 -1
  211. package/dist/search/meilisearch-backend.js +3 -2
  212. package/dist/search/noop-backend.d.ts +1 -1
  213. package/dist/search/noop-backend.js +1 -1
  214. package/dist/search/orama-backend.d.ts +1 -1
  215. package/dist/search/orama-backend.js +3 -2
  216. package/dist/search/port.d.ts +6 -1
  217. package/dist/search/port.js +7 -0
  218. package/dist/search/remote-backend.d.ts +1 -1
  219. package/dist/search/remote-backend.js +1 -1
  220. package/dist/semantic-consolidation.js +4 -4
  221. package/dist/semantic-rule-promotion.js +3 -3
  222. package/dist/semantic-rule-verifier.js +3 -3
  223. package/dist/session-observer-state.js +1 -1
  224. package/dist/storage.js +2 -2
  225. package/dist/summarizer.js +2 -2
  226. package/dist/temporal-index.js +1 -1
  227. package/dist/{tier-stats-SKML2OSF.js → tier-stats-3LYQ3VV5.js} +3 -3
  228. package/dist/transfer/backup.js +2 -2
  229. package/dist/transfer/capsule-export.js +2 -2
  230. package/dist/transfer/capsule-import.js +2 -2
  231. package/dist/transfer/export-sqlite.js +1 -1
  232. package/dist/types.d.ts +32 -0
  233. package/dist/types.js +1 -1
  234. package/dist/utility-learner.js +1 -1
  235. package/dist/utility-runtime.js +2 -2
  236. package/dist/verified-recall.js +3 -3
  237. package/dist/work/board.js +2 -2
  238. package/dist/work/storage.d.ts +2 -0
  239. package/dist/work/storage.js +1 -1
  240. package/package.json +1 -1
  241. package/src/access-http.ts +3 -0
  242. package/src/access-mcp.test.ts +51 -0
  243. package/src/access-mcp.ts +26 -5
  244. package/src/active-recall.test.ts +40 -0
  245. package/src/active-recall.ts +19 -2
  246. package/src/behavior-learner.ts +5 -3
  247. package/src/buffer-session.test.ts +58 -0
  248. package/src/buffer-surprise-trigger.test.ts +4 -18
  249. package/src/buffer.ts +39 -11
  250. package/src/calibration.ts +10 -4
  251. package/src/causal-consolidation.test.ts +47 -2
  252. package/src/causal-consolidation.ts +13 -9
  253. package/src/cli.ts +19 -4
  254. package/src/compounding/engine.ts +2 -0
  255. package/src/compounding/preference-consolidator.test.ts +292 -0
  256. package/src/compounding/preference-consolidator.ts +55 -19
  257. package/src/config.test.ts +213 -0
  258. package/src/config.ts +175 -4
  259. package/src/connectors/codex-materialize-runner.ts +7 -4
  260. package/src/consolidation-provenance-check.ts +24 -5
  261. package/src/conversation-index/indexer.test.ts +22 -0
  262. package/src/conversation-index/indexer.ts +7 -3
  263. package/src/cross-namespace-budget.test.ts +44 -21
  264. package/src/cross-namespace-budget.ts +2 -2
  265. package/src/enrichment/pipeline.ts +11 -16
  266. package/src/evals.ts +1 -1
  267. package/src/extraction-judge-chain.test.ts +55 -0
  268. package/src/extraction-judge.ts +7 -9
  269. package/src/extraction.ts +16 -5
  270. package/src/fallback-llm.test.ts +600 -1
  271. package/src/fallback-llm.ts +91 -22
  272. package/src/maintenance/memory-governance-cron.ts +39 -29
  273. package/src/mcp-memory-inspector-app.ts +54 -12
  274. package/src/message-parts/index.ts +6 -0
  275. package/src/message-parts/message-parts.test.ts +30 -0
  276. package/src/migrate/from-engram.ts +19 -5
  277. package/src/namespaces/search.test.ts +15 -2
  278. package/src/namespaces/search.ts +1 -1
  279. package/src/network/webdav.ts +61 -21
  280. package/src/operator-toolkit.ts +6 -2
  281. package/src/orchestrator.ts +173 -20
  282. package/src/qmd-client.test.ts +85 -0
  283. package/src/qmd-recall-cache.test.ts +16 -0
  284. package/src/qmd-recall-cache.ts +7 -0
  285. package/src/qmd.test.ts +54 -0
  286. package/src/qmd.ts +119 -19
  287. package/src/recall-planner-llm.test.ts +224 -0
  288. package/src/recall-planner-llm.ts +289 -0
  289. package/src/routing/store.ts +4 -8
  290. package/src/search/factory.ts +3 -0
  291. package/src/search/lancedb-backend.ts +15 -3
  292. package/src/search/meilisearch-backend.ts +70 -7
  293. package/src/search/noop-backend.ts +5 -1
  294. package/src/search/orama-backend.ts +15 -3
  295. package/src/search/port.ts +15 -0
  296. package/src/search/remote-backend.ts +5 -1
  297. package/src/session-observer-state.ts +1 -1
  298. package/src/summarizer.ts +3 -3
  299. package/src/temporal-index.test.ts +18 -0
  300. package/src/temporal-index.ts +45 -0
  301. package/src/training-export/cli-date-validation.test.ts +36 -0
  302. package/src/training-export/date-parse.ts +21 -2
  303. package/src/transfer/export-sqlite.ts +3 -0
  304. package/src/types.ts +35 -0
  305. package/src/utility-learner.ts +1 -0
  306. package/src/work/storage.ts +23 -0
  307. package/dist/chunk-5RPTH6AU.js.map +0 -1
  308. package/dist/chunk-AJA46VX5.js.map +0 -1
  309. package/dist/chunk-C4SQJZAF.js.map +0 -1
  310. package/dist/chunk-CHCA44C3.js.map +0 -1
  311. package/dist/chunk-CSKLPDN6.js.map +0 -1
  312. package/dist/chunk-DLJ4IR6M.js.map +0 -1
  313. package/dist/chunk-EAZGEEG2.js.map +0 -1
  314. package/dist/chunk-EVZFIAPG.js.map +0 -1
  315. package/dist/chunk-G3Z3QEF5.js.map +0 -1
  316. package/dist/chunk-GMAG2HS4.js.map +0 -1
  317. package/dist/chunk-HENLZHIT.js.map +0 -1
  318. package/dist/chunk-HINSGUA7.js.map +0 -1
  319. package/dist/chunk-HJNQQICM.js.map +0 -1
  320. package/dist/chunk-HPWVAEET.js.map +0 -1
  321. package/dist/chunk-IOTENEVL.js.map +0 -1
  322. package/dist/chunk-IP73YCZP.js.map +0 -1
  323. package/dist/chunk-JHMFYY7L.js.map +0 -1
  324. package/dist/chunk-JNANKJLN.js.map +0 -1
  325. package/dist/chunk-KGK2QKWL.js.map +0 -1
  326. package/dist/chunk-KM2A35EO.js.map +0 -1
  327. package/dist/chunk-KVEVLBKC.js.map +0 -1
  328. package/dist/chunk-L227SKTB.js.map +0 -1
  329. package/dist/chunk-LZ3VEOU5.js.map +0 -1
  330. package/dist/chunk-NOMEVTUD.js.map +0 -1
  331. package/dist/chunk-NZPF2SYV.js.map +0 -1
  332. package/dist/chunk-PCI747N2.js.map +0 -1
  333. package/dist/chunk-TH67Q46T.js.map +0 -1
  334. package/dist/chunk-UWY7GIVS.js.map +0 -1
  335. package/dist/chunk-VJXSUAO7.js.map +0 -1
  336. package/dist/chunk-XKIQZXUB.js.map +0 -1
  337. package/dist/chunk-XPSVGJYA.js.map +0 -1
  338. package/dist/chunk-XSWKORGM.js.map +0 -1
  339. package/dist/chunk-YCN4BVDK.js.map +0 -1
  340. package/dist/chunk-ZDTVJXIP.js.map +0 -1
  341. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
  342. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  343. /package/dist/{chunk-HSVJGWYS.js.map → chunk-2ROPI5OE.js.map} +0 -0
  344. /package/dist/{chunk-CF3ZF2YU.js.map → chunk-3QSU4NFF.js.map} +0 -0
  345. /package/dist/{chunk-OI27U2HT.js.map → chunk-5BTCT236.js.map} +0 -0
  346. /package/dist/{chunk-CO7ZO4TU.js.map → chunk-5VDJMYTF.js.map} +0 -0
  347. /package/dist/{chunk-YFS5OEKO.js.map → chunk-7MLB4NCL.js.map} +0 -0
  348. /package/dist/{chunk-2QANQKSQ.js.map → chunk-ADNZVFXG.js.map} +0 -0
  349. /package/dist/{chunk-557IAFPD.js.map → chunk-APRRL26Q.js.map} +0 -0
  350. /package/dist/{chunk-QDDHYAKV.js.map → chunk-AZDOWD2L.js.map} +0 -0
  351. /package/dist/{chunk-MLT75J5S.js.map → chunk-B6SU7YSE.js.map} +0 -0
  352. /package/dist/{chunk-FXKPZ3H6.js.map → chunk-BPSGLMQ4.js.map} +0 -0
  353. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  354. /package/dist/{chunk-IK34DVAC.js.map → chunk-CIOMS6DI.js.map} +0 -0
  355. /package/dist/{chunk-7DZRO2DC.js.map → chunk-DEPRLVLK.js.map} +0 -0
  356. /package/dist/{chunk-DHGSZ3UD.js.map → chunk-DGNQRNLL.js.map} +0 -0
  357. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  358. /package/dist/{chunk-ETUPBUHB.js.map → chunk-GDASG7NC.js.map} +0 -0
  359. /package/dist/{chunk-4HP7HIE3.js.map → chunk-HP5FMB6L.js.map} +0 -0
  360. /package/dist/{chunk-DOX2CG6Y.js.map → chunk-IEUU7O4F.js.map} +0 -0
  361. /package/dist/{chunk-WSGF57U2.js.map → chunk-JQDZQ4TB.js.map} +0 -0
  362. /package/dist/{chunk-W7L6HXUC.js.map → chunk-LXOM6IQU.js.map} +0 -0
  363. /package/dist/{chunk-6JGNHWCI.js.map → chunk-OBIRVF36.js.map} +0 -0
  364. /package/dist/{chunk-GUPISBV2.js.map → chunk-PP2JH3GP.js.map} +0 -0
  365. /package/dist/{chunk-OXJBNGBK.js.map → chunk-PSUB67YB.js.map} +0 -0
  366. /package/dist/{chunk-KIB7SDIJ.js.map → chunk-Q6YIJGXJ.js.map} +0 -0
  367. /package/dist/{chunk-PPPZY2EU.js.map → chunk-QEMCQFDW.js.map} +0 -0
  368. /package/dist/{chunk-ZT3EGNLR.js.map → chunk-QPD426WT.js.map} +0 -0
  369. /package/dist/{chunk-RLV3PQGH.js.map → chunk-QVO4YOB7.js.map} +0 -0
  370. /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
  371. /package/dist/{chunk-IK7DCC5H.js.map → chunk-VMGLYN42.js.map} +0 -0
  372. /package/dist/{chunk-NSKYFGDL.js.map → chunk-X4QQB7O6.js.map} +0 -0
  373. /package/dist/{first-start-migration-GYJWIH36.js.map → first-start-migration-FF7YFGRP.js.map} +0 -0
  374. /package/dist/{tier-stats-SKML2OSF.js.map → tier-stats-3LYQ3VV5.js.map} +0 -0
@@ -1 +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\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 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 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 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":[]}
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 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 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 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(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 removePathFromAllSets(index.dates, memoryPath);\n addPathToSet(index.dates, dateKey, memoryPath);\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 });\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 removePathFromAllSets(index.dates, entry.path);\n addPathToSet(index.dates, dateKey, entry.path);\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 = 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 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":[]}
@@ -179,7 +179,7 @@ var SessionObserverState = class {
179
179
  return merged;
180
180
  }
181
181
  async writeSessions(sessionsMap) {
182
- const sessions = {};
182
+ const sessions = /* @__PURE__ */ Object.create(null);
183
183
  for (const [key, value] of sessionsMap.entries()) {
184
184
  sessions[key] = value;
185
185
  }
@@ -323,4 +323,4 @@ export {
323
323
  normalizeObserverBands,
324
324
  SessionObserverState
325
325
  };
326
- //# sourceMappingURL=chunk-DLJ4IR6M.js.map
326
+ //# sourceMappingURL=chunk-MHQC2WU2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/session-observer-state.ts"],"sourcesContent":["import path from \"node:path\";\nimport { mkdir, open, readFile, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { log } from \"./logger.js\";\nimport type { SessionObserverBandConfig } from \"./types.js\";\nimport { cloneDefaultSessionObserverBands } from \"./session-observer-bands.js\";\n\ninterface SessionObserverCursor {\n sessionKey: string;\n cursorBytes: number;\n cursorTokens: number;\n lastObservedAt: string;\n lastTriggeredAt?: string;\n lastResetAt?: string;\n}\n\ninterface SessionObserverPersistedState {\n version: 1;\n sessions: Record<string, SessionObserverCursor>;\n}\n\nexport interface SessionObservationInput {\n sessionKey: string;\n totalBytes: number;\n totalTokens: number;\n observedAt?: string;\n}\n\nexport interface SessionObservationDecision {\n triggered: boolean;\n deltaBytes: number;\n deltaTokens: number;\n band: SessionObserverBandConfig;\n reason?: \"threshold\" | \"debounced\" | \"baseline\";\n}\n\nfunction sanitizeNonNegativeInt(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.max(0, Math.floor(value));\n}\n\nfunction parseIsoMs(value?: string): number {\n if (!value) return 0;\n const ms = Date.parse(value);\n return Number.isFinite(ms) ? ms : 0;\n}\n\nfunction mergeSessionCursor(\n existing: SessionObserverCursor,\n incoming: SessionObserverCursor,\n): SessionObserverCursor {\n const existingObservedMs = parseIsoMs(existing.lastObservedAt);\n const incomingObservedMs = parseIsoMs(incoming.lastObservedAt);\n const existingTriggeredMs = parseIsoMs(existing.lastTriggeredAt);\n const incomingTriggeredMs = parseIsoMs(incoming.lastTriggeredAt);\n const existingResetMs = parseIsoMs(existing.lastResetAt);\n const incomingResetMs = parseIsoMs(incoming.lastResetAt);\n\n const observedAt =\n incomingObservedMs >= existingObservedMs ? incoming.lastObservedAt : existing.lastObservedAt;\n const triggeredAt =\n incomingTriggeredMs >= existingTriggeredMs ? incoming.lastTriggeredAt : existing.lastTriggeredAt;\n\n // Preserve monotonic cursor progression except for explicit reset observations.\n const incomingIsNewer = incomingObservedMs >= existingObservedMs;\n const incomingHasNewerReset = incomingResetMs > existingResetMs;\n const allowIncomingReset = incomingIsNewer && incomingHasNewerReset;\n const keepExistingReset =\n existingResetMs > incomingResetMs && existingObservedMs >= incomingObservedMs;\n\n let cursorBytes = Math.max(\n sanitizeNonNegativeInt(existing.cursorBytes),\n sanitizeNonNegativeInt(incoming.cursorBytes),\n );\n let cursorTokens = Math.max(\n sanitizeNonNegativeInt(existing.cursorTokens),\n sanitizeNonNegativeInt(incoming.cursorTokens),\n );\n if (keepExistingReset) {\n cursorBytes = sanitizeNonNegativeInt(existing.cursorBytes);\n cursorTokens = sanitizeNonNegativeInt(existing.cursorTokens);\n } else if (allowIncomingReset) {\n cursorBytes = sanitizeNonNegativeInt(incoming.cursorBytes);\n cursorTokens = sanitizeNonNegativeInt(incoming.cursorTokens);\n }\n\n return {\n sessionKey: existing.sessionKey,\n cursorBytes,\n cursorTokens,\n lastObservedAt: observedAt,\n lastTriggeredAt: triggeredAt,\n lastResetAt:\n incomingResetMs >= existingResetMs ? incoming.lastResetAt : existing.lastResetAt,\n };\n}\n\nexport function normalizeObserverBands(\n bands: SessionObserverBandConfig[],\n): SessionObserverBandConfig[] {\n const normalized = bands\n .map((band) => ({\n maxBytes: sanitizeNonNegativeInt(band.maxBytes),\n triggerDeltaBytes: sanitizeNonNegativeInt(band.triggerDeltaBytes),\n triggerDeltaTokens: sanitizeNonNegativeInt(band.triggerDeltaTokens),\n }))\n .filter((band) => band.maxBytes > 0)\n .sort((a, b) => a.maxBytes - b.maxBytes);\n\n if (normalized.length === 0) {\n return cloneDefaultSessionObserverBands();\n }\n\n const last = normalized[normalized.length - 1];\n if (last && last.maxBytes < 1_000_000_000) {\n normalized.push({\n maxBytes: 1_000_000_000,\n triggerDeltaBytes: last.triggerDeltaBytes,\n triggerDeltaTokens: last.triggerDeltaTokens,\n });\n }\n return normalized;\n}\n\nexport class SessionObserverState {\n private readonly statePath: string;\n private readonly lockPath: string;\n private readonly lockStaleMs = 120_000;\n private readonly debounceMs: number;\n private readonly bands: SessionObserverBandConfig[];\n private sessions = new Map<string, SessionObserverCursor>();\n private saveQueue: Promise<void> = Promise.resolve();\n\n private async readPersistedState(): Promise<SessionObserverPersistedState | null> {\n try {\n const raw = await readFile(this.statePath, \"utf-8\");\n const parsed = JSON.parse(raw) as SessionObserverPersistedState;\n if (parsed?.version !== 1 || !parsed.sessions || typeof parsed.sessions !== \"object\") {\n return null;\n }\n return parsed;\n } catch {\n return null;\n }\n }\n\n private normalizePersistedSessions(\n sessions: Record<string, SessionObserverCursor>,\n ): Map<string, SessionObserverCursor> {\n const next = new Map<string, SessionObserverCursor>();\n for (const [sessionKey, value] of Object.entries(sessions)) {\n if (!value || typeof value !== \"object\") continue;\n next.set(sessionKey, {\n sessionKey,\n cursorBytes: sanitizeNonNegativeInt(value.cursorBytes),\n cursorTokens: sanitizeNonNegativeInt(value.cursorTokens),\n lastObservedAt:\n typeof value.lastObservedAt === \"string\" ? value.lastObservedAt : new Date(0).toISOString(),\n lastTriggeredAt: typeof value.lastTriggeredAt === \"string\" ? value.lastTriggeredAt : undefined,\n lastResetAt: typeof value.lastResetAt === \"string\" ? value.lastResetAt : undefined,\n });\n }\n return next;\n }\n\n constructor(opts: {\n memoryDir: string;\n debounceMs: number;\n bands: SessionObserverBandConfig[];\n }) {\n this.statePath = path.join(opts.memoryDir, \"state\", \"session-observer-state.json\");\n this.lockPath = path.join(opts.memoryDir, \"state\", \"session-observer-state.lock\");\n this.debounceMs = Math.max(0, Math.floor(opts.debounceMs));\n this.bands = normalizeObserverBands(opts.bands);\n }\n\n private async withSaveLock(fn: () => Promise<void>): Promise<void> {\n await mkdir(path.dirname(this.lockPath), { recursive: true });\n for (let attempt = 0; attempt < 80; attempt++) {\n try {\n const handle = await open(this.lockPath, \"wx\");\n try {\n await fn();\n } finally {\n await handle.close();\n await unlink(this.lockPath).catch(() => {});\n }\n return;\n } catch (err: any) {\n if (err?.code !== \"EEXIST\") throw err;\n try {\n const lockInfo = await stat(this.lockPath);\n if (Date.now() - lockInfo.mtimeMs > this.lockStaleMs) {\n await unlink(this.lockPath).catch(() => {});\n continue;\n }\n } catch {\n // Lock might have been released between EEXIST and stat/read.\n }\n await new Promise((resolve) => setTimeout(resolve, 25));\n }\n }\n const error = new Error(\"session observer save lock timeout\");\n log.debug(error.message);\n throw error;\n }\n\n async load(): Promise<void> {\n const parsed = await this.readPersistedState();\n if (!parsed) {\n this.sessions.clear();\n return;\n }\n this.sessions = this.normalizePersistedSessions(parsed.sessions);\n }\n\n async save(): Promise<void> {\n await this.withSaveLock(async () => {\n const merged = await this.readMergedSessions();\n this.sessions = merged;\n\n await this.writeSessions(merged);\n });\n }\n\n private async readMergedSessions(): Promise<Map<string, SessionObserverCursor>> {\n const merged = new Map<string, SessionObserverCursor>();\n const persisted = await this.readPersistedState();\n if (persisted) {\n for (const [key, value] of this.normalizePersistedSessions(persisted.sessions).entries()) {\n merged.set(key, value);\n }\n }\n for (const [key, current] of this.sessions.entries()) {\n const existing = merged.get(key);\n if (!existing) {\n merged.set(key, current);\n continue;\n }\n merged.set(key, mergeSessionCursor(existing, current));\n }\n return merged;\n }\n\n private async writeSessions(sessionsMap: Map<string, SessionObserverCursor>): Promise<void> {\n const sessions = Object.create(null) as Record<string, SessionObserverCursor>;\n for (const [key, value] of sessionsMap.entries()) {\n sessions[key] = value;\n }\n const payload: SessionObserverPersistedState = { version: 1, sessions };\n await mkdir(path.dirname(this.statePath), { recursive: true });\n await writeFile(this.statePath, JSON.stringify(payload, null, 2), \"utf-8\");\n }\n\n private enqueueObservation(\n input: SessionObservationInput,\n ): Promise<SessionObservationDecision> {\n const operation = this.saveQueue\n .catch(() => undefined)\n .then(() => this.observeWithSaveLock(input));\n this.saveQueue = operation.then(\n () => undefined,\n () => undefined,\n );\n return operation;\n }\n\n private bandForTotalBytes(totalBytes: number): SessionObserverBandConfig {\n const bytes = sanitizeNonNegativeInt(totalBytes);\n for (const band of this.bands) {\n if (bytes <= band.maxBytes) return band;\n }\n return this.bands[this.bands.length - 1];\n }\n\n async observe(input: SessionObservationInput): Promise<SessionObservationDecision> {\n return this.enqueueObservation(input);\n }\n\n private async observeWithSaveLock(\n input: SessionObservationInput,\n ): Promise<SessionObservationDecision> {\n let decision: SessionObservationDecision | undefined;\n await this.withSaveLock(async () => {\n const localExisting = this.sessions.get(input.sessionKey);\n const sessions = await this.readMergedSessions();\n decision = await this.observeMerged(\n input,\n sessions,\n localExisting ? { ...localExisting } : undefined,\n );\n });\n if (!decision) {\n throw new Error(\"session observer decision was not computed\");\n }\n return decision;\n }\n\n private async observeMerged(\n input: SessionObservationInput,\n sessions: Map<string, SessionObserverCursor>,\n localExisting?: SessionObserverCursor,\n ): Promise<SessionObservationDecision> {\n const nowIso = input.observedAt ?? new Date().toISOString();\n const totalBytes = sanitizeNonNegativeInt(input.totalBytes);\n const totalTokens = sanitizeNonNegativeInt(input.totalTokens);\n const band = this.bandForTotalBytes(totalBytes);\n\n const existing = sessions.get(input.sessionKey);\n if (!existing) {\n sessions.set(input.sessionKey, {\n sessionKey: input.sessionKey,\n cursorBytes: totalBytes,\n cursorTokens: totalTokens,\n lastObservedAt: nowIso,\n });\n this.sessions = sessions;\n await this.writeSessions(sessions);\n return {\n triggered: false,\n deltaBytes: 0,\n deltaTokens: 0,\n band,\n reason: \"baseline\",\n };\n }\n\n const session = { ...existing };\n const observedMs = parseIsoMs(nowIso);\n const existingObservedMs = parseIsoMs(session.lastObservedAt);\n const existingResetMs = parseIsoMs(session.lastResetAt);\n if (existingResetMs > 0 && observedMs < existingResetMs) {\n this.sessions = sessions;\n return { triggered: false, deltaBytes: 0, deltaTokens: 0, band, reason: \"baseline\" };\n }\n\n if (totalBytes < session.cursorBytes || totalTokens < session.cursorTokens) {\n const localSawReset =\n localExisting !== undefined\n && (totalBytes < sanitizeNonNegativeInt(localExisting.cursorBytes)\n || totalTokens < sanitizeNonNegativeInt(localExisting.cursorTokens));\n const canApplyReset = localSawReset && observedMs >= existingObservedMs;\n\n if (canApplyReset) {\n session.cursorBytes = totalBytes;\n session.cursorTokens = totalTokens;\n session.lastObservedAt = nowIso;\n session.lastResetAt = nowIso;\n } else if (observedMs >= existingObservedMs) {\n session.lastObservedAt = nowIso;\n }\n sessions.set(input.sessionKey, session);\n this.sessions = sessions;\n await this.writeSessions(sessions);\n return { triggered: false, deltaBytes: 0, deltaTokens: 0, band, reason: \"baseline\" };\n }\n\n const deltaBytes = totalBytes - session.cursorBytes;\n const deltaTokens = totalTokens - session.cursorTokens;\n const crossedThreshold =\n (band.triggerDeltaBytes > 0 && deltaBytes >= band.triggerDeltaBytes)\n || (band.triggerDeltaTokens > 0 && deltaTokens >= band.triggerDeltaTokens);\n session.lastObservedAt = nowIso;\n\n if (!crossedThreshold) {\n const unchanged = deltaBytes === 0 && deltaTokens === 0;\n if (!unchanged) {\n sessions.set(input.sessionKey, session);\n this.sessions = sessions;\n await this.writeSessions(sessions);\n } else {\n this.sessions = sessions;\n }\n return {\n triggered: false,\n deltaBytes,\n deltaTokens,\n band,\n };\n }\n\n const nowMs = Date.parse(nowIso);\n const lastTriggeredMs = session.lastTriggeredAt ? Date.parse(session.lastTriggeredAt) : NaN;\n const withinDebounce =\n Number.isFinite(lastTriggeredMs) && nowMs - lastTriggeredMs < this.debounceMs;\n\n if (withinDebounce) {\n sessions.set(input.sessionKey, session);\n this.sessions = sessions;\n await this.writeSessions(sessions);\n return {\n triggered: false,\n deltaBytes,\n deltaTokens,\n band,\n reason: \"debounced\",\n };\n }\n\n session.lastTriggeredAt = nowIso;\n session.cursorBytes = totalBytes;\n session.cursorTokens = totalTokens;\n sessions.set(input.sessionKey, session);\n this.sessions = sessions;\n await this.writeSessions(sessions);\n return {\n triggered: true,\n deltaBytes,\n deltaTokens,\n band,\n reason: \"threshold\",\n };\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  namespaceCollectionName
3
- } from "./chunk-6ZZP4EJF.js";
3
+ } from "./chunk-ZJR7VG5L.js";
4
4
  import {
5
5
  ALL_CATEGORY_DIRS,
6
6
  NamespaceStorageRouter
7
- } from "./chunk-IK7DCC5H.js";
7
+ } from "./chunk-VMGLYN42.js";
8
8
  import {
9
9
  namespaceIdentityFromToken,
10
10
  namespaceIdentityToken
@@ -202,4 +202,4 @@ export {
202
202
  verifyNamespaces,
203
203
  runNamespaceMigration
204
204
  };
205
- //# sourceMappingURL=chunk-6JGNHWCI.js.map
205
+ //# sourceMappingURL=chunk-OBIRVF36.js.map
@@ -74,7 +74,7 @@ var CrossNamespaceBudget = class {
74
74
  bucket.timestamps.push(normalizedNow);
75
75
  this.buckets.set(principal, bucket);
76
76
  const count = bucket.timestamps.length;
77
- if (count > hardLimit) {
77
+ if (count >= hardLimit) {
78
78
  bucket.timestamps.pop();
79
79
  if (bucket.timestamps.length === 0) {
80
80
  this.buckets.delete(principal);
@@ -140,7 +140,7 @@ var CrossNamespaceBudget = class {
140
140
  if (ts >= cutoff) liveCount++;
141
141
  }
142
142
  const projected = liveCount + 1;
143
- if (projected > hardLimit) {
143
+ if (projected >= hardLimit) {
144
144
  return { allowed: false, reason: "deny-over-hard", count: liveCount, limit };
145
145
  }
146
146
  if (projected > softLimit) {
@@ -210,4 +210,4 @@ export {
210
210
  DEFAULT_CROSS_NAMESPACE_BUDGET,
211
211
  CrossNamespaceBudget
212
212
  };
213
- //# sourceMappingURL=chunk-CHCA44C3.js.map
213
+ //# sourceMappingURL=chunk-ODPLEWB6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cross-namespace-budget.ts"],"sourcesContent":["/**\n * Per-principal cross-namespace query-budget limiter (issue #565 PR 4/5).\n *\n * Detects and throttles bursts of recall-type operations that a principal\n * issues against namespaces *other than their own*. Thresholds come from the\n * memory-extraction threat model (`docs/security/memory-extraction-threat-model.md`\n * §6.2) and the ADAM baseline report (`docs/security/adam-baseline-2026-04.md`):\n * a T2-class same-namespace attacker plateaus at 61 queries in the published\n * baseline, so the default window is set well below that to force any\n * adaptive loop to noticeably slow down.\n *\n * Shape:\n * - Pure, in-process, per-principal sliding window. No persistence.\n * - Only cross-namespace reads count: a principal hitting only their own\n * namespace is never throttled.\n * - The limiter is behind the `recallCrossNamespaceBudgetEnabled` feature\n * flag (defaults to `false`) and is a no-op when disabled. This mirrors\n * the canonical \"new filter/transform needs an enabled check\" pattern\n * (see CLAUDE.md gotcha #30).\n *\n * The module has no side effects beyond incrementing its own counters, and\n * it does NOT take a clock dependency — callers pass the current epoch ms\n * (or let the default `Date.now()` do it) so tests can step time\n * deterministically.\n */\n\nexport interface CrossNamespaceBudgetConfig {\n /** Feature flag. Defaults to false — a disabled limiter is always allow. */\n enabled?: boolean;\n /**\n * Rolling window size in milliseconds. Counts decay out of the window\n * as the clock advances. Default: 60_000 (1 minute).\n */\n windowMs?: number;\n /**\n * Soft cap. Once a principal has `softLimit` cross-namespace reads in the\n * window, the limiter *records* a warning on the decision but still\n * allows the call. Used by PR 5's anomaly detector to surface flags\n * without blocking. Default: 10.\n */\n softLimit?: number;\n /**\n * Hard cap. Once `hardLimit` is reached, the limiter denies the call.\n * Default: 30 — picked to be well below the T2 baseline of ~60 queries\n * at half-plateau, so an ADAM-style adaptive loop is throttled before it\n * meaningfully leaks.\n */\n hardLimit?: number;\n}\n\nexport const DEFAULT_CROSS_NAMESPACE_BUDGET: Required<CrossNamespaceBudgetConfig> =\n Object.freeze({\n enabled: false,\n windowMs: 60_000,\n softLimit: 10,\n hardLimit: 30,\n });\n\n/**\n * Why a call was denied / warned. Stable strings so callers can key log\n * lines and metrics on them.\n */\nexport type BudgetDecisionReason =\n | \"allowed-same-namespace\"\n | \"allowed-no-limit\"\n | \"allowed-under-soft\"\n | \"warn-over-soft\"\n | \"deny-over-hard\";\n\nexport interface BudgetDecision {\n allowed: boolean;\n reason: BudgetDecisionReason;\n /** Cross-namespace reads by this principal currently in the window. */\n count: number;\n /** Active config snapshot at decision time. */\n limit: {\n softLimit: number;\n hardLimit: number;\n windowMs: number;\n };\n}\n\ninterface PrincipalBucket {\n /** Epoch-ms timestamps of cross-namespace reads in the active window. */\n timestamps: number[];\n}\n\n/**\n * Normalize the provided config against the defaults and reject clearly\n * invalid shapes (non-positive windows, inverted limits). Never throws —\n * returns a safe effective config the limiter can use.\n */\nfunction effectiveConfig(\n raw: CrossNamespaceBudgetConfig | undefined,\n): Required<CrossNamespaceBudgetConfig> {\n const base = { ...DEFAULT_CROSS_NAMESPACE_BUDGET };\n if (!raw) return base;\n const out = { ...base };\n if (typeof raw.enabled === \"boolean\") out.enabled = raw.enabled;\n if (\n typeof raw.windowMs === \"number\" &&\n Number.isFinite(raw.windowMs) &&\n raw.windowMs > 0\n ) {\n out.windowMs = raw.windowMs;\n }\n if (\n typeof raw.softLimit === \"number\" &&\n Number.isFinite(raw.softLimit) &&\n raw.softLimit >= 0\n ) {\n out.softLimit = Math.floor(raw.softLimit);\n }\n if (\n typeof raw.hardLimit === \"number\" &&\n Number.isFinite(raw.hardLimit) &&\n raw.hardLimit >= 1\n ) {\n // Floor the value, then defensively require the floored result is\n // still >= 1. `raw.hardLimit = 0.5` previously passed the `> 0`\n // gate and floored to 0, turning a minor misconfiguration into a\n // full denial of cross-namespace reads. Now we fall back to the\n // default instead.\n const floored = Math.floor(raw.hardLimit);\n if (floored >= 1) out.hardLimit = floored;\n }\n if (out.softLimit > out.hardLimit) {\n // Inverted limits -> treat soft = hard so we never warn past the deny\n // threshold. Defensive, should never happen with well-formed config.\n out.softLimit = out.hardLimit;\n }\n return out;\n}\n\nfunction normalizeClock(now: number | undefined): number {\n if (now === undefined) return Date.now();\n if (Number.isFinite(now)) return Math.floor(now);\n return 0;\n}\n\n/**\n * In-process cross-namespace budget limiter. Instantiate once per\n * orchestrator / access-service.\n *\n * Threadsafe-by-construction: Node.js is single-threaded per process for\n * application code, and the limiter never awaits between read-modify-write\n * operations on its internal state.\n */\nexport class CrossNamespaceBudget {\n private readonly config: Required<CrossNamespaceBudgetConfig>;\n private readonly buckets = new Map<string, PrincipalBucket>();\n\n constructor(config?: CrossNamespaceBudgetConfig) {\n this.config = effectiveConfig(config);\n }\n\n /** Exposed for tests / audit surfaces. Never mutate the returned value. */\n getConfig(): Required<CrossNamespaceBudgetConfig> {\n return this.config;\n }\n\n /**\n * Check whether `principal` is allowed to issue another cross-namespace\n * read. Call site is expected to compare `principalNamespace` against\n * `queryNamespace` and only pass through reads where they differ — the\n * limiter treats every call as a cross-namespace event.\n *\n * @param principal Stable identifier for the calling principal (token\n * subject, session principal, etc.). Must be non-empty.\n * @param now Epoch-ms clock read. Defaults to `Date.now()`; tests pass a\n * fixed value to step time deterministically.\n */\n record(principal: string, now?: number): BudgetDecision {\n const normalizedNow = normalizeClock(now);\n const { enabled, windowMs, softLimit, hardLimit } = this.config;\n const limit = { softLimit, hardLimit, windowMs };\n\n if (!enabled) {\n return {\n allowed: true,\n reason: \"allowed-no-limit\",\n count: 0,\n limit,\n };\n }\n\n if (typeof principal !== \"string\" || principal.length === 0) {\n // A missing principal means \"we can't attribute this call\". Rather\n // than fail open, treat it as a cross-namespace event against a\n // shared bucket — denial-of-service risk is bounded because the\n // bucket is scoped per-process.\n principal = \"__anonymous__\";\n }\n\n const bucket = this.buckets.get(principal) ?? { timestamps: [] };\n const cutoff = normalizedNow - windowMs;\n // Drop timestamps that slid out of the window.\n while (bucket.timestamps.length > 0 && bucket.timestamps[0]! < cutoff) {\n bucket.timestamps.shift();\n }\n\n // Count the current call against the window BEFORE deciding — a call\n // that crosses the deny threshold should itself be denied, not the\n // next one. This is what the threat model calls \"fail at the Nth,\n // not the (N+1)th\".\n bucket.timestamps.push(normalizedNow);\n this.buckets.set(principal, bucket);\n const count = bucket.timestamps.length;\n\n if (count >= hardLimit) {\n // Denied: roll back the timestamp we just added so a repeated denied\n // call does not push the bucket further into the future. This keeps\n // the limiter stateless with respect to denied attempts.\n bucket.timestamps.pop();\n // Evict empty buckets (e.g. the first record after a long idle\n // rolled the only timestamp out, then got denied and rolled back).\n // Prevents unbounded map growth across many transient principals.\n if (bucket.timestamps.length === 0) {\n this.buckets.delete(principal);\n }\n return {\n allowed: false,\n reason: \"deny-over-hard\",\n count: bucket.timestamps.length,\n limit,\n };\n }\n\n if (count > softLimit) {\n return {\n allowed: true,\n reason: \"warn-over-soft\",\n count,\n limit,\n };\n }\n\n return {\n allowed: true,\n reason: \"allowed-under-soft\",\n count,\n limit,\n };\n }\n\n /**\n * Read-only peek at whether a call would be allowed, WITHOUT recording a\n * timestamp. Useful when the caller must inspect multiple namespaces before\n * deciding to record a single event. The returned `count` reflects the\n * current window state at call time.\n */\n peek(args: {\n principal: string;\n principalNamespace: string;\n queryNamespace: string;\n now?: number;\n }): BudgetDecision {\n const pn = args.principalNamespace;\n const qn = args.queryNamespace;\n const bothPresent =\n typeof pn === \"string\" && pn.length > 0 &&\n typeof qn === \"string\" && qn.length > 0;\n if (bothPresent && pn === qn) {\n return {\n allowed: true,\n reason: \"allowed-same-namespace\",\n count: 0,\n limit: {\n softLimit: this.config.softLimit,\n hardLimit: this.config.hardLimit,\n windowMs: this.config.windowMs,\n },\n };\n }\n // Cross-namespace: simulate what record() would do without the push.\n const { enabled, windowMs, softLimit, hardLimit } = this.config;\n const limit = { softLimit, hardLimit, windowMs };\n if (!enabled) {\n return { allowed: true, reason: \"allowed-no-limit\", count: 0, limit };\n }\n let principal = args.principal;\n if (typeof principal !== \"string\" || principal.length === 0) {\n principal = \"__anonymous__\";\n }\n const now = normalizeClock(args.now);\n const bucket = this.buckets.get(principal) ?? { timestamps: [] };\n const cutoff = now - windowMs;\n let liveCount = 0;\n for (const ts of bucket.timestamps) {\n if (ts >= cutoff) liveCount++;\n }\n const projected = liveCount + 1; // +1 for the current call\n if (projected >= hardLimit) {\n return { allowed: false, reason: \"deny-over-hard\", count: liveCount, limit };\n }\n if (projected > softLimit) {\n return { allowed: true, reason: \"warn-over-soft\", count: projected, limit };\n }\n return { allowed: true, reason: \"allowed-under-soft\", count: projected, limit };\n }\n\n /**\n * Convenience guard that also skips the limiter when `principalNamespace`\n * equals `queryNamespace` (same-namespace is never cross-namespace).\n * Returns an `allowed-same-namespace` decision in that case.\n */\n check(args: {\n principal: string;\n principalNamespace: string;\n queryNamespace: string;\n now?: number;\n }): BudgetDecision {\n // Same-namespace short-circuit requires BOTH namespaces to be\n // non-empty strings. Two empty/undefined namespaces at runtime\n // would otherwise compare equal and fail-open — a critical bypass\n // in a security-critical module. Force the limiter to engage when\n // either side is missing so we never silently skip enforcement.\n const pn = args.principalNamespace;\n const qn = args.queryNamespace;\n const bothPresent =\n typeof pn === \"string\" && pn.length > 0 &&\n typeof qn === \"string\" && qn.length > 0;\n if (bothPresent && pn === qn) {\n return {\n allowed: true,\n reason: \"allowed-same-namespace\",\n count: 0,\n limit: {\n softLimit: this.config.softLimit,\n hardLimit: this.config.hardLimit,\n windowMs: this.config.windowMs,\n },\n };\n }\n return this.record(args.principal, args.now);\n }\n\n /**\n * Clear all state. Intended for tests and for the orchestrator's\n * lifecycle `before_reset` hook.\n */\n reset(): void {\n this.buckets.clear();\n }\n\n /**\n * Evict buckets whose entire timestamp list has slid out of the\n * active window by `now`. Intended to be called periodically by a\n * long-lived host process (e.g. from a maintenance cron) that sees\n * many transient principals. Safe to call at any time; returns the\n * number of buckets evicted.\n */\n gc(now?: number): number {\n const normalizedNow = normalizeClock(now);\n const cutoff = normalizedNow - this.config.windowMs;\n let evicted = 0;\n for (const [principal, bucket] of this.buckets.entries()) {\n while (bucket.timestamps.length > 0 && bucket.timestamps[0]! < cutoff) {\n bucket.timestamps.shift();\n }\n if (bucket.timestamps.length === 0) {\n this.buckets.delete(principal);\n evicted++;\n }\n }\n return evicted;\n }\n\n /** For tests: current number of live buckets. */\n bucketCount(): number {\n return this.buckets.size;\n }\n}\n"],"mappings":"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","names":[]}
@@ -26,6 +26,9 @@ function sanitizeSessionKey(sessionKey) {
26
26
  function sanitizeChunkId(id) {
27
27
  return sanitizePathComponent(id, "chunk");
28
28
  }
29
+ function yamlQuotedScalar(value) {
30
+ return JSON.stringify(value);
31
+ }
29
32
  function datePathComponent(startTs) {
30
33
  const match = typeof startTs === "string" ? /^(\d{4})-(\d{2})-(\d{2})T/.exec(startTs) : null;
31
34
  if (!match) {
@@ -113,9 +116,9 @@ async function writeConversationChunks(rootDir, chunks) {
113
116
  await rejectSymlinkIfExists(fp);
114
117
  const content = `---
115
118
  kind: conversation_chunk
116
- sessionKey: ${c.sessionKey}
117
- startTs: ${c.startTs}
118
- endTs: ${c.endTs}
119
+ sessionKey: ${yamlQuotedScalar(c.sessionKey)}
120
+ startTs: ${yamlQuotedScalar(c.startTs)}
121
+ endTs: ${yamlQuotedScalar(c.endTs)}
119
122
  ---
120
123
 
121
124
  ` + c.text + "\n";
@@ -155,4 +158,4 @@ export {
155
158
  upsertConversationChunksFailOpen,
156
159
  rebuildConversationChunksFailOpen
157
160
  };
158
- //# sourceMappingURL=chunk-HENLZHIT.js.map
161
+ //# sourceMappingURL=chunk-OIF36KGD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/conversation-index/indexer.ts"],"sourcesContent":["import { lstat, mkdir, writeFile } from \"node:fs/promises\";\nimport type { Stats } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport { log } from \"../logger.js\";\nimport type { FaissConversationIndexAdapter } from \"./faiss-adapter.js\";\nimport type { ConversationChunk } from \"./chunker.js\";\n\nconst MAX_PATH_COMPONENT_LENGTH = 200;\n\nfunction sanitizePathComponent(\n value: string,\n fallback: string,\n opts: { lowercase?: boolean } = {},\n): string {\n const raw = typeof value === \"string\" && value.trim().length > 0\n ? value.trim()\n : fallback;\n const normalized = opts.lowercase ? raw.toLowerCase() : raw;\n const sanitized = normalized\n .replace(/[^a-zA-Z0-9._-]+/g, \"_\")\n .slice(0, MAX_PATH_COMPONENT_LENGTH);\n if (!sanitized || sanitized === \".\" || sanitized === \"..\") {\n return fallback;\n }\n return sanitized;\n}\n\nexport function sanitizeSessionKey(sessionKey: string): string {\n const raw = typeof sessionKey === \"string\" && sessionKey.trim().length > 0\n ? sessionKey.trim()\n : \"\";\n const safe = sanitizePathComponent(raw, \"unknown-session\", { lowercase: true });\n if (!raw) return safe;\n const suffix = `-${createHash(\"sha256\").update(raw).digest(\"hex\").slice(0, 12)}`;\n return `${safe.slice(0, MAX_PATH_COMPONENT_LENGTH - suffix.length)}${suffix}`;\n}\n\nfunction sanitizeChunkId(id: string): string {\n return sanitizePathComponent(id, \"chunk\");\n}\n\nfunction yamlQuotedScalar(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction datePathComponent(startTs: string): string {\n const match = typeof startTs === \"string\"\n ? /^(\\d{4})-(\\d{2})-(\\d{2})T/.exec(startTs)\n : null;\n if (!match) {\n throw new Error(\"invalid conversation chunk start timestamp\");\n }\n const date = new Date(startTs);\n if (!Number.isFinite(date.getTime())) {\n throw new Error(\"invalid conversation chunk start timestamp\");\n }\n const [, year, month, day] = match;\n if (\n date.getUTCFullYear() !== Number(year) ||\n date.getUTCMonth() + 1 !== Number(month) ||\n date.getUTCDate() !== Number(day)\n ) {\n throw new Error(\"invalid conversation chunk start timestamp\");\n }\n return date.toISOString().slice(0, 10);\n}\n\nfunction resolveInsideRoot(rootDir: string, candidate: string): string {\n const root = path.resolve(rootDir);\n const resolved = path.resolve(candidate);\n const rel = path.relative(root, resolved);\n if (\n rel === \"\" ||\n (rel !== \"..\" && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel))\n ) {\n return resolved;\n }\n throw new Error(\"conversation chunk path escapes index root\");\n}\n\nasync function lstatIfExists(candidate: string): Promise<Stats | undefined> {\n try {\n return await lstat(candidate);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw err;\n }\n}\n\nasync function rejectSymlinkIfExists(candidate: string): Promise<void> {\n const stat = await lstatIfExists(candidate);\n if (stat?.isSymbolicLink()) {\n throw new Error(\"conversation chunk path contains symlink\");\n }\n}\n\nasync function rejectExistingSymlinksInPath(baseDir: string, candidate: string): Promise<void> {\n const base = path.resolve(baseDir);\n const resolved = path.resolve(candidate);\n\n let current = base;\n while (current !== path.dirname(current)) {\n const stat = await lstatIfExists(current);\n if (stat) {\n if (stat.isSymbolicLink()) {\n throw new Error(\"conversation chunk path contains symlink\");\n }\n break;\n }\n current = path.dirname(current);\n }\n\n const relative = path.relative(base, resolved);\n if (relative === \"\") return;\n\n current = base;\n for (const part of relative.split(path.sep)) {\n current = path.join(current, part);\n const stat = await lstatIfExists(current);\n if (!stat) return;\n if (stat.isSymbolicLink()) {\n throw new Error(\"conversation chunk path contains symlink\");\n }\n }\n}\n\nasync function rejectExistingSymlinkAncestors(\n rootDir: string,\n candidate: string,\n): Promise<void> {\n const root = path.resolve(rootDir);\n const resolved = resolveInsideRoot(root, candidate);\n await rejectExistingSymlinksInPath(root, resolved);\n}\n\nexport async function writeConversationChunks(\n rootDir: string,\n chunks: ConversationChunk[],\n): Promise<string[]> {\n const written: string[] = [];\n const root = path.resolve(rootDir);\n await rejectExistingSymlinksInPath(root, root);\n await mkdir(root, { recursive: true });\n await rejectExistingSymlinksInPath(root, root);\n\n for (const c of chunks) {\n const safe = sanitizeSessionKey(c.sessionKey);\n const date = datePathComponent(c.startTs);\n const dir = resolveInsideRoot(root, path.join(root, safe, date));\n await rejectExistingSymlinkAncestors(root, dir);\n await mkdir(dir, { recursive: true });\n await rejectExistingSymlinkAncestors(root, dir);\n const fp = resolveInsideRoot(root, path.join(dir, `${sanitizeChunkId(c.id)}.md`));\n await rejectExistingSymlinkAncestors(root, path.dirname(fp));\n await rejectSymlinkIfExists(fp);\n const content =\n `---\\n` +\n `kind: conversation_chunk\\n` +\n `sessionKey: ${yamlQuotedScalar(c.sessionKey)}\\n` +\n `startTs: ${yamlQuotedScalar(c.startTs)}\\n` +\n `endTs: ${yamlQuotedScalar(c.endTs)}\\n` +\n `---\\n\\n` +\n c.text +\n \"\\n\";\n await writeFile(fp, content, \"utf-8\");\n written.push(fp);\n }\n return written;\n}\n\nexport interface ConversationChunkUpsertResult {\n upserted: number;\n skipped: boolean;\n reason?: \"adapter-unavailable\" | \"adapter-error\";\n}\n\nexport interface ConversationChunkRebuildResult {\n rebuilt: number;\n skipped: boolean;\n reason?: \"adapter-unavailable\" | \"adapter-error\";\n}\n\nexport async function upsertConversationChunksFailOpen(\n adapter: FaissConversationIndexAdapter | undefined,\n chunks: ConversationChunk[],\n options: { retentionCutoffMs?: number } = {},\n): Promise<ConversationChunkUpsertResult> {\n if (!adapter) {\n return { upserted: 0, skipped: true, reason: \"adapter-unavailable\" };\n }\n try {\n const upserted = await adapter.upsertChunks(chunks, options);\n return { upserted, skipped: false };\n } catch (err) {\n log.debug(`conversation index FAISS upsert failed (fail-open): ${err}`);\n return { upserted: 0, skipped: true, reason: \"adapter-error\" };\n }\n}\n\nexport async function rebuildConversationChunksFailOpen(\n adapter: FaissConversationIndexAdapter | undefined,\n chunks: ConversationChunk[],\n): Promise<ConversationChunkRebuildResult> {\n if (!adapter) {\n return { rebuilt: 0, skipped: true, reason: \"adapter-unavailable\" };\n }\n try {\n const rebuilt = await adapter.rebuildChunks(chunks);\n return { rebuilt, skipped: false };\n } catch (err) {\n log.debug(`conversation index FAISS rebuild failed (fail-open): ${err}`);\n return { rebuilt: 0, skipped: true, reason: \"adapter-error\" };\n }\n}\n"],"mappings":";;;;;AAAA,SAAS,OAAO,OAAO,iBAAiB;AAExC,SAAS,kBAAkB;AAC3B,OAAO,UAAU;AAKjB,IAAM,4BAA4B;AAElC,SAAS,sBACP,OACA,UACA,OAAgC,CAAC,GACzB;AACR,QAAM,MAAM,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,IAC3D,MAAM,KAAK,IACX;AACJ,QAAM,aAAa,KAAK,YAAY,IAAI,YAAY,IAAI;AACxD,QAAM,YAAY,WACf,QAAQ,qBAAqB,GAAG,EAChC,MAAM,GAAG,yBAAyB;AACrC,MAAI,CAAC,aAAa,cAAc,OAAO,cAAc,MAAM;AACzD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,mBAAmB,YAA4B;AAC7D,QAAM,MAAM,OAAO,eAAe,YAAY,WAAW,KAAK,EAAE,SAAS,IACrE,WAAW,KAAK,IAChB;AACJ,QAAM,OAAO,sBAAsB,KAAK,mBAAmB,EAAE,WAAW,KAAK,CAAC;AAC9E,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,SAAS,IAAI,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC;AAC9E,SAAO,GAAG,KAAK,MAAM,GAAG,4BAA4B,OAAO,MAAM,CAAC,GAAG,MAAM;AAC7E;AAEA,SAAS,gBAAgB,IAAoB;AAC3C,SAAO,sBAAsB,IAAI,OAAO;AAC1C;AAEA,SAAS,iBAAiB,OAAuB;AAC/C,SAAO,KAAK,UAAU,KAAK;AAC7B;AAEA,SAAS,kBAAkB,SAAyB;AAClD,QAAM,QAAQ,OAAO,YAAY,WAC7B,4BAA4B,KAAK,OAAO,IACxC;AACJ,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,QAAM,OAAO,IAAI,KAAK,OAAO;AAC7B,MAAI,CAAC,OAAO,SAAS,KAAK,QAAQ,CAAC,GAAG;AACpC,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,QAAM,CAAC,EAAE,MAAM,OAAO,GAAG,IAAI;AAC7B,MACE,KAAK,eAAe,MAAM,OAAO,IAAI,KACrC,KAAK,YAAY,IAAI,MAAM,OAAO,KAAK,KACvC,KAAK,WAAW,MAAM,OAAO,GAAG,GAChC;AACA,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,SAAO,KAAK,YAAY,EAAE,MAAM,GAAG,EAAE;AACvC;AAEA,SAAS,kBAAkB,SAAiB,WAA2B;AACrE,QAAM,OAAO,KAAK,QAAQ,OAAO;AACjC,QAAM,WAAW,KAAK,QAAQ,SAAS;AACvC,QAAM,MAAM,KAAK,SAAS,MAAM,QAAQ;AACxC,MACE,QAAQ,MACP,QAAQ,QAAQ,CAAC,IAAI,WAAW,KAAK,KAAK,GAAG,EAAE,KAAK,CAAC,KAAK,WAAW,GAAG,GACzE;AACA,WAAO;AAAA,EACT;AACA,QAAM,IAAI,MAAM,4CAA4C;AAC9D;AAEA,eAAe,cAAc,WAA+C;AAC1E,MAAI;AACF,WAAO,MAAM,MAAM,SAAS;AAAA,EAC9B,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO;AAC7D,UAAM;AAAA,EACR;AACF;AAEA,eAAe,sBAAsB,WAAkC;AACrE,QAAM,OAAO,MAAM,cAAc,SAAS;AAC1C,MAAI,MAAM,eAAe,GAAG;AAC1B,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACF;AAEA,eAAe,6BAA6B,SAAiB,WAAkC;AAC7F,QAAM,OAAO,KAAK,QAAQ,OAAO;AACjC,QAAM,WAAW,KAAK,QAAQ,SAAS;AAEvC,MAAI,UAAU;AACd,SAAO,YAAY,KAAK,QAAQ,OAAO,GAAG;AACxC,UAAM,OAAO,MAAM,cAAc,OAAO;AACxC,QAAI,MAAM;AACR,UAAI,KAAK,eAAe,GAAG;AACzB,cAAM,IAAI,MAAM,0CAA0C;AAAA,MAC5D;AACA;AAAA,IACF;AACA,cAAU,KAAK,QAAQ,OAAO;AAAA,EAChC;AAEA,QAAM,WAAW,KAAK,SAAS,MAAM,QAAQ;AAC7C,MAAI,aAAa,GAAI;AAErB,YAAU;AACV,aAAW,QAAQ,SAAS,MAAM,KAAK,GAAG,GAAG;AAC3C,cAAU,KAAK,KAAK,SAAS,IAAI;AACjC,UAAM,OAAO,MAAM,cAAc,OAAO;AACxC,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,eAAe,GAAG;AACzB,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AAAA,EACF;AACF;AAEA,eAAe,+BACb,SACA,WACe;AACf,QAAM,OAAO,KAAK,QAAQ,OAAO;AACjC,QAAM,WAAW,kBAAkB,MAAM,SAAS;AAClD,QAAM,6BAA6B,MAAM,QAAQ;AACnD;AAEA,eAAsB,wBACpB,SACA,QACmB;AACnB,QAAM,UAAoB,CAAC;AAC3B,QAAM,OAAO,KAAK,QAAQ,OAAO;AACjC,QAAM,6BAA6B,MAAM,IAAI;AAC7C,QAAM,MAAM,MAAM,EAAE,WAAW,KAAK,CAAC;AACrC,QAAM,6BAA6B,MAAM,IAAI;AAE7C,aAAW,KAAK,QAAQ;AACtB,UAAM,OAAO,mBAAmB,EAAE,UAAU;AAC5C,UAAM,OAAO,kBAAkB,EAAE,OAAO;AACxC,UAAM,MAAM,kBAAkB,MAAM,KAAK,KAAK,MAAM,MAAM,IAAI,CAAC;AAC/D,UAAM,+BAA+B,MAAM,GAAG;AAC9C,UAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC,UAAM,+BAA+B,MAAM,GAAG;AAC9C,UAAM,KAAK,kBAAkB,MAAM,KAAK,KAAK,KAAK,GAAG,gBAAgB,EAAE,EAAE,CAAC,KAAK,CAAC;AAChF,UAAM,+BAA+B,MAAM,KAAK,QAAQ,EAAE,CAAC;AAC3D,UAAM,sBAAsB,EAAE;AAC9B,UAAM,UACJ;AAAA;AAAA,cAEe,iBAAiB,EAAE,UAAU,CAAC;AAAA,WACjC,iBAAiB,EAAE,OAAO,CAAC;AAAA,SAC7B,iBAAiB,EAAE,KAAK,CAAC;AAAA;AAAA;AAAA,IAEnC,EAAE,OACF;AACF,UAAM,UAAU,IAAI,SAAS,OAAO;AACpC,YAAQ,KAAK,EAAE;AAAA,EACjB;AACA,SAAO;AACT;AAcA,eAAsB,iCACpB,SACA,QACA,UAA0C,CAAC,GACH;AACxC,MAAI,CAAC,SAAS;AACZ,WAAO,EAAE,UAAU,GAAG,SAAS,MAAM,QAAQ,sBAAsB;AAAA,EACrE;AACA,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,aAAa,QAAQ,OAAO;AAC3D,WAAO,EAAE,UAAU,SAAS,MAAM;AAAA,EACpC,SAAS,KAAK;AACZ,QAAI,MAAM,uDAAuD,GAAG,EAAE;AACtE,WAAO,EAAE,UAAU,GAAG,SAAS,MAAM,QAAQ,gBAAgB;AAAA,EAC/D;AACF;AAEA,eAAsB,kCACpB,SACA,QACyC;AACzC,MAAI,CAAC,SAAS;AACZ,WAAO,EAAE,SAAS,GAAG,SAAS,MAAM,QAAQ,sBAAsB;AAAA,EACpE;AACA,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,cAAc,MAAM;AAClD,WAAO,EAAE,SAAS,SAAS,MAAM;AAAA,EACnC,SAAS,KAAK;AACZ,QAAI,MAAM,wDAAwD,GAAG,EAAE;AACvE,WAAO,EAAE,SAAS,GAAG,SAAS,MAAM,QAAQ,gBAAgB;AAAA,EAC9D;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  StorageManager
3
- } from "./chunk-YFS5OEKO.js";
3
+ } from "./chunk-7MLB4NCL.js";
4
4
  import {
5
5
  getCachedRuleMemories,
6
6
  setCachedRuleMemories
@@ -115,4 +115,4 @@ async function searchVerifiedSemanticRules(options) {
115
115
  export {
116
116
  searchVerifiedSemanticRules
117
117
  };
118
- //# sourceMappingURL=chunk-GUPISBV2.js.map
118
+ //# sourceMappingURL=chunk-PP2JH3GP.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-SOBJ6NEY.js";
4
4
  import {
5
5
  summarizeDisclosureTokens
6
- } from "./chunk-FXKPZ3H6.js";
6
+ } from "./chunk-BPSGLMQ4.js";
7
7
  import {
8
8
  summarizeRetrievedMemoryProvenance
9
9
  } from "./chunk-AC5LO7IU.js";
@@ -567,4 +567,4 @@ export {
567
567
  renderRecallExplain,
568
568
  parseRecallExplainFormat
569
569
  };
570
- //# sourceMappingURL=chunk-OXJBNGBK.js.map
570
+ //# sourceMappingURL=chunk-PSUB67YB.js.map