@remnic/core 1.0.0

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 (551) hide show
  1. package/dist/abstraction-nodes.d.ts +52 -0
  2. package/dist/abstraction-nodes.js +15 -0
  3. package/dist/abstraction-nodes.js.map +1 -0
  4. package/dist/access-cli.d.ts +5 -0
  5. package/dist/access-cli.js +308 -0
  6. package/dist/access-cli.js.map +1 -0
  7. package/dist/access-http.d.ts +158 -0
  8. package/dist/access-http.js +32 -0
  9. package/dist/access-http.js.map +1 -0
  10. package/dist/access-idempotency.d.ts +31 -0
  11. package/dist/access-idempotency.js +11 -0
  12. package/dist/access-idempotency.js.map +1 -0
  13. package/dist/access-mcp.d.ts +76 -0
  14. package/dist/access-mcp.js +8 -0
  15. package/dist/access-mcp.js.map +1 -0
  16. package/dist/access-schema.d.ts +266 -0
  17. package/dist/access-schema.js +29 -0
  18. package/dist/access-schema.js.map +1 -0
  19. package/dist/access-service.d.ts +614 -0
  20. package/dist/access-service.js +32 -0
  21. package/dist/access-service.js.map +1 -0
  22. package/dist/behavior-learner.d.ts +16 -0
  23. package/dist/behavior-learner.js +124 -0
  24. package/dist/behavior-learner.js.map +1 -0
  25. package/dist/behavior-signals.d.ts +15 -0
  26. package/dist/behavior-signals.js +11 -0
  27. package/dist/behavior-signals.js.map +1 -0
  28. package/dist/bootstrap.d.ts +46 -0
  29. package/dist/bootstrap.js +9 -0
  30. package/dist/bootstrap.js.map +1 -0
  31. package/dist/boxes.d.ts +93 -0
  32. package/dist/boxes.js +14 -0
  33. package/dist/boxes.js.map +1 -0
  34. package/dist/buffer.d.ts +22 -0
  35. package/dist/buffer.js +9 -0
  36. package/dist/buffer.js.map +1 -0
  37. package/dist/calibration.d.ts +81 -0
  38. package/dist/calibration.js +239 -0
  39. package/dist/calibration.js.map +1 -0
  40. package/dist/causal-behavior.d.ts +79 -0
  41. package/dist/causal-behavior.js +190 -0
  42. package/dist/causal-behavior.js.map +1 -0
  43. package/dist/causal-chain.d.ts +61 -0
  44. package/dist/causal-chain.js +24 -0
  45. package/dist/causal-chain.js.map +1 -0
  46. package/dist/causal-consolidation.d.ts +71 -0
  47. package/dist/causal-consolidation.js +211 -0
  48. package/dist/causal-consolidation.js.map +1 -0
  49. package/dist/causal-retrieval.d.ts +44 -0
  50. package/dist/causal-retrieval.js +184 -0
  51. package/dist/causal-retrieval.js.map +1 -0
  52. package/dist/causal-trajectory-graph.d.ts +13 -0
  53. package/dist/causal-trajectory-graph.js +59 -0
  54. package/dist/causal-trajectory-graph.js.map +1 -0
  55. package/dist/causal-trajectory.d.ts +68 -0
  56. package/dist/causal-trajectory.js +18 -0
  57. package/dist/causal-trajectory.js.map +1 -0
  58. package/dist/chunk-2CJCWDMR.js +87 -0
  59. package/dist/chunk-2CJCWDMR.js.map +1 -0
  60. package/dist/chunk-2NMMFZ5T.js +216 -0
  61. package/dist/chunk-2NMMFZ5T.js.map +1 -0
  62. package/dist/chunk-2PO5ZRKV.js +103 -0
  63. package/dist/chunk-2PO5ZRKV.js.map +1 -0
  64. package/dist/chunk-3QKK7QOS.js +154 -0
  65. package/dist/chunk-3QKK7QOS.js.map +1 -0
  66. package/dist/chunk-3SLRNYNG.js +26 -0
  67. package/dist/chunk-3SLRNYNG.js.map +1 -0
  68. package/dist/chunk-4A24LIM2.js +68 -0
  69. package/dist/chunk-4A24LIM2.js.map +1 -0
  70. package/dist/chunk-6HZ6AO2P.js +164 -0
  71. package/dist/chunk-6HZ6AO2P.js.map +1 -0
  72. package/dist/chunk-763GUIOU.js +302 -0
  73. package/dist/chunk-763GUIOU.js.map +1 -0
  74. package/dist/chunk-AAI7JARD.js +173 -0
  75. package/dist/chunk-AAI7JARD.js.map +1 -0
  76. package/dist/chunk-B7LOFDVE.js +112 -0
  77. package/dist/chunk-B7LOFDVE.js.map +1 -0
  78. package/dist/chunk-BDFZXRSO.js +318 -0
  79. package/dist/chunk-BDFZXRSO.js.map +1 -0
  80. package/dist/chunk-BOUYNNYD.js +707 -0
  81. package/dist/chunk-BOUYNNYD.js.map +1 -0
  82. package/dist/chunk-BRK4ODMI.js +60 -0
  83. package/dist/chunk-BRK4ODMI.js.map +1 -0
  84. package/dist/chunk-C6QPK5GG.js +111 -0
  85. package/dist/chunk-C6QPK5GG.js.map +1 -0
  86. package/dist/chunk-C7VW7C3F.js +117 -0
  87. package/dist/chunk-C7VW7C3F.js.map +1 -0
  88. package/dist/chunk-CDW777AI.js +621 -0
  89. package/dist/chunk-CDW777AI.js.map +1 -0
  90. package/dist/chunk-CULXMQJH.js +185 -0
  91. package/dist/chunk-CULXMQJH.js.map +1 -0
  92. package/dist/chunk-CXWFUJR2.js +1203 -0
  93. package/dist/chunk-CXWFUJR2.js.map +1 -0
  94. package/dist/chunk-DGXUHMOV.js +61 -0
  95. package/dist/chunk-DGXUHMOV.js.map +1 -0
  96. package/dist/chunk-DM2T26WE.js +61 -0
  97. package/dist/chunk-DM2T26WE.js.map +1 -0
  98. package/dist/chunk-DORBM6OB.js +81 -0
  99. package/dist/chunk-DORBM6OB.js.map +1 -0
  100. package/dist/chunk-DT5TVLJE.js +32 -0
  101. package/dist/chunk-DT5TVLJE.js.map +1 -0
  102. package/dist/chunk-EEQLFRUM.js +89 -0
  103. package/dist/chunk-EEQLFRUM.js.map +1 -0
  104. package/dist/chunk-EQINRHYR.js +672 -0
  105. package/dist/chunk-EQINRHYR.js.map +1 -0
  106. package/dist/chunk-ESSMF2FR.js +146 -0
  107. package/dist/chunk-ESSMF2FR.js.map +1 -0
  108. package/dist/chunk-ETOW6ACV.js +158 -0
  109. package/dist/chunk-ETOW6ACV.js.map +1 -0
  110. package/dist/chunk-FYIYMQ5N.js +221 -0
  111. package/dist/chunk-FYIYMQ5N.js.map +1 -0
  112. package/dist/chunk-G3AG3KZN.js +78 -0
  113. package/dist/chunk-G3AG3KZN.js.map +1 -0
  114. package/dist/chunk-GJR6D6KC.js +61 -0
  115. package/dist/chunk-GJR6D6KC.js.map +1 -0
  116. package/dist/chunk-GPGBSNKM.js +380 -0
  117. package/dist/chunk-GPGBSNKM.js.map +1 -0
  118. package/dist/chunk-H63EDPFJ.js +57 -0
  119. package/dist/chunk-H63EDPFJ.js.map +1 -0
  120. package/dist/chunk-HG2NKWR2.js +185 -0
  121. package/dist/chunk-HG2NKWR2.js.map +1 -0
  122. package/dist/chunk-HL4DB7TO.js +13 -0
  123. package/dist/chunk-HL4DB7TO.js.map +1 -0
  124. package/dist/chunk-HLBYLYRD.js +346 -0
  125. package/dist/chunk-HLBYLYRD.js.map +1 -0
  126. package/dist/chunk-HLXVTBF3.js +109 -0
  127. package/dist/chunk-HLXVTBF3.js.map +1 -0
  128. package/dist/chunk-IFFFR3MR.js +68 -0
  129. package/dist/chunk-IFFFR3MR.js.map +1 -0
  130. package/dist/chunk-ISY75RLM.js +1027 -0
  131. package/dist/chunk-ISY75RLM.js.map +1 -0
  132. package/dist/chunk-IZME7KW2.js +1886 -0
  133. package/dist/chunk-IZME7KW2.js.map +1 -0
  134. package/dist/chunk-J3BT33K7.js +720 -0
  135. package/dist/chunk-J3BT33K7.js.map +1 -0
  136. package/dist/chunk-J47FNDR7.js +113 -0
  137. package/dist/chunk-J47FNDR7.js.map +1 -0
  138. package/dist/chunk-JWPLJLDU.js +63 -0
  139. package/dist/chunk-JWPLJLDU.js.map +1 -0
  140. package/dist/chunk-K6WK37A6.js +865 -0
  141. package/dist/chunk-K6WK37A6.js.map +1 -0
  142. package/dist/chunk-KL4CP4SB.js +130 -0
  143. package/dist/chunk-KL4CP4SB.js.map +1 -0
  144. package/dist/chunk-KT4NEUNF.js +315 -0
  145. package/dist/chunk-KT4NEUNF.js.map +1 -0
  146. package/dist/chunk-KWBU5S5U.js +42 -0
  147. package/dist/chunk-KWBU5S5U.js.map +1 -0
  148. package/dist/chunk-L5RPWGFK.js +59 -0
  149. package/dist/chunk-L5RPWGFK.js.map +1 -0
  150. package/dist/chunk-L7WO3MZ4.js +128 -0
  151. package/dist/chunk-L7WO3MZ4.js.map +1 -0
  152. package/dist/chunk-LIRZNNUP.js +74 -0
  153. package/dist/chunk-LIRZNNUP.js.map +1 -0
  154. package/dist/chunk-LK6SGL53.js +22 -0
  155. package/dist/chunk-LK6SGL53.js.map +1 -0
  156. package/dist/chunk-LOBRX7VD.js +200 -0
  157. package/dist/chunk-LOBRX7VD.js.map +1 -0
  158. package/dist/chunk-LPSF4OQH.js +47 -0
  159. package/dist/chunk-LPSF4OQH.js.map +1 -0
  160. package/dist/chunk-LU3GQNDQ.js +152 -0
  161. package/dist/chunk-LU3GQNDQ.js.map +1 -0
  162. package/dist/chunk-M5KEYE5E.js +350 -0
  163. package/dist/chunk-M5KEYE5E.js.map +1 -0
  164. package/dist/chunk-M62O4P4T.js +41 -0
  165. package/dist/chunk-M62O4P4T.js.map +1 -0
  166. package/dist/chunk-MARWOCVP.js +48 -0
  167. package/dist/chunk-MARWOCVP.js.map +1 -0
  168. package/dist/chunk-MDDAA2AO.js +925 -0
  169. package/dist/chunk-MDDAA2AO.js.map +1 -0
  170. package/dist/chunk-MWGVGUIS.js +198 -0
  171. package/dist/chunk-MWGVGUIS.js.map +1 -0
  172. package/dist/chunk-N5AKDXAI.js +74 -0
  173. package/dist/chunk-N5AKDXAI.js.map +1 -0
  174. package/dist/chunk-NGAVDO7E.js +115 -0
  175. package/dist/chunk-NGAVDO7E.js.map +1 -0
  176. package/dist/chunk-NTTLPF7F.js +283 -0
  177. package/dist/chunk-NTTLPF7F.js.map +1 -0
  178. package/dist/chunk-ONRU4L2N.js +240 -0
  179. package/dist/chunk-ONRU4L2N.js.map +1 -0
  180. package/dist/chunk-ORZMT74A.js +209 -0
  181. package/dist/chunk-ORZMT74A.js.map +1 -0
  182. package/dist/chunk-OTAVQCSF.js +268 -0
  183. package/dist/chunk-OTAVQCSF.js.map +1 -0
  184. package/dist/chunk-PGK3VUHN.js +160 -0
  185. package/dist/chunk-PGK3VUHN.js.map +1 -0
  186. package/dist/chunk-Q6FETXJA.js +1362 -0
  187. package/dist/chunk-Q6FETXJA.js.map +1 -0
  188. package/dist/chunk-QANCTXQF.js +271 -0
  189. package/dist/chunk-QANCTXQF.js.map +1 -0
  190. package/dist/chunk-QCCCQT3O.js +189 -0
  191. package/dist/chunk-QCCCQT3O.js.map +1 -0
  192. package/dist/chunk-QDOSNLB4.js +1048 -0
  193. package/dist/chunk-QDOSNLB4.js.map +1 -0
  194. package/dist/chunk-QFQVZOGA.js +2168 -0
  195. package/dist/chunk-QFQVZOGA.js.map +1 -0
  196. package/dist/chunk-QPKFPHOO.js +178 -0
  197. package/dist/chunk-QPKFPHOO.js.map +1 -0
  198. package/dist/chunk-QSVPYQPG.js +268 -0
  199. package/dist/chunk-QSVPYQPG.js.map +1 -0
  200. package/dist/chunk-QWUUMMIK.js +3045 -0
  201. package/dist/chunk-QWUUMMIK.js.map +1 -0
  202. package/dist/chunk-QY2BHY5O.js +2378 -0
  203. package/dist/chunk-QY2BHY5O.js.map +1 -0
  204. package/dist/chunk-SCHEKPYH.js +349 -0
  205. package/dist/chunk-SCHEKPYH.js.map +1 -0
  206. package/dist/chunk-SCU65EZI.js +15 -0
  207. package/dist/chunk-SCU65EZI.js.map +1 -0
  208. package/dist/chunk-T4WRIV2C.js +170 -0
  209. package/dist/chunk-T4WRIV2C.js.map +1 -0
  210. package/dist/chunk-TKO4HZCK.js +1852 -0
  211. package/dist/chunk-TKO4HZCK.js.map +1 -0
  212. package/dist/chunk-TP4FZJIZ.js +93 -0
  213. package/dist/chunk-TP4FZJIZ.js.map +1 -0
  214. package/dist/chunk-TPB3I2AC.js +403 -0
  215. package/dist/chunk-TPB3I2AC.js.map +1 -0
  216. package/dist/chunk-TVVVQQAK.js +1431 -0
  217. package/dist/chunk-TVVVQQAK.js.map +1 -0
  218. package/dist/chunk-U4PV25RD.js +14 -0
  219. package/dist/chunk-U4PV25RD.js.map +1 -0
  220. package/dist/chunk-UCYSTFZR.js +284 -0
  221. package/dist/chunk-UCYSTFZR.js.map +1 -0
  222. package/dist/chunk-UHGBNIOS.js +205 -0
  223. package/dist/chunk-UHGBNIOS.js.map +1 -0
  224. package/dist/chunk-UIYZ5T3I.js +108 -0
  225. package/dist/chunk-UIYZ5T3I.js.map +1 -0
  226. package/dist/chunk-UV2FO7J4.js +747 -0
  227. package/dist/chunk-UV2FO7J4.js.map +1 -0
  228. package/dist/chunk-UZB5KHKX.js +63 -0
  229. package/dist/chunk-UZB5KHKX.js.map +1 -0
  230. package/dist/chunk-V3RXWQIE.js +626 -0
  231. package/dist/chunk-V3RXWQIE.js.map +1 -0
  232. package/dist/chunk-V4YC4LUK.js +444 -0
  233. package/dist/chunk-V4YC4LUK.js.map +1 -0
  234. package/dist/chunk-VEWZZM3H.js +133 -0
  235. package/dist/chunk-VEWZZM3H.js.map +1 -0
  236. package/dist/chunk-WWIQTB2Y.js +98 -0
  237. package/dist/chunk-WWIQTB2Y.js.map +1 -0
  238. package/dist/chunk-X7XN6YU4.js +24 -0
  239. package/dist/chunk-X7XN6YU4.js.map +1 -0
  240. package/dist/chunk-XKECPATV.js +202 -0
  241. package/dist/chunk-XKECPATV.js.map +1 -0
  242. package/dist/chunk-XYIK4LF6.js +75 -0
  243. package/dist/chunk-XYIK4LF6.js.map +1 -0
  244. package/dist/chunk-Y27UJK6V.js +39 -0
  245. package/dist/chunk-Y27UJK6V.js.map +1 -0
  246. package/dist/chunk-Y4Z4I6WK.js +9 -0
  247. package/dist/chunk-Y4Z4I6WK.js.map +1 -0
  248. package/dist/chunk-YAPUAHAY.js +10761 -0
  249. package/dist/chunk-YAPUAHAY.js.map +1 -0
  250. package/dist/chunk-YAZNBMNF.js +92 -0
  251. package/dist/chunk-YAZNBMNF.js.map +1 -0
  252. package/dist/chunk-YCN4BVDK.js +66 -0
  253. package/dist/chunk-YCN4BVDK.js.map +1 -0
  254. package/dist/chunk-YNCQ7E4M.js +388 -0
  255. package/dist/chunk-YNCQ7E4M.js.map +1 -0
  256. package/dist/chunk-YNI4S5WT.js +143 -0
  257. package/dist/chunk-YNI4S5WT.js.map +1 -0
  258. package/dist/chunk-YRMVARQP.js +406 -0
  259. package/dist/chunk-YRMVARQP.js.map +1 -0
  260. package/dist/chunk-Z5AAYHUC.js +79 -0
  261. package/dist/chunk-Z5AAYHUC.js.map +1 -0
  262. package/dist/chunk-Z5LAYHGJ.js +15 -0
  263. package/dist/chunk-Z5LAYHGJ.js.map +1 -0
  264. package/dist/chunk-ZJLY4QSU.js +823 -0
  265. package/dist/chunk-ZJLY4QSU.js.map +1 -0
  266. package/dist/chunk-ZKYI7UVO.js +276 -0
  267. package/dist/chunk-ZKYI7UVO.js.map +1 -0
  268. package/dist/chunk-ZPKBYX2F.js +297 -0
  269. package/dist/chunk-ZPKBYX2F.js.map +1 -0
  270. package/dist/chunking.d.ts +48 -0
  271. package/dist/chunking.js +11 -0
  272. package/dist/chunking.js.map +1 -0
  273. package/dist/cli.d.ts +1162 -0
  274. package/dist/cli.js +7187 -0
  275. package/dist/cli.js.map +1 -0
  276. package/dist/commitment-ledger.d.ts +83 -0
  277. package/dist/commitment-ledger.js +19 -0
  278. package/dist/commitment-ledger.js.map +1 -0
  279. package/dist/compression-optimizer.d.ts +37 -0
  280. package/dist/compression-optimizer.js +13 -0
  281. package/dist/compression-optimizer.js.map +1 -0
  282. package/dist/config.d.ts +6 -0
  283. package/dist/config.js +12 -0
  284. package/dist/config.js.map +1 -0
  285. package/dist/cue-anchors.d.ts +50 -0
  286. package/dist/cue-anchors.js +15 -0
  287. package/dist/cue-anchors.js.map +1 -0
  288. package/dist/dashboard-runtime.d.ts +46 -0
  289. package/dist/dashboard-runtime.js +10 -0
  290. package/dist/dashboard-runtime.js.map +1 -0
  291. package/dist/day-summary.d.ts +6 -0
  292. package/dist/day-summary.js +10 -0
  293. package/dist/day-summary.js.map +1 -0
  294. package/dist/delinearize.d.ts +34 -0
  295. package/dist/delinearize.js +11 -0
  296. package/dist/delinearize.js.map +1 -0
  297. package/dist/embedding-fallback.d.ts +22 -0
  298. package/dist/embedding-fallback.js +8 -0
  299. package/dist/embedding-fallback.js.map +1 -0
  300. package/dist/engine-P26JFSVY.js +19 -0
  301. package/dist/engine-P26JFSVY.js.map +1 -0
  302. package/dist/entity-retrieval.d.ts +23 -0
  303. package/dist/entity-retrieval.js +24 -0
  304. package/dist/entity-retrieval.js.map +1 -0
  305. package/dist/evals.d.ts +282 -0
  306. package/dist/evals.js +32 -0
  307. package/dist/evals.js.map +1 -0
  308. package/dist/explicit-capture.d.ts +60 -0
  309. package/dist/explicit-capture.js +23 -0
  310. package/dist/explicit-capture.js.map +1 -0
  311. package/dist/extraction.d.ts +141 -0
  312. package/dist/extraction.js +22 -0
  313. package/dist/extraction.js.map +1 -0
  314. package/dist/fallback-llm.d.ts +95 -0
  315. package/dist/fallback-llm.js +12 -0
  316. package/dist/fallback-llm.js.map +1 -0
  317. package/dist/graph-dashboard-diff.d.ts +12 -0
  318. package/dist/graph-dashboard-diff.js +8 -0
  319. package/dist/graph-dashboard-diff.js.map +1 -0
  320. package/dist/graph-dashboard-key.d.ts +5 -0
  321. package/dist/graph-dashboard-key.js +7 -0
  322. package/dist/graph-dashboard-key.js.map +1 -0
  323. package/dist/graph-dashboard-parser.d.ts +20 -0
  324. package/dist/graph-dashboard-parser.js +8 -0
  325. package/dist/graph-dashboard-parser.js.map +1 -0
  326. package/dist/graph.d.ts +157 -0
  327. package/dist/graph.js +27 -0
  328. package/dist/graph.js.map +1 -0
  329. package/dist/harmonic-retrieval.d.ts +27 -0
  330. package/dist/harmonic-retrieval.js +12 -0
  331. package/dist/harmonic-retrieval.js.map +1 -0
  332. package/dist/himem.d.ts +23 -0
  333. package/dist/himem.js +7 -0
  334. package/dist/himem.js.map +1 -0
  335. package/dist/hygiene.d.ts +24 -0
  336. package/dist/hygiene.js +9 -0
  337. package/dist/hygiene.js.map +1 -0
  338. package/dist/identity-continuity.d.ts +17 -0
  339. package/dist/identity-continuity.js +19 -0
  340. package/dist/identity-continuity.js.map +1 -0
  341. package/dist/importance.d.ts +25 -0
  342. package/dist/importance.js +11 -0
  343. package/dist/importance.js.map +1 -0
  344. package/dist/index.d.ts +923 -0
  345. package/dist/index.js +2512 -0
  346. package/dist/index.js.map +1 -0
  347. package/dist/intent.d.ts +8 -0
  348. package/dist/intent.js +13 -0
  349. package/dist/intent.js.map +1 -0
  350. package/dist/json-extract.d.ts +14 -0
  351. package/dist/json-extract.js +9 -0
  352. package/dist/json-extract.js.map +1 -0
  353. package/dist/json-store.d.ts +5 -0
  354. package/dist/json-store.js +11 -0
  355. package/dist/json-store.js.map +1 -0
  356. package/dist/legacy-hook-compat.d.ts +3 -0
  357. package/dist/legacy-hook-compat.js +35 -0
  358. package/dist/legacy-hook-compat.js.map +1 -0
  359. package/dist/lifecycle.d.ts +52 -0
  360. package/dist/lifecycle.js +21 -0
  361. package/dist/lifecycle.js.map +1 -0
  362. package/dist/local-llm.d.ts +154 -0
  363. package/dist/local-llm.js +10 -0
  364. package/dist/local-llm.js.map +1 -0
  365. package/dist/logger.d.ts +15 -0
  366. package/dist/logger.js +9 -0
  367. package/dist/logger.js.map +1 -0
  368. package/dist/memory-action-policy.d.ts +13 -0
  369. package/dist/memory-action-policy.js +7 -0
  370. package/dist/memory-action-policy.js.map +1 -0
  371. package/dist/memory-cache.d.ts +35 -0
  372. package/dist/memory-cache.js +37 -0
  373. package/dist/memory-cache.js.map +1 -0
  374. package/dist/memory-lifecycle-ledger-utils.d.ts +13 -0
  375. package/dist/memory-lifecycle-ledger-utils.js +23 -0
  376. package/dist/memory-lifecycle-ledger-utils.js.map +1 -0
  377. package/dist/memory-projection-format.d.ts +4 -0
  378. package/dist/memory-projection-format.js +9 -0
  379. package/dist/memory-projection-format.js.map +1 -0
  380. package/dist/memory-projection-store-NxMkbocT.d.ts +221 -0
  381. package/dist/memory-projection-store.d.ts +3 -0
  382. package/dist/memory-projection-store.js +31 -0
  383. package/dist/memory-projection-store.js.map +1 -0
  384. package/dist/model-registry.d.ts +60 -0
  385. package/dist/model-registry.js +8 -0
  386. package/dist/model-registry.js.map +1 -0
  387. package/dist/native-knowledge.d.ts +94 -0
  388. package/dist/native-knowledge.js +26 -0
  389. package/dist/native-knowledge.js.map +1 -0
  390. package/dist/negative.d.ts +26 -0
  391. package/dist/negative.js +8 -0
  392. package/dist/negative.js.map +1 -0
  393. package/dist/objective-state-writers.d.ts +22 -0
  394. package/dist/objective-state-writers.js +313 -0
  395. package/dist/objective-state-writers.js.map +1 -0
  396. package/dist/objective-state.d.ts +75 -0
  397. package/dist/objective-state.js +17 -0
  398. package/dist/objective-state.js.map +1 -0
  399. package/dist/openai-chat-compat.d.ts +13 -0
  400. package/dist/openai-chat-compat.js +11 -0
  401. package/dist/openai-chat-compat.js.map +1 -0
  402. package/dist/operator-toolkit.d.ts +304 -0
  403. package/dist/operator-toolkit.js +41 -0
  404. package/dist/operator-toolkit.js.map +1 -0
  405. package/dist/opik-exporter.d.ts +72 -0
  406. package/dist/opik-exporter.js +361 -0
  407. package/dist/opik-exporter.js.map +1 -0
  408. package/dist/orchestrator-zTa-Qo-1.d.ts +1104 -0
  409. package/dist/orchestrator.d.ts +21 -0
  410. package/dist/orchestrator.js +145 -0
  411. package/dist/orchestrator.js.map +1 -0
  412. package/dist/policy-runtime.d.ts +37 -0
  413. package/dist/policy-runtime.js +13 -0
  414. package/dist/policy-runtime.js.map +1 -0
  415. package/dist/port-C1GZFv8h.d.ts +41 -0
  416. package/dist/profiling.d.ts +80 -0
  417. package/dist/profiling.js +10 -0
  418. package/dist/profiling.js.map +1 -0
  419. package/dist/qmd-recall-cache.d.ts +29 -0
  420. package/dist/qmd-recall-cache.js +13 -0
  421. package/dist/qmd-recall-cache.js.map +1 -0
  422. package/dist/qmd.d.ts +105 -0
  423. package/dist/qmd.js +13 -0
  424. package/dist/qmd.js.map +1 -0
  425. package/dist/recall-qos.d.ts +33 -0
  426. package/dist/recall-qos.js +10 -0
  427. package/dist/recall-qos.js.map +1 -0
  428. package/dist/recall-query-policy.d.ts +20 -0
  429. package/dist/recall-query-policy.js +11 -0
  430. package/dist/recall-query-policy.js.map +1 -0
  431. package/dist/recall-state.d.ts +113 -0
  432. package/dist/recall-state.js +12 -0
  433. package/dist/recall-state.js.map +1 -0
  434. package/dist/recall-tokenization.d.ts +4 -0
  435. package/dist/recall-tokenization.js +9 -0
  436. package/dist/recall-tokenization.js.map +1 -0
  437. package/dist/reconstruct.d.ts +16 -0
  438. package/dist/reconstruct.js +7 -0
  439. package/dist/reconstruct.js.map +1 -0
  440. package/dist/release-changelog.d.ts +7 -0
  441. package/dist/release-changelog.js +30 -0
  442. package/dist/release-changelog.js.map +1 -0
  443. package/dist/relevance.d.ts +18 -0
  444. package/dist/relevance.js +8 -0
  445. package/dist/relevance.js.map +1 -0
  446. package/dist/rerank.d.ts +57 -0
  447. package/dist/rerank.js +11 -0
  448. package/dist/rerank.js.map +1 -0
  449. package/dist/resolve-provider-secret.d.ts +16 -0
  450. package/dist/resolve-provider-secret.js +11 -0
  451. package/dist/resolve-provider-secret.js.map +1 -0
  452. package/dist/resume-bundles.d.ts +66 -0
  453. package/dist/resume-bundles.js +27 -0
  454. package/dist/resume-bundles.js.map +1 -0
  455. package/dist/retrieval-agents.d.ts +129 -0
  456. package/dist/retrieval-agents.js +23 -0
  457. package/dist/retrieval-agents.js.map +1 -0
  458. package/dist/retrieval.d.ts +19 -0
  459. package/dist/retrieval.js +10 -0
  460. package/dist/retrieval.js.map +1 -0
  461. package/dist/sanitize.d.ts +9 -0
  462. package/dist/sanitize.js +9 -0
  463. package/dist/sanitize.js.map +1 -0
  464. package/dist/schemas.d.ts +688 -0
  465. package/dist/schemas.js +51 -0
  466. package/dist/schemas.js.map +1 -0
  467. package/dist/sdk-compat.d.ts +21 -0
  468. package/dist/sdk-compat.js +28 -0
  469. package/dist/sdk-compat.js.map +1 -0
  470. package/dist/semantic-consolidation.d.ts +42 -0
  471. package/dist/semantic-consolidation.js +12 -0
  472. package/dist/semantic-consolidation.js.map +1 -0
  473. package/dist/semantic-rule-promotion.d.ts +28 -0
  474. package/dist/semantic-rule-promotion.js +17 -0
  475. package/dist/semantic-rule-promotion.js.map +1 -0
  476. package/dist/semantic-rule-verifier.d.ts +19 -0
  477. package/dist/semantic-rule-verifier.js +18 -0
  478. package/dist/semantic-rule-verifier.js.map +1 -0
  479. package/dist/session-integrity.d.ts +67 -0
  480. package/dist/session-integrity.js +11 -0
  481. package/dist/session-integrity.js.map +1 -0
  482. package/dist/session-observer-bands.d.ts +6 -0
  483. package/dist/session-observer-bands.js +9 -0
  484. package/dist/session-observer-bands.js.map +1 -0
  485. package/dist/session-observer-state.d.ts +40 -0
  486. package/dist/session-observer-state.js +11 -0
  487. package/dist/session-observer-state.js.map +1 -0
  488. package/dist/signal.d.ts +6 -0
  489. package/dist/signal.js +9 -0
  490. package/dist/signal.js.map +1 -0
  491. package/dist/storage.d.ts +453 -0
  492. package/dist/storage.js +24 -0
  493. package/dist/storage.js.map +1 -0
  494. package/dist/store-contract.d.ts +10 -0
  495. package/dist/store-contract.js +21 -0
  496. package/dist/store-contract.js.map +1 -0
  497. package/dist/summarizer.d.ts +35 -0
  498. package/dist/summarizer.js +17 -0
  499. package/dist/summarizer.js.map +1 -0
  500. package/dist/summary-snapshot.d.ts +8 -0
  501. package/dist/summary-snapshot.js +13 -0
  502. package/dist/summary-snapshot.js.map +1 -0
  503. package/dist/temporal-index.d.ts +139 -0
  504. package/dist/temporal-index.js +29 -0
  505. package/dist/temporal-index.js.map +1 -0
  506. package/dist/threading.d.ts +62 -0
  507. package/dist/threading.js +8 -0
  508. package/dist/threading.js.map +1 -0
  509. package/dist/tier-migration.d.ts +44 -0
  510. package/dist/tier-migration.js +7 -0
  511. package/dist/tier-migration.js.map +1 -0
  512. package/dist/tier-routing.d.ts +21 -0
  513. package/dist/tier-routing.js +10 -0
  514. package/dist/tier-routing.js.map +1 -0
  515. package/dist/tmt.d.ts +79 -0
  516. package/dist/tmt.js +29 -0
  517. package/dist/tmt.js.map +1 -0
  518. package/dist/tokens.d.ts +24 -0
  519. package/dist/tokens.js +21 -0
  520. package/dist/tokens.js.map +1 -0
  521. package/dist/topics.d.ts +29 -0
  522. package/dist/topics.js +9 -0
  523. package/dist/topics.js.map +1 -0
  524. package/dist/transcript.d.ts +171 -0
  525. package/dist/transcript.js +9 -0
  526. package/dist/transcript.js.map +1 -0
  527. package/dist/trust-zones.d.ts +170 -0
  528. package/dist/trust-zones.js +32 -0
  529. package/dist/trust-zones.js.map +1 -0
  530. package/dist/types.d.ts +1243 -0
  531. package/dist/types.js +9 -0
  532. package/dist/types.js.map +1 -0
  533. package/dist/utility-learner.d.ts +59 -0
  534. package/dist/utility-learner.js +17 -0
  535. package/dist/utility-learner.js.map +1 -0
  536. package/dist/utility-runtime.d.ts +21 -0
  537. package/dist/utility-runtime.js +16 -0
  538. package/dist/utility-runtime.js.map +1 -0
  539. package/dist/utility-telemetry.d.ts +68 -0
  540. package/dist/utility-telemetry.js +17 -0
  541. package/dist/utility-telemetry.js.map +1 -0
  542. package/dist/verified-recall.d.ts +17 -0
  543. package/dist/verified-recall.js +19 -0
  544. package/dist/verified-recall.js.map +1 -0
  545. package/dist/version-utils.d.ts +4 -0
  546. package/dist/version-utils.js +7 -0
  547. package/dist/version-utils.js.map +1 -0
  548. package/dist/work-product-ledger.d.ts +65 -0
  549. package/dist/work-product-ledger.js +18 -0
  550. package/dist/work-product-ledger.js.map +1 -0
  551. package/package.json +58 -0
@@ -0,0 +1,3045 @@
1
+ import {
2
+ SPECULATIVE_TTL_DAYS,
3
+ confidenceTier
4
+ } from "./chunk-U4PV25RD.js";
5
+ import {
6
+ getCachedEntities,
7
+ setCachedEntities
8
+ } from "./chunk-ESSMF2FR.js";
9
+ import {
10
+ inferMemoryStatus,
11
+ isArchivedMemoryPath,
12
+ sortMemoryLifecycleEvents,
13
+ toMemoryPathRel
14
+ } from "./chunk-TP4FZJIZ.js";
15
+ import {
16
+ normalizeProjectionPreview,
17
+ normalizeProjectionTags
18
+ } from "./chunk-SCU65EZI.js";
19
+ import {
20
+ readProjectedGovernanceRecord,
21
+ readProjectedMemoryBrowse,
22
+ readProjectedMemoryState,
23
+ readProjectedMemoryTimeline
24
+ } from "./chunk-BOUYNNYD.js";
25
+ import {
26
+ rotateMarkdownFileToArchive
27
+ } from "./chunk-DM2T26WE.js";
28
+ import {
29
+ closeContinuityIncidentRecord,
30
+ createContinuityIncidentRecord,
31
+ parseContinuityImprovementLoops,
32
+ parseContinuityIncident,
33
+ reviewContinuityLoopInMarkdown,
34
+ serializeContinuityIncident,
35
+ upsertContinuityLoopInMarkdown
36
+ } from "./chunk-QSVPYQPG.js";
37
+ import {
38
+ sanitizeMemoryContent
39
+ } from "./chunk-M62O4P4T.js";
40
+ import {
41
+ log
42
+ } from "./chunk-KWBU5S5U.js";
43
+
44
+ // src/storage.ts
45
+ import { access, readdir, readFile, stat, writeFile, mkdir, unlink, rename, appendFile } from "fs/promises";
46
+ import { appendFileSync, mkdirSync, statSync } from "fs";
47
+ import { createHash } from "crypto";
48
+ import path from "path";
49
+ var ARTIFACT_SEARCH_STOPWORDS = /* @__PURE__ */ new Set([
50
+ "a",
51
+ "an",
52
+ "and",
53
+ "are",
54
+ "as",
55
+ "at",
56
+ "be",
57
+ "but",
58
+ "by",
59
+ "for",
60
+ "from",
61
+ "has",
62
+ "have",
63
+ "i",
64
+ "in",
65
+ "is",
66
+ "it",
67
+ "of",
68
+ "on",
69
+ "or",
70
+ "that",
71
+ "the",
72
+ "this",
73
+ "to",
74
+ "was",
75
+ "were",
76
+ "with"
77
+ ]);
78
+ function tokenizeArtifactSearchText(input) {
79
+ return input.toLowerCase().split(/[^a-z0-9]+/i).map((t) => t.trim()).filter((t) => t.length >= 2).filter((t) => !ARTIFACT_SEARCH_STOPWORDS.has(t));
80
+ }
81
+ function serializeFrontmatter(fm) {
82
+ const lines = [
83
+ "---",
84
+ `id: ${fm.id}`,
85
+ `category: ${fm.category}`,
86
+ `created: ${fm.created}`,
87
+ `updated: ${fm.updated}`,
88
+ `source: ${fm.source}`,
89
+ `confidence: ${fm.confidence}`,
90
+ `confidenceTier: ${fm.confidenceTier}`,
91
+ `tags: [${fm.tags.map((t) => `"${t}"`).join(", ")}]`
92
+ ];
93
+ if (fm.entityRef) lines.push(`entityRef: ${fm.entityRef}`);
94
+ if (fm.supersedes) lines.push(`supersedes: ${fm.supersedes}`);
95
+ if (fm.expiresAt) lines.push(`expiresAt: ${fm.expiresAt}`);
96
+ if (fm.lineage && fm.lineage.length > 0) {
97
+ lines.push(`lineage: [${fm.lineage.map((l) => `"${l}"`).join(", ")}]`);
98
+ }
99
+ if (fm.status && fm.status !== "active") lines.push(`status: ${fm.status}`);
100
+ if (fm.supersededBy) lines.push(`supersededBy: ${fm.supersededBy}`);
101
+ if (fm.supersededAt) lines.push(`supersededAt: ${fm.supersededAt}`);
102
+ if (fm.archivedAt) lines.push(`archivedAt: ${fm.archivedAt}`);
103
+ if (fm.lifecycleState) lines.push(`lifecycleState: ${fm.lifecycleState}`);
104
+ if (fm.verificationState) lines.push(`verificationState: ${fm.verificationState}`);
105
+ if (fm.policyClass) lines.push(`policyClass: ${fm.policyClass}`);
106
+ if (fm.lastValidatedAt) lines.push(`lastValidatedAt: ${fm.lastValidatedAt}`);
107
+ if (fm.decayScore !== void 0) lines.push(`decayScore: ${fm.decayScore}`);
108
+ if (fm.heatScore !== void 0) lines.push(`heatScore: ${fm.heatScore}`);
109
+ if (fm.accessCount !== void 0 && fm.accessCount > 0) {
110
+ lines.push(`accessCount: ${fm.accessCount}`);
111
+ }
112
+ if (fm.lastAccessed) lines.push(`lastAccessed: ${fm.lastAccessed}`);
113
+ if (fm.importance) {
114
+ lines.push(`importanceScore: ${fm.importance.score}`);
115
+ lines.push(`importanceLevel: ${fm.importance.level}`);
116
+ if (fm.importance.reasons.length > 0) {
117
+ lines.push(
118
+ `importanceReasons: [${fm.importance.reasons.map((r) => `"${r.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`).join(", ")}]`
119
+ );
120
+ }
121
+ if (fm.importance.keywords.length > 0) {
122
+ lines.push(`importanceKeywords: [${fm.importance.keywords.map((k) => `"${k}"`).join(", ")}]`);
123
+ }
124
+ }
125
+ if (fm.parentId) lines.push(`parentId: ${fm.parentId}`);
126
+ if (fm.chunkIndex !== void 0) lines.push(`chunkIndex: ${fm.chunkIndex}`);
127
+ if (fm.chunkTotal !== void 0) lines.push(`chunkTotal: ${fm.chunkTotal}`);
128
+ if (fm.links && fm.links.length > 0) {
129
+ lines.push("links:");
130
+ for (const link of fm.links) {
131
+ lines.push(` - targetId: ${link.targetId}`);
132
+ lines.push(` linkType: ${link.linkType}`);
133
+ lines.push(` strength: ${link.strength}`);
134
+ if (link.reason) lines.push(` reason: ${JSON.stringify(link.reason)}`);
135
+ }
136
+ }
137
+ if (fm.intentGoal) lines.push(`intentGoal: ${fm.intentGoal}`);
138
+ if (fm.intentActionType) lines.push(`intentActionType: ${fm.intentActionType}`);
139
+ if (fm.intentEntityTypes && fm.intentEntityTypes.length > 0) {
140
+ lines.push(`intentEntityTypes: [${fm.intentEntityTypes.map((t) => `"${t}"`).join(", ")}]`);
141
+ }
142
+ if (fm.artifactType) lines.push(`artifactType: ${fm.artifactType}`);
143
+ if (fm.sourceMemoryId) lines.push(`sourceMemoryId: ${fm.sourceMemoryId}`);
144
+ if (fm.sourceTurnId) lines.push(`sourceTurnId: ${fm.sourceTurnId}`);
145
+ if (fm.memoryKind) lines.push(`memoryKind: ${fm.memoryKind}`);
146
+ if (fm.structuredAttributes && Object.keys(fm.structuredAttributes).length > 0) {
147
+ lines.push(`structuredAttributes: ${JSON.stringify(fm.structuredAttributes)}`);
148
+ }
149
+ lines.push("---");
150
+ return lines.join("\n");
151
+ }
152
+ function parseStructuredAttributes(raw) {
153
+ if (!raw || !raw.trim()) return void 0;
154
+ try {
155
+ const parsed = JSON.parse(raw);
156
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
157
+ const result = {};
158
+ for (const [k, v] of Object.entries(parsed)) {
159
+ if (typeof k === "string" && typeof v === "string") {
160
+ result[k] = v;
161
+ }
162
+ }
163
+ return Object.keys(result).length > 0 ? result : void 0;
164
+ }
165
+ } catch {
166
+ }
167
+ return void 0;
168
+ }
169
+ function parseLinkReasonValue(rawValue) {
170
+ const legacyValue = rawValue.replace(/\\"/g, '"');
171
+ const looksLikeLegacyPath = !rawValue.includes("\\\\") && (/[A-Za-z]:\\[A-Za-z0-9._ -]+(?:\\[A-Za-z0-9._ -]+)*/.test(rawValue) || /\\[A-Za-z0-9._ -]+\\[A-Za-z0-9._ -]+/.test(rawValue));
172
+ if (looksLikeLegacyPath) {
173
+ return legacyValue;
174
+ }
175
+ try {
176
+ return JSON.parse(`"${rawValue}"`);
177
+ } catch {
178
+ return legacyValue;
179
+ }
180
+ }
181
+ function parseFrontmatter(raw) {
182
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
183
+ if (!match) return null;
184
+ const fmBlock = match[1];
185
+ const content = match[2].trim();
186
+ const fm = {};
187
+ for (const line of fmBlock.split("\n")) {
188
+ const colonIdx = line.indexOf(":");
189
+ if (colonIdx === -1) continue;
190
+ const key = line.slice(0, colonIdx).trim();
191
+ const value = line.slice(colonIdx + 1).trim();
192
+ fm[key] = value;
193
+ }
194
+ let tags = [];
195
+ const tagsStr = fm.tags ?? "";
196
+ const tagMatch = tagsStr.match(/\[(.*)]/);
197
+ if (tagMatch) {
198
+ tags = tagMatch[1].split(",").map((t) => t.trim().replace(/^"|"$/g, "")).filter(Boolean);
199
+ }
200
+ let intentEntityTypes;
201
+ const intentEntityTypesStr = fm.intentEntityTypes ?? "";
202
+ const intentEntityTypesMatch = intentEntityTypesStr.match(/\[(.*)]/);
203
+ if (intentEntityTypesMatch) {
204
+ intentEntityTypes = intentEntityTypesMatch[1].split(",").map((t) => t.trim().replace(/^"|"$/g, "")).filter(Boolean);
205
+ }
206
+ const conf = parseFloat(fm.confidence ?? "0.8");
207
+ let lineage;
208
+ const lineageStr = fm.lineage ?? "";
209
+ const lineageMatch = lineageStr.match(/\[(.*)]/);
210
+ if (lineageMatch) {
211
+ lineage = lineageMatch[1].split(",").map((l) => l.trim().replace(/^"|"$/g, "")).filter(Boolean);
212
+ }
213
+ const accessCount = fm.accessCount ? parseInt(fm.accessCount, 10) : void 0;
214
+ const decayScore = fm.decayScore !== void 0 ? parseFloat(fm.decayScore) : void 0;
215
+ const heatScore = fm.heatScore !== void 0 ? parseFloat(fm.heatScore) : void 0;
216
+ let importance;
217
+ if (fm.importanceScore) {
218
+ const score = parseFloat(fm.importanceScore);
219
+ const level = fm.importanceLevel || "normal";
220
+ let reasons = [];
221
+ const reasonsStr = fm.importanceReasons ?? "";
222
+ if (reasonsStr.trim().startsWith("[") && reasonsStr.trim().endsWith("]")) {
223
+ const reasonMatches = reasonsStr.matchAll(/"((?:\\.|[^"\\])*)"/g);
224
+ for (const match2 of reasonMatches) {
225
+ const reason = parseLinkReasonValue(match2[1]);
226
+ if (reason.length > 0) {
227
+ reasons.push(reason);
228
+ }
229
+ }
230
+ }
231
+ let keywords = [];
232
+ const keywordsStr = fm.importanceKeywords ?? "";
233
+ const keywordsMatch = keywordsStr.match(/\[(.*)]/);
234
+ if (keywordsMatch) {
235
+ keywords = keywordsMatch[1].split(",").map((k) => k.trim().replace(/^"|"$/g, "")).filter(Boolean);
236
+ }
237
+ importance = { score, level, reasons, keywords };
238
+ }
239
+ const result = {
240
+ frontmatter: {
241
+ id: fm.id ?? "",
242
+ category: fm.category ?? "fact",
243
+ created: fm.created ?? (/* @__PURE__ */ new Date()).toISOString(),
244
+ updated: fm.updated ?? (/* @__PURE__ */ new Date()).toISOString(),
245
+ source: fm.source ?? "unknown",
246
+ confidence: conf,
247
+ confidenceTier: fm.confidenceTier || confidenceTier(conf),
248
+ tags,
249
+ entityRef: fm.entityRef || void 0,
250
+ supersedes: fm.supersedes || void 0,
251
+ expiresAt: fm.expiresAt || void 0,
252
+ lineage: lineage && lineage.length > 0 ? lineage : void 0,
253
+ // Status management
254
+ status: fm.status || "active",
255
+ supersededBy: fm.supersededBy || void 0,
256
+ supersededAt: fm.supersededAt || void 0,
257
+ archivedAt: fm.archivedAt || void 0,
258
+ lifecycleState: fm.lifecycleState || void 0,
259
+ verificationState: fm.verificationState || void 0,
260
+ policyClass: fm.policyClass || void 0,
261
+ lastValidatedAt: fm.lastValidatedAt || void 0,
262
+ decayScore: Number.isFinite(decayScore) ? decayScore : void 0,
263
+ heatScore: Number.isFinite(heatScore) ? heatScore : void 0,
264
+ // Access tracking
265
+ accessCount: accessCount && accessCount > 0 ? accessCount : void 0,
266
+ lastAccessed: fm.lastAccessed || void 0,
267
+ // Importance scoring
268
+ importance,
269
+ // Chunking
270
+ parentId: fm.parentId || void 0,
271
+ chunkIndex: fm.chunkIndex ? parseInt(fm.chunkIndex, 10) : void 0,
272
+ chunkTotal: fm.chunkTotal ? parseInt(fm.chunkTotal, 10) : void 0,
273
+ // Links are parsed separately below
274
+ intentGoal: fm.intentGoal || void 0,
275
+ intentActionType: fm.intentActionType || void 0,
276
+ intentEntityTypes: intentEntityTypes && intentEntityTypes.length > 0 ? intentEntityTypes : void 0,
277
+ artifactType: fm.artifactType || void 0,
278
+ sourceMemoryId: fm.sourceMemoryId || void 0,
279
+ sourceTurnId: fm.sourceTurnId || void 0,
280
+ // v8.0 Phase 2B: HiMem episode/note classification
281
+ memoryKind: fm.memoryKind || void 0,
282
+ // Structured attributes (JSON on a single line)
283
+ structuredAttributes: parseStructuredAttributes(fm.structuredAttributes)
284
+ },
285
+ content
286
+ };
287
+ if (fmBlock.includes("links:")) {
288
+ const links = [];
289
+ const linkMatches = fmBlock.matchAll(
290
+ /- targetId: (\S+)\s+linkType: (\S+)\s+strength: ([\d.]+)(?:\s+reason: "((?:\\.|[^"\\])*)")?/g
291
+ );
292
+ for (const match2 of linkMatches) {
293
+ links.push({
294
+ targetId: match2[1],
295
+ linkType: match2[2],
296
+ strength: parseFloat(match2[3]),
297
+ reason: match2[4] ? parseLinkReasonValue(match2[4]) : void 0
298
+ });
299
+ }
300
+ if (links.length > 0) {
301
+ result.frontmatter.links = links;
302
+ }
303
+ }
304
+ return result;
305
+ }
306
+ function normalizeFrontmatterForPath(frontmatter, pathRel) {
307
+ if (isArchivedMemoryPath(pathRel) && (!frontmatter.status || frontmatter.status === "active")) {
308
+ return {
309
+ ...frontmatter,
310
+ status: "archived"
311
+ };
312
+ }
313
+ return frontmatter;
314
+ }
315
+ function inferCurrentStateStatus(frontmatter, pathRel, fallbackStatus) {
316
+ return inferMemoryStatus(frontmatter, pathRel, fallbackStatus);
317
+ }
318
+ var userAliases = {};
319
+ var BUILTIN_ALIASES = {
320
+ openclaw: "openclaw",
321
+ "open-claw": "openclaw"
322
+ };
323
+ function normalizeEntityName(raw, type) {
324
+ const rawStr = typeof raw === "string" ? raw : "";
325
+ const typeStr = typeof type === "string" && type.trim().length > 0 ? type : "entity";
326
+ let name = rawStr.toLowerCase().trim();
327
+ const typePrefix = `${typeStr.toLowerCase()}-`;
328
+ if (name.startsWith(typePrefix)) {
329
+ name = name.slice(typePrefix.length);
330
+ }
331
+ let normalized = name.replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
332
+ if (userAliases[normalized]) {
333
+ normalized = userAliases[normalized];
334
+ } else if (BUILTIN_ALIASES[normalized]) {
335
+ normalized = BUILTIN_ALIASES[normalized];
336
+ }
337
+ return `${typeStr.toLowerCase()}-${normalized}`;
338
+ }
339
+ function levenshtein(a, b) {
340
+ const m = a.length;
341
+ const n = b.length;
342
+ if (m === 0) return n;
343
+ if (n === 0) return m;
344
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
345
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
346
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
347
+ for (let i = 1; i <= m; i++) {
348
+ for (let j = 1; j <= n; j++) {
349
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
350
+ }
351
+ }
352
+ return dp[m][n];
353
+ }
354
+ function dehyphenate(s) {
355
+ return s.replace(/-/g, "");
356
+ }
357
+ var ContentHashIndex = class _ContentHashIndex {
358
+ hashes = /* @__PURE__ */ new Set();
359
+ dirty = false;
360
+ filePath;
361
+ constructor(stateDir) {
362
+ this.filePath = path.join(stateDir, "fact-hashes.txt");
363
+ }
364
+ /** Load existing hashes from disk. Safe to call multiple times. */
365
+ async load() {
366
+ try {
367
+ const raw = await readFile(this.filePath, "utf-8");
368
+ for (const line of raw.split("\n")) {
369
+ const trimmed = line.trim();
370
+ if (trimmed.length > 0) {
371
+ this.hashes.add(trimmed);
372
+ }
373
+ }
374
+ log.debug(`content-hash index: loaded ${this.hashes.size} hashes`);
375
+ } catch {
376
+ log.debug("content-hash index: no existing index \u2014 starting fresh");
377
+ }
378
+ }
379
+ /** Check if content already exists in the index. */
380
+ has(content) {
381
+ return this.hashes.has(_ContentHashIndex.computeHash(content));
382
+ }
383
+ /** Add content hash to the index. */
384
+ add(content) {
385
+ const hash = _ContentHashIndex.computeHash(content);
386
+ if (!this.hashes.has(hash)) {
387
+ this.hashes.add(hash);
388
+ this.dirty = true;
389
+ }
390
+ }
391
+ get size() {
392
+ return this.hashes.size;
393
+ }
394
+ /** Persist index to disk if changed. */
395
+ async save() {
396
+ if (!this.dirty) return;
397
+ await mkdir(path.dirname(this.filePath), { recursive: true });
398
+ await writeFile(this.filePath, [...this.hashes].join("\n") + "\n", "utf-8");
399
+ this.dirty = false;
400
+ log.debug(`content-hash index: saved ${this.hashes.size} hashes`);
401
+ }
402
+ /** Remove a hash from the index (used when archiving/deleting). */
403
+ remove(content) {
404
+ const hash = _ContentHashIndex.computeHash(content);
405
+ if (this.hashes.delete(hash)) {
406
+ this.dirty = true;
407
+ }
408
+ }
409
+ /** Normalize content and compute SHA-256 hash. */
410
+ static normalizeContent(content) {
411
+ return content.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
412
+ }
413
+ /** Normalize content and compute SHA-256 hash. */
414
+ static computeHash(content) {
415
+ const normalized = _ContentHashIndex.normalizeContent(content);
416
+ return createHash("sha256").update(normalized).digest("hex");
417
+ }
418
+ };
419
+ function parseEntityFile(content) {
420
+ const lines = content.split("\n");
421
+ let name = "";
422
+ let type = "other";
423
+ let updated = "";
424
+ let summary;
425
+ const facts = [];
426
+ const relationships = [];
427
+ const activity = [];
428
+ const aliases = [];
429
+ const headingLine = lines.find((l) => l.startsWith("# "));
430
+ if (headingLine) name = headingLine.slice(2).trim();
431
+ const typeLine = lines.find((l) => l.startsWith("**Type:**"));
432
+ if (typeLine) type = typeLine.replace("**Type:**", "").trim();
433
+ const updatedLine = lines.find((l) => l.startsWith("**Updated:**"));
434
+ if (updatedLine) updated = updatedLine.replace("**Updated:**", "").trim();
435
+ let section = "";
436
+ for (const line of lines) {
437
+ if (line.startsWith("## ")) {
438
+ section = line.slice(3).trim().toLowerCase();
439
+ continue;
440
+ }
441
+ if (!line.startsWith("- ")) continue;
442
+ const bullet = line.slice(2).trim();
443
+ if (!bullet) continue;
444
+ switch (section) {
445
+ case "facts":
446
+ facts.push(bullet);
447
+ break;
448
+ case "summary":
449
+ break;
450
+ case "connected to": {
451
+ const relMatch = bullet.match(/^\[\[([^\]]+)\]\]\s*[—–-]\s*(.+)$/);
452
+ if (relMatch) {
453
+ relationships.push({ target: relMatch[1].trim(), label: relMatch[2].trim() });
454
+ }
455
+ break;
456
+ }
457
+ case "activity": {
458
+ const actMatch = bullet.match(/^(\d{4}-\d{2}-\d{2}):\s*(.+)$/);
459
+ if (actMatch) {
460
+ activity.push({ date: actMatch[1], note: actMatch[2].trim() });
461
+ }
462
+ break;
463
+ }
464
+ case "aliases":
465
+ aliases.push(bullet);
466
+ break;
467
+ }
468
+ }
469
+ const summaryIdx = lines.findIndex((l) => l.startsWith("## Summary"));
470
+ if (summaryIdx !== -1) {
471
+ const summaryLines = [];
472
+ for (let i = summaryIdx + 1; i < lines.length; i++) {
473
+ if (lines[i].startsWith("## ")) break;
474
+ const trimmed = lines[i].trim();
475
+ if (trimmed) summaryLines.push(trimmed);
476
+ }
477
+ if (summaryLines.length > 0) summary = summaryLines.join(" ");
478
+ }
479
+ return { name, type, updated, facts, summary, relationships, activity, aliases };
480
+ }
481
+ function serializeEntityFile(entity) {
482
+ const lines = [
483
+ `# ${entity.name}`,
484
+ "",
485
+ `**Type:** ${entity.type}`,
486
+ `**Updated:** ${entity.updated || (/* @__PURE__ */ new Date()).toISOString()}`,
487
+ ""
488
+ ];
489
+ if (entity.summary) {
490
+ lines.push("## Summary", "", entity.summary, "");
491
+ }
492
+ lines.push("## Facts", "");
493
+ for (const f of entity.facts) {
494
+ lines.push(`- ${f}`);
495
+ }
496
+ lines.push("");
497
+ if (entity.relationships.length > 0) {
498
+ lines.push("## Connected to", "");
499
+ for (const rel of entity.relationships) {
500
+ lines.push(`- [[${rel.target}]] \u2014 ${rel.label}`);
501
+ }
502
+ lines.push("");
503
+ }
504
+ if (entity.activity.length > 0) {
505
+ lines.push("## Activity", "");
506
+ for (const act of entity.activity) {
507
+ lines.push(`- ${act.date}: ${act.note}`);
508
+ }
509
+ lines.push("");
510
+ }
511
+ if (entity.aliases.length > 0) {
512
+ lines.push("## Aliases", "");
513
+ for (const alias of entity.aliases) {
514
+ lines.push(`- ${alias}`);
515
+ }
516
+ lines.push("");
517
+ }
518
+ return lines.join("\n");
519
+ }
520
+ var StorageManager = class _StorageManager {
521
+ constructor(baseDir) {
522
+ this.baseDir = baseDir;
523
+ }
524
+ baseDir;
525
+ knowledgeIndexCache = null;
526
+ static KNOWLEDGE_INDEX_CACHE_TTL_MS = 6e5;
527
+ // 10 minutes (entity mutations invalidate)
528
+ artifactIndexCache = null;
529
+ static ARTIFACT_INDEX_CACHE_TTL_MS = 6e4;
530
+ // 1 minute
531
+ static artifactWriteVersionByDir = /* @__PURE__ */ new Map();
532
+ static memoryStatusVersionByDir = /* @__PURE__ */ new Map();
533
+ // Module-level cache for readAllMemories() keyed by base directory.
534
+ // Shared across all StorageManager instances to avoid duplicate I/O when
535
+ // multiple concurrent callers (e.g. verifiedRecall + verifiedRules) read the
536
+ // same directory simultaneously. In-flight deduplication prevents multiple
537
+ // concurrent reads of the same directory.
538
+ //
539
+ // Stale-while-revalidate: once the cache has a value, subsequent reads after
540
+ // TTL expiry return the stale cached data immediately and kick off a background
541
+ // refresh. This eliminates the 13-60 s cold-scan penalty that would otherwise
542
+ // block recall requests every 5 minutes on large memory collections (80k+ files).
543
+ static allMemoriesInFlight = /* @__PURE__ */ new Map();
544
+ // Cache for readQuestions() — avoids serially re-reading tens of thousands of
545
+ // question files on every recall. 60-second TTL is intentionally short so that
546
+ // newly written questions surface quickly.
547
+ static QUESTIONS_CACHE_TTL_MS = 6e4;
548
+ // 1 minute
549
+ static questionsCache = /* @__PURE__ */ new Map();
550
+ factHashIndex = null;
551
+ factHashIndexLoadPromise = null;
552
+ factHashIndexAuthoritative = null;
553
+ factHashIndexAuthoritativePromise = null;
554
+ /** The root directory of this storage instance. */
555
+ get dir() {
556
+ return this.baseDir;
557
+ }
558
+ identityFilePath(workspaceDir, namespace) {
559
+ const rawNamespace = typeof namespace === "string" ? namespace.trim() : "";
560
+ if (!rawNamespace) return path.join(workspaceDir, "IDENTITY.md");
561
+ const safeNamespace = rawNamespace.replace(/[^a-zA-Z0-9._-]/g, "-");
562
+ return path.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
563
+ }
564
+ versionFilePath(kind) {
565
+ const fileName = kind === "memory-status" ? ".memory-status-version.log" : ".artifact-write-version.log";
566
+ return path.join(this.stateDir, fileName);
567
+ }
568
+ bumpSharedVersion(kind, fallbackMap) {
569
+ const filePath = this.versionFilePath(kind);
570
+ try {
571
+ mkdirSync(this.stateDir, { recursive: true });
572
+ appendFileSync(filePath, "x");
573
+ const next = statSync(filePath).size;
574
+ fallbackMap.set(this.baseDir, next);
575
+ return next;
576
+ } catch {
577
+ const next = (fallbackMap.get(this.baseDir) ?? 0) + 1;
578
+ fallbackMap.set(this.baseDir, next);
579
+ return next;
580
+ }
581
+ }
582
+ readSharedVersion(kind, fallbackMap) {
583
+ const filePath = this.versionFilePath(kind);
584
+ try {
585
+ return statSync(filePath).size;
586
+ } catch {
587
+ return fallbackMap.get(this.baseDir) ?? 0;
588
+ }
589
+ }
590
+ bumpMemoryStatusVersion() {
591
+ this.bumpSharedVersion("memory-status", _StorageManager.memoryStatusVersionByDir);
592
+ }
593
+ getMemoryStatusVersion() {
594
+ return this.readSharedVersion("memory-status", _StorageManager.memoryStatusVersionByDir);
595
+ }
596
+ bumpArtifactWriteVersion() {
597
+ return this.bumpSharedVersion("artifact-write", _StorageManager.artifactWriteVersionByDir);
598
+ }
599
+ getArtifactWriteVersion() {
600
+ return this.readSharedVersion("artifact-write", _StorageManager.artifactWriteVersionByDir);
601
+ }
602
+ get factsDir() {
603
+ return path.join(this.baseDir, "facts");
604
+ }
605
+ get correctionsDir() {
606
+ return path.join(this.baseDir, "corrections");
607
+ }
608
+ get entitiesDir() {
609
+ return path.join(this.baseDir, "entities");
610
+ }
611
+ get stateDir() {
612
+ return path.join(this.baseDir, "state");
613
+ }
614
+ get factHashIndexReadyPath() {
615
+ return path.join(this.stateDir, "fact-hashes.ready");
616
+ }
617
+ async getFactHashIndex() {
618
+ if (this.factHashIndex) {
619
+ return this.factHashIndex;
620
+ }
621
+ if (!this.factHashIndexLoadPromise) {
622
+ const index = new ContentHashIndex(this.stateDir);
623
+ this.factHashIndexLoadPromise = index.load().then(() => {
624
+ this.factHashIndex = index;
625
+ return index;
626
+ }).catch((err) => {
627
+ this.factHashIndexLoadPromise = null;
628
+ throw err;
629
+ });
630
+ }
631
+ return this.factHashIndexLoadPromise;
632
+ }
633
+ async ensureFactHashIndexAuthoritative() {
634
+ if (this.factHashIndexAuthoritative === true) {
635
+ return;
636
+ }
637
+ if (this.factHashIndexAuthoritativePromise) {
638
+ await this.factHashIndexAuthoritativePromise;
639
+ return;
640
+ }
641
+ this.factHashIndexAuthoritativePromise = (async () => {
642
+ try {
643
+ await access(this.factHashIndexReadyPath);
644
+ this.factHashIndexAuthoritative = true;
645
+ return;
646
+ } catch {
647
+ }
648
+ const factHashIndex = await this.getFactHashIndex();
649
+ const existing = await this.readAllMemories();
650
+ for (const memory of existing) {
651
+ if (memory.frontmatter.category !== "fact") continue;
652
+ if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") continue;
653
+ factHashIndex.add(memory.content);
654
+ }
655
+ await factHashIndex.save();
656
+ await mkdir(path.dirname(this.factHashIndexReadyPath), { recursive: true });
657
+ await writeFile(this.factHashIndexReadyPath, "v1\n", "utf-8");
658
+ this.factHashIndexAuthoritative = true;
659
+ })().finally(() => {
660
+ this.factHashIndexAuthoritativePromise = null;
661
+ });
662
+ await this.factHashIndexAuthoritativePromise;
663
+ }
664
+ get questionsDir() {
665
+ return path.join(this.baseDir, "questions");
666
+ }
667
+ get artifactsDir() {
668
+ return path.join(this.baseDir, "artifacts");
669
+ }
670
+ get identityDir() {
671
+ return path.join(this.baseDir, "identity");
672
+ }
673
+ get identityAnchorPath() {
674
+ return path.join(this.identityDir, "identity-anchor.md");
675
+ }
676
+ get identityIncidentsDir() {
677
+ return path.join(this.identityDir, "incidents");
678
+ }
679
+ get identityAuditsWeeklyDir() {
680
+ return path.join(this.identityDir, "audits", "weekly");
681
+ }
682
+ get identityAuditsMonthlyDir() {
683
+ return path.join(this.identityDir, "audits", "monthly");
684
+ }
685
+ get identityImprovementLoopsPath() {
686
+ return path.join(this.identityDir, "improvement-loops.md");
687
+ }
688
+ get identityReflectionsPath() {
689
+ return path.join(this.identityDir, "reflections.md");
690
+ }
691
+ get profilePath() {
692
+ return path.join(this.baseDir, "profile.md");
693
+ }
694
+ get memoryActionsPath() {
695
+ return path.join(this.stateDir, "memory-actions.jsonl");
696
+ }
697
+ get memoryLifecycleLedgerPath() {
698
+ return path.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
699
+ }
700
+ get compressionGuidelinesPath() {
701
+ return path.join(this.stateDir, "compression-guidelines.md");
702
+ }
703
+ get compressionGuidelineDraftPath() {
704
+ return path.join(this.stateDir, "compression-guidelines.draft.md");
705
+ }
706
+ get compressionGuidelineStatePath() {
707
+ return path.join(this.stateDir, "compression-guideline-state.json");
708
+ }
709
+ get compressionGuidelineDraftStatePath() {
710
+ return path.join(this.stateDir, "compression-guideline-draft-state.json");
711
+ }
712
+ get behaviorSignalsPath() {
713
+ return path.join(this.stateDir, "behavior-signals.jsonl");
714
+ }
715
+ /**
716
+ * Load user-defined entity aliases from config/aliases.json in the memory store.
717
+ * File format: { "variant": "canonical", "variant2": "canonical", ... }
718
+ * Call this once at startup (e.g. from orchestrator.initialize()).
719
+ */
720
+ async loadAliases() {
721
+ const aliasPath = path.join(this.baseDir, "config", "aliases.json");
722
+ try {
723
+ const raw = await readFile(aliasPath, "utf-8");
724
+ const parsed = JSON.parse(raw);
725
+ if (typeof parsed === "object" && parsed !== null) {
726
+ userAliases = parsed;
727
+ log.debug(`loaded ${Object.keys(userAliases).length} entity aliases from ${aliasPath}`);
728
+ }
729
+ } catch {
730
+ log.debug("no config/aliases.json found \u2014 using built-in aliases only");
731
+ }
732
+ }
733
+ async ensureDirectories() {
734
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
735
+ await mkdir(path.join(this.factsDir, today), { recursive: true });
736
+ await mkdir(this.correctionsDir, { recursive: true });
737
+ await mkdir(this.entitiesDir, { recursive: true });
738
+ await mkdir(this.stateDir, { recursive: true });
739
+ await mkdir(this.questionsDir, { recursive: true });
740
+ await mkdir(this.artifactsDir, { recursive: true });
741
+ await mkdir(this.identityDir, { recursive: true });
742
+ await mkdir(this.identityIncidentsDir, { recursive: true });
743
+ await mkdir(this.identityAuditsWeeklyDir, { recursive: true });
744
+ await mkdir(this.identityAuditsMonthlyDir, { recursive: true });
745
+ await mkdir(path.join(this.baseDir, "config"), { recursive: true });
746
+ }
747
+ async writeMemory(category, content, options = {}) {
748
+ await this.ensureDirectories();
749
+ const now = /* @__PURE__ */ new Date();
750
+ const today = now.toISOString().slice(0, 10);
751
+ const id = `${category}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
752
+ const conf = options.confidence ?? 0.8;
753
+ const tier = confidenceTier(conf);
754
+ let expiresAt;
755
+ if (typeof options.expiresAt === "string" && options.expiresAt.length > 0) {
756
+ expiresAt = options.expiresAt;
757
+ } else if (tier === "speculative") {
758
+ const expiry = new Date(now.getTime() + SPECULATIVE_TTL_DAYS * 24 * 60 * 60 * 1e3);
759
+ expiresAt = expiry.toISOString();
760
+ }
761
+ const fm = {
762
+ id,
763
+ category,
764
+ created: now.toISOString(),
765
+ updated: now.toISOString(),
766
+ source: options.source ?? "extraction",
767
+ confidence: conf,
768
+ confidenceTier: tier,
769
+ tags: options.tags ?? [],
770
+ entityRef: options.entityRef,
771
+ supersedes: options.supersedes,
772
+ expiresAt,
773
+ lineage: options.lineage,
774
+ importance: options.importance,
775
+ links: options.links,
776
+ intentGoal: options.intentGoal,
777
+ intentActionType: options.intentActionType,
778
+ intentEntityTypes: options.intentEntityTypes,
779
+ artifactType: options.artifactType,
780
+ sourceMemoryId: options.sourceMemoryId,
781
+ sourceTurnId: options.sourceTurnId,
782
+ memoryKind: options.memoryKind,
783
+ structuredAttributes: options.structuredAttributes
784
+ };
785
+ let enrichedContent = content;
786
+ if (options.structuredAttributes && Object.keys(options.structuredAttributes).length > 0) {
787
+ const attrLines = Object.entries(options.structuredAttributes).map(([k, v]) => `${k}: ${v}`).join("; ");
788
+ enrichedContent = `${content}
789
+ [Attributes: ${attrLines}]`;
790
+ }
791
+ const sanitized = sanitizeMemoryContent(enrichedContent);
792
+ if (!sanitized.clean) {
793
+ log.warn(`memory content sanitized for ${id}; violations=${sanitized.violations.join(", ")}`);
794
+ }
795
+ const fileContent = `${serializeFrontmatter(fm)}
796
+
797
+ ${sanitized.text}
798
+ `;
799
+ let filePath;
800
+ if (category === "correction") {
801
+ filePath = path.join(this.correctionsDir, `${id}.md`);
802
+ } else {
803
+ filePath = path.join(this.factsDir, today, `${id}.md`);
804
+ }
805
+ await writeFile(filePath, fileContent, "utf-8");
806
+ this.invalidateAllMemoriesCache();
807
+ await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeMemory", {
808
+ memoryId: id,
809
+ eventType: "created",
810
+ timestamp: fm.created,
811
+ actor: options.actor ?? "storage.writeMemory",
812
+ after: this.summarizeLifecycleState(fm, filePath),
813
+ relatedMemoryIds: [
814
+ ...options.supersedes ? [options.supersedes] : [],
815
+ ...(options.lineage ?? []).filter(Boolean)
816
+ ]
817
+ });
818
+ if (category === "fact") {
819
+ try {
820
+ const factHashIndex = await this.getFactHashIndex();
821
+ factHashIndex.add(sanitized.text);
822
+ await factHashIndex.save();
823
+ } catch (err) {
824
+ log.warn(`storage.writeMemory completed but failed to update fact hash index: ${err}`);
825
+ }
826
+ }
827
+ log.debug(`wrote memory ${id} to ${filePath}`);
828
+ return id;
829
+ }
830
+ async hasFactContentHash(content) {
831
+ await this.ensureFactHashIndexAuthoritative();
832
+ const factHashIndex = await this.getFactHashIndex();
833
+ const sanitized = sanitizeMemoryContent(content);
834
+ return factHashIndex.has(sanitized.text);
835
+ }
836
+ async isFactContentHashAuthoritative() {
837
+ await this.ensureFactHashIndexAuthoritative();
838
+ return true;
839
+ }
840
+ async writeArtifact(quote, options = {}) {
841
+ await this.ensureDirectories();
842
+ const now = /* @__PURE__ */ new Date();
843
+ const day = now.toISOString().slice(0, 10);
844
+ const dir = path.join(this.artifactsDir, day);
845
+ await mkdir(dir, { recursive: true });
846
+ const id = `artifact-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
847
+ const fm = {
848
+ id,
849
+ category: "fact",
850
+ created: now.toISOString(),
851
+ updated: now.toISOString(),
852
+ source: "artifact",
853
+ confidence: options.confidence ?? 0.9,
854
+ confidenceTier: confidenceTier(options.confidence ?? 0.9),
855
+ tags: options.tags ?? [],
856
+ artifactType: options.artifactType ?? "fact",
857
+ sourceMemoryId: options.sourceMemoryId,
858
+ sourceTurnId: options.sourceTurnId,
859
+ intentGoal: options.intentGoal,
860
+ intentActionType: options.intentActionType,
861
+ intentEntityTypes: options.intentEntityTypes
862
+ };
863
+ const sanitized = sanitizeMemoryContent(quote);
864
+ if (!sanitized.clean) {
865
+ log.warn(`artifact content rejected for ${id}; violations=${sanitized.violations.join(", ")}`);
866
+ return "";
867
+ }
868
+ const filePath = path.join(dir, `${id}.md`);
869
+ await writeFile(filePath, `${serializeFrontmatter(fm)}
870
+
871
+ ${sanitized.text}
872
+ `, "utf-8");
873
+ const actor = typeof options.actor === "string" && options.actor.length > 0 ? options.actor : "storage.writeArtifact";
874
+ await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeArtifact", {
875
+ memoryId: id,
876
+ eventType: "created",
877
+ timestamp: fm.created,
878
+ actor,
879
+ after: this.summarizeLifecycleState(fm, filePath),
880
+ relatedMemoryIds: options.sourceMemoryId ? [options.sourceMemoryId] : []
881
+ });
882
+ this.bumpArtifactWriteVersion();
883
+ this.artifactIndexCache = null;
884
+ return id;
885
+ }
886
+ async readAllArtifactsCached() {
887
+ if (this.artifactIndexCache && Date.now() - this.artifactIndexCache.loadedAtMs <= _StorageManager.ARTIFACT_INDEX_CACHE_TTL_MS && this.artifactIndexCache.writeVersion === this.getArtifactWriteVersion()) {
888
+ return this.artifactIndexCache.memories;
889
+ }
890
+ const scanArtifacts = async () => {
891
+ const artifacts = [];
892
+ const readDir = async (dir) => {
893
+ try {
894
+ const entries = await readdir(dir, { withFileTypes: true });
895
+ for (const entry of entries) {
896
+ const fullPath = path.join(dir, entry.name);
897
+ if (entry.isDirectory()) {
898
+ await readDir(fullPath);
899
+ continue;
900
+ }
901
+ if (!entry.name.endsWith(".md")) continue;
902
+ const memory = await this.readMemoryByPath(fullPath);
903
+ if (!memory) continue;
904
+ artifacts.push(memory);
905
+ }
906
+ } catch {
907
+ }
908
+ };
909
+ await readDir(this.artifactsDir);
910
+ return artifacts;
911
+ };
912
+ const MAX_REBUILD_RETRIES = 2;
913
+ let latestArtifacts = [];
914
+ for (let attempt = 0; attempt <= MAX_REBUILD_RETRIES; attempt += 1) {
915
+ const versionBefore = this.getArtifactWriteVersion();
916
+ const artifacts = await scanArtifacts();
917
+ const versionAfter = this.getArtifactWriteVersion();
918
+ latestArtifacts = artifacts;
919
+ if (versionAfter === versionBefore) {
920
+ this.artifactIndexCache = { memories: artifacts, loadedAtMs: Date.now(), writeVersion: versionAfter };
921
+ return artifacts;
922
+ }
923
+ }
924
+ this.artifactIndexCache = null;
925
+ return latestArtifacts;
926
+ }
927
+ async searchArtifacts(query, maxResults) {
928
+ const tokens = tokenizeArtifactSearchText(query);
929
+ if (tokens.length === 0) return [];
930
+ const artifacts = await this.readAllArtifactsCached();
931
+ const hits = [];
932
+ for (const memory of artifacts) {
933
+ const indexedTokens = new Set(
934
+ tokenizeArtifactSearchText(`${memory.content} ${(memory.frontmatter.tags ?? []).join(" ")}`)
935
+ );
936
+ const score = tokens.reduce((sum, t) => sum + (indexedTokens.has(t) ? 1 : 0), 0);
937
+ if (score > 0) {
938
+ hits.push({ score, memory });
939
+ }
940
+ }
941
+ hits.sort((a, b) => b.score - a.score);
942
+ return hits.slice(0, maxResults).map((h) => h.memory);
943
+ }
944
+ async writeEntity(name, type, facts) {
945
+ await this.ensureDirectories();
946
+ if (typeof name !== "string" || !name.trim() || typeof type !== "string" || !type.trim()) {
947
+ log.warn("writeEntity: invalid entity payload, skipping", {
948
+ nameType: typeof name,
949
+ typeType: typeof type
950
+ });
951
+ return "";
952
+ }
953
+ const safeFacts = Array.isArray(facts) ? facts.filter((f) => typeof f === "string") : [];
954
+ let normalized = normalizeEntityName(name, type);
955
+ const match = await this.findMatchingEntity(name, type);
956
+ if (match && match !== normalized) {
957
+ log.debug(`fuzzy match: "${normalized}" \u2192 existing "${match}"`);
958
+ normalized = match;
959
+ }
960
+ const filePath = path.join(this.entitiesDir, `${normalized}.md`);
961
+ let entity = {
962
+ name,
963
+ type,
964
+ updated: (/* @__PURE__ */ new Date()).toISOString(),
965
+ facts: [],
966
+ summary: void 0,
967
+ relationships: [],
968
+ activity: [],
969
+ aliases: []
970
+ };
971
+ try {
972
+ const existing = await readFile(filePath, "utf-8");
973
+ entity = parseEntityFile(existing);
974
+ } catch {
975
+ }
976
+ entity.facts = [.../* @__PURE__ */ new Set([...entity.facts, ...safeFacts])];
977
+ entity.name = name;
978
+ entity.type = type;
979
+ entity.updated = (/* @__PURE__ */ new Date()).toISOString();
980
+ await writeFile(filePath, serializeEntityFile(entity), "utf-8");
981
+ this.invalidateKnowledgeIndexCache();
982
+ this.bumpMemoryStatusVersion();
983
+ log.debug(`wrote entity ${normalized}`);
984
+ return normalized;
985
+ }
986
+ async readProfile() {
987
+ try {
988
+ return await readFile(this.profilePath, "utf-8");
989
+ } catch {
990
+ return "";
991
+ }
992
+ }
993
+ async writeProfile(content) {
994
+ await this.ensureDirectories();
995
+ await writeFile(this.profilePath, content, "utf-8");
996
+ log.debug("updated profile.md");
997
+ }
998
+ /**
999
+ * Normalize a string for fuzzy profile dedup: lowercase, strip punctuation, collapse whitespace.
1000
+ */
1001
+ static normalizeForDedup(s) {
1002
+ if (typeof s !== "string") return "";
1003
+ return s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
1004
+ }
1005
+ /**
1006
+ * Check if a new bullet is a fuzzy duplicate of any existing bullet.
1007
+ * Returns true if the new bullet should be skipped.
1008
+ */
1009
+ static isFuzzyDuplicate(newNorm, existingNorms) {
1010
+ for (const existing of existingNorms) {
1011
+ if (newNorm === existing) return true;
1012
+ const shorter = newNorm.length <= existing.length ? newNorm : existing;
1013
+ const longer = newNorm.length > existing.length ? newNorm : existing;
1014
+ if (shorter.length > 20 && shorter.length / longer.length > 0.6 && longer.includes(shorter)) {
1015
+ return true;
1016
+ }
1017
+ }
1018
+ return false;
1019
+ }
1020
+ async appendToProfile(updates) {
1021
+ updates = updates.filter((u) => typeof u === "string" && u.trim().length > 0);
1022
+ if (updates.length === 0) return;
1023
+ const existing = await this.readProfile();
1024
+ const lines = existing ? existing.split("\n") : [];
1025
+ const existingBulletRaw = lines.filter((l) => l.startsWith("- ")).map((l) => l.slice(2).trim());
1026
+ const existingNorms = existingBulletRaw.map(_StorageManager.normalizeForDedup);
1027
+ const newBullets = updates.filter((u) => {
1028
+ const norm = _StorageManager.normalizeForDedup(u);
1029
+ return !_StorageManager.isFuzzyDuplicate(norm, existingNorms);
1030
+ });
1031
+ if (newBullets.length === 0) return;
1032
+ if (!existing) {
1033
+ const content = [
1034
+ "# Behavioral Profile",
1035
+ "",
1036
+ `*Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}*`,
1037
+ "",
1038
+ ...newBullets.map((b) => `- ${b}`),
1039
+ ""
1040
+ ].join("\n");
1041
+ await this.writeProfile(content);
1042
+ } else {
1043
+ const updatedTimestamp = existing.replace(
1044
+ /\*Last updated:.*\*/,
1045
+ `*Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}*`
1046
+ );
1047
+ const withBullets = updatedTimestamp.trimEnd() + "\n" + newBullets.map((b) => `- ${b}`).join("\n") + "\n";
1048
+ await this.writeProfile(withBullets);
1049
+ }
1050
+ }
1051
+ /** Check if profile.md exceeds the max line cap and needs LLM consolidation */
1052
+ async profileNeedsConsolidation(triggerLines) {
1053
+ const profile = await this.readProfile();
1054
+ if (!profile) return false;
1055
+ const lineCount = profile.split("\n").length;
1056
+ const threshold = typeof triggerLines === "number" ? Math.max(0, Math.floor(triggerLines)) : _StorageManager.PROFILE_MAX_LINES;
1057
+ return lineCount > threshold;
1058
+ }
1059
+ async readAllMemories() {
1060
+ const inFlight = _StorageManager.allMemoriesInFlight.get(this.baseDir);
1061
+ if (inFlight) return inFlight;
1062
+ const readPromise = this._readAllMemoriesFromDisk();
1063
+ _StorageManager.allMemoriesInFlight.set(this.baseDir, readPromise);
1064
+ try {
1065
+ return await readPromise;
1066
+ } finally {
1067
+ if (_StorageManager.allMemoriesInFlight.get(this.baseDir) === readPromise) {
1068
+ _StorageManager.allMemoriesInFlight.delete(this.baseDir);
1069
+ }
1070
+ }
1071
+ }
1072
+ /** Invalidate the readAllMemories() cache after writes that add/remove memories. */
1073
+ /** Public cache invalidation for callers that need authoritative disk reads
1074
+ * (e.g. projection verify/rebuild). */
1075
+ invalidateAllMemoriesCacheForDir() {
1076
+ this.invalidateAllMemoriesCache();
1077
+ }
1078
+ /** Clear ALL static caches. Use in tests that write files directly
1079
+ * (bypassing StorageManager.writeMemory) to avoid stale reads. */
1080
+ static clearAllStaticCaches() {
1081
+ _StorageManager.allMemoriesInFlight.clear();
1082
+ _StorageManager.questionsCache.clear();
1083
+ }
1084
+ /** Cancel any in-flight concurrent read so the next readAllMemories()
1085
+ * starts a fresh disk scan and sees the just-written data. */
1086
+ invalidateAllMemoriesCache() {
1087
+ _StorageManager.allMemoriesInFlight.delete(this.baseDir);
1088
+ }
1089
+ normalizeMemoryReadBatchSize(batchSize) {
1090
+ if (typeof batchSize !== "number" || !Number.isFinite(batchSize)) {
1091
+ return 50;
1092
+ }
1093
+ return Math.max(1, Math.floor(batchSize));
1094
+ }
1095
+ async collectActiveMemoryPaths() {
1096
+ const filePaths = [];
1097
+ const collectPaths = async (dir) => {
1098
+ try {
1099
+ const entries = await readdir(dir, { withFileTypes: true });
1100
+ const subdirs = [];
1101
+ for (const entry of entries) {
1102
+ const fullPath = path.join(dir, entry.name);
1103
+ if (entry.isDirectory()) {
1104
+ subdirs.push(fullPath);
1105
+ } else if (entry.name.endsWith(".md")) {
1106
+ filePaths.push(fullPath);
1107
+ }
1108
+ }
1109
+ for (const subdir of subdirs) {
1110
+ await collectPaths(subdir);
1111
+ }
1112
+ } catch {
1113
+ }
1114
+ };
1115
+ await collectPaths(this.factsDir);
1116
+ await collectPaths(this.correctionsDir);
1117
+ return filePaths;
1118
+ }
1119
+ async readParsedMemoriesFromPaths(filePaths, batchSize) {
1120
+ if (filePaths.length === 0) return [];
1121
+ const normalizedBatchSize = this.normalizeMemoryReadBatchSize(batchSize);
1122
+ const memories = [];
1123
+ for (let i = 0; i < filePaths.length; i += normalizedBatchSize) {
1124
+ const batch = filePaths.slice(i, i + normalizedBatchSize);
1125
+ const results = await Promise.all(
1126
+ batch.map(async (fullPath) => {
1127
+ try {
1128
+ const raw = await readFile(fullPath, "utf-8");
1129
+ const parsed = parseFrontmatter(raw);
1130
+ if (!parsed) return null;
1131
+ return {
1132
+ path: fullPath,
1133
+ frontmatter: normalizeFrontmatterForPath(
1134
+ parsed.frontmatter,
1135
+ toMemoryPathRel(this.baseDir, fullPath)
1136
+ ),
1137
+ content: parsed.content
1138
+ };
1139
+ } catch {
1140
+ return null;
1141
+ }
1142
+ })
1143
+ );
1144
+ for (const memory of results) {
1145
+ if (memory !== null) memories.push(memory);
1146
+ }
1147
+ }
1148
+ return memories;
1149
+ }
1150
+ async readWindowUpdatedMs(filePath) {
1151
+ try {
1152
+ const raw = await readFile(filePath, "utf-8");
1153
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n?/);
1154
+ if (!match) return null;
1155
+ const frontmatterBlock = match[1];
1156
+ const rawUpdated = frontmatterBlock.match(/^updated:\s*"?([^"\n]*)"?/m)?.[1] ?? frontmatterBlock.match(/^created:\s*"?([^"\n]*)"?/m)?.[1] ?? null;
1157
+ const updatedMs = rawUpdated ? Date.parse(rawUpdated) : Number.NaN;
1158
+ return Number.isFinite(updatedMs) ? updatedMs : null;
1159
+ } catch {
1160
+ return null;
1161
+ }
1162
+ }
1163
+ async filterWindowPathsByUpdatedAfter(filePaths, updatedAfterMs) {
1164
+ const results = await Promise.all(filePaths.map(async (filePath) => {
1165
+ const updatedMs = await this.readWindowUpdatedMs(filePath);
1166
+ if (updatedMs !== null) {
1167
+ return updatedMs >= updatedAfterMs ? filePath : null;
1168
+ }
1169
+ try {
1170
+ const fileStat = await stat(filePath);
1171
+ return fileStat.mtimeMs >= updatedAfterMs ? filePath : null;
1172
+ } catch {
1173
+ return filePath;
1174
+ }
1175
+ }));
1176
+ return results.filter((filePath) => filePath !== null);
1177
+ }
1178
+ orderWindowPaths(filePaths) {
1179
+ const correctionPaths = [];
1180
+ const factPaths = [];
1181
+ for (const filePath of filePaths) {
1182
+ if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${path.sep}`)) {
1183
+ correctionPaths.push(filePath);
1184
+ } else {
1185
+ factPaths.push(filePath);
1186
+ }
1187
+ }
1188
+ correctionPaths.sort((left, right) => right.localeCompare(left));
1189
+ factPaths.sort((left, right) => right.localeCompare(left));
1190
+ if (correctionPaths.length === 0) return factPaths;
1191
+ if (factPaths.length === 0) return correctionPaths;
1192
+ const ordered = [];
1193
+ const maxLength = Math.max(correctionPaths.length, factPaths.length);
1194
+ for (let i = 0; i < maxLength; i += 1) {
1195
+ const correctionPath = correctionPaths[i];
1196
+ if (correctionPath) ordered.push(correctionPath);
1197
+ const factPath = factPaths[i];
1198
+ if (factPath) ordered.push(factPath);
1199
+ }
1200
+ return ordered;
1201
+ }
1202
+ async readWindowBoundedBatch(candidateBatchPaths, remainingSlots, remainingInspectionBudget, readBatchSize) {
1203
+ const memories = [];
1204
+ const filePaths = [];
1205
+ const normalizedReadBatchSize = this.normalizeMemoryReadBatchSize(readBatchSize);
1206
+ for (let index = 0; index < candidateBatchPaths.length; ) {
1207
+ if (memories.length >= remainingSlots || filePaths.length >= remainingInspectionBudget) break;
1208
+ const availableSlots = remainingSlots - memories.length;
1209
+ const availableInspectionBudget = remainingInspectionBudget - filePaths.length;
1210
+ const parallelWindow = availableSlots >= 4 && availableInspectionBudget >= 4 ? Math.min(normalizedReadBatchSize, 4) : 1;
1211
+ const candidatePaths = candidateBatchPaths.slice(
1212
+ index,
1213
+ index + Math.min(parallelWindow, availableInspectionBudget)
1214
+ );
1215
+ index += candidatePaths.length;
1216
+ if (candidatePaths.length === 0) break;
1217
+ filePaths.push(...candidatePaths);
1218
+ const parsedMemories = await this.readParsedMemoriesFromPaths(candidatePaths, candidatePaths.length);
1219
+ if (parsedMemories.length === 0) continue;
1220
+ memories.push(...parsedMemories.slice(0, availableSlots));
1221
+ }
1222
+ return { memories, filePaths };
1223
+ }
1224
+ async readMemoriesWindow(options = {}) {
1225
+ const allPaths = await this.collectActiveMemoryPaths();
1226
+ const sortedPaths = this.orderWindowPaths(allPaths);
1227
+ const maxMemories = typeof options.maxMemories === "number" && Number.isFinite(options.maxMemories) ? Math.max(1, Math.floor(options.maxMemories)) : void 0;
1228
+ const maxCandidatePaths = maxMemories === void 0 ? void 0 : maxMemories * 2;
1229
+ const updatedAfterMs = options.updatedAfter?.getTime();
1230
+ const normalizedBatchSize = this.normalizeMemoryReadBatchSize(options.batchSize);
1231
+ const memories = [];
1232
+ const selectedPaths = [];
1233
+ for (let i = 0; i < sortedPaths.length; i += normalizedBatchSize) {
1234
+ if (maxMemories !== void 0 && (memories.length >= maxMemories || maxCandidatePaths !== void 0 && selectedPaths.length >= maxCandidatePaths)) {
1235
+ return { memories, filePaths: selectedPaths };
1236
+ }
1237
+ const batchPaths = sortedPaths.slice(i, i + normalizedBatchSize);
1238
+ const candidateBatchPaths = updatedAfterMs === void 0 ? batchPaths : await this.filterWindowPathsByUpdatedAfter(batchPaths, updatedAfterMs);
1239
+ const remainingSlots = maxMemories === void 0 ? void 0 : Math.max(0, maxMemories - memories.length);
1240
+ const remainingInspectionBudget = maxCandidatePaths === void 0 ? void 0 : Math.max(0, maxCandidatePaths - selectedPaths.length);
1241
+ const { memories: batchMemories, filePaths: parsedCandidatePaths } = remainingSlots === void 0 ? {
1242
+ memories: await this.readParsedMemoriesFromPaths(candidateBatchPaths, normalizedBatchSize),
1243
+ filePaths: candidateBatchPaths
1244
+ } : await this.readWindowBoundedBatch(
1245
+ candidateBatchPaths,
1246
+ remainingSlots,
1247
+ remainingInspectionBudget ?? remainingSlots,
1248
+ normalizedBatchSize
1249
+ );
1250
+ selectedPaths.push(...parsedCandidatePaths);
1251
+ for (const memory of batchMemories) {
1252
+ memories.push(memory);
1253
+ if (maxMemories !== void 0 && memories.length >= maxMemories) {
1254
+ return { memories, filePaths: selectedPaths };
1255
+ }
1256
+ }
1257
+ }
1258
+ return { memories, filePaths: selectedPaths };
1259
+ }
1260
+ async _readAllMemoriesFromDisk() {
1261
+ const filePaths = await this.collectActiveMemoryPaths();
1262
+ return this.readParsedMemoriesFromPaths(filePaths, 50);
1263
+ }
1264
+ /**
1265
+ * Read archived memory markdown files under archive/.
1266
+ * Used by long-term recall fallback when hot recall has no hits.
1267
+ */
1268
+ async readArchivedMemories() {
1269
+ const memories = [];
1270
+ const root = this.archiveDir;
1271
+ const readDir = async (dir) => {
1272
+ try {
1273
+ const entries = await readdir(dir, { withFileTypes: true });
1274
+ for (const entry of entries) {
1275
+ const fullPath = path.join(dir, entry.name);
1276
+ if (entry.isDirectory()) {
1277
+ await readDir(fullPath);
1278
+ } else if (entry.name.endsWith(".md")) {
1279
+ try {
1280
+ const raw = await readFile(fullPath, "utf-8");
1281
+ const parsed = parseFrontmatter(raw);
1282
+ if (parsed) {
1283
+ memories.push({
1284
+ path: fullPath,
1285
+ frontmatter: normalizeFrontmatterForPath(
1286
+ parsed.frontmatter,
1287
+ toMemoryPathRel(this.baseDir, fullPath)
1288
+ ),
1289
+ content: parsed.content
1290
+ });
1291
+ }
1292
+ } catch {
1293
+ }
1294
+ }
1295
+ }
1296
+ } catch {
1297
+ }
1298
+ };
1299
+ await readDir(root);
1300
+ return memories;
1301
+ }
1302
+ /** Read a single memory file by its absolute path. Returns null if unreadable. */
1303
+ async readMemoryByPath(filePath) {
1304
+ try {
1305
+ const raw = await readFile(filePath, "utf-8");
1306
+ const parsed = parseFrontmatter(raw);
1307
+ if (parsed) {
1308
+ return {
1309
+ path: filePath,
1310
+ frontmatter: normalizeFrontmatterForPath(
1311
+ parsed.frontmatter,
1312
+ toMemoryPathRel(this.baseDir, filePath)
1313
+ ),
1314
+ content: parsed.content
1315
+ };
1316
+ }
1317
+ const normalizedPath = filePath.split(path.sep).join("/");
1318
+ if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
1319
+ const entity = parseEntityFile(raw);
1320
+ if (!entity.name) return null;
1321
+ const nameWithoutExt = path.basename(filePath, ".md");
1322
+ const fileMtime = entity.updated || await stat(filePath).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
1323
+ return {
1324
+ path: filePath,
1325
+ frontmatter: {
1326
+ id: nameWithoutExt,
1327
+ category: "entity",
1328
+ created: fileMtime,
1329
+ updated: fileMtime,
1330
+ source: "entity_extraction",
1331
+ confidence: 0.9,
1332
+ confidenceTier: confidenceTier(0.9),
1333
+ tags: entity.type ? [entity.type] : []
1334
+ },
1335
+ content: raw
1336
+ };
1337
+ }
1338
+ return null;
1339
+ } catch {
1340
+ return null;
1341
+ }
1342
+ }
1343
+ resolveTierRootDir(tier) {
1344
+ return tier === "cold" ? path.join(this.baseDir, "cold") : this.baseDir;
1345
+ }
1346
+ resolveMemoryDateDir(memory) {
1347
+ const preferred = memory.frontmatter.created || memory.frontmatter.updated;
1348
+ const dateToken = (preferred ?? "").slice(0, 10);
1349
+ return /^\d{4}-\d{2}-\d{2}$/.test(dateToken) ? dateToken : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1350
+ }
1351
+ isArtifactMemory(memory) {
1352
+ if (memory.frontmatter.source === "artifact") return true;
1353
+ if (memory.frontmatter.artifactType !== void 0) return true;
1354
+ return /[\\/]artifacts[\\/]/.test(memory.path);
1355
+ }
1356
+ buildTierMemoryPath(memory, tier) {
1357
+ const root = this.resolveTierRootDir(tier);
1358
+ if (this.isArtifactMemory(memory)) {
1359
+ return path.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
1360
+ }
1361
+ if (memory.frontmatter.category === "correction") {
1362
+ return path.join(root, "corrections", `${memory.frontmatter.id}.md`);
1363
+ }
1364
+ return path.join(root, "facts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
1365
+ }
1366
+ async writeMemoryFileAtomic(targetPath, memory) {
1367
+ const fileContent = `${serializeFrontmatter(memory.frontmatter)}
1368
+
1369
+ ${memory.content}
1370
+ `;
1371
+ await mkdir(path.dirname(targetPath), { recursive: true });
1372
+ const tempPath = `${targetPath}.tmp-${process.pid}-${Date.now()}`;
1373
+ try {
1374
+ await writeFile(tempPath, fileContent, "utf-8");
1375
+ await rename(tempPath, targetPath);
1376
+ this.invalidateAllMemoriesCache();
1377
+ } catch (err) {
1378
+ try {
1379
+ await unlink(tempPath);
1380
+ } catch {
1381
+ }
1382
+ throw err;
1383
+ }
1384
+ }
1385
+ async moveMemoryToPath(memory, targetPath) {
1386
+ await this.writeMemoryFileAtomic(targetPath, memory);
1387
+ const sourcePath = path.resolve(memory.path);
1388
+ const destPath = path.resolve(targetPath);
1389
+ if (sourcePath !== destPath) {
1390
+ try {
1391
+ await unlink(memory.path);
1392
+ } catch (err) {
1393
+ const message = err instanceof Error ? err.message : String(err);
1394
+ if (!message.includes("ENOENT")) {
1395
+ throw err;
1396
+ }
1397
+ }
1398
+ this.invalidateAllMemoriesCache();
1399
+ }
1400
+ }
1401
+ async migrateMemoryToTier(memory, targetTier) {
1402
+ const targetPath = this.buildTierMemoryPath(memory, targetTier);
1403
+ const sourcePath = path.resolve(memory.path);
1404
+ const destPath = path.resolve(targetPath);
1405
+ if (sourcePath === destPath) {
1406
+ return { changed: false, targetPath };
1407
+ }
1408
+ const existing = await this.readMemoryByPath(targetPath);
1409
+ if (existing?.frontmatter.id === memory.frontmatter.id) {
1410
+ try {
1411
+ await unlink(memory.path);
1412
+ } catch (err) {
1413
+ const message = err instanceof Error ? err.message : String(err);
1414
+ if (!message.includes("ENOENT")) {
1415
+ throw err;
1416
+ }
1417
+ }
1418
+ this.bumpMemoryStatusVersion();
1419
+ return { changed: false, targetPath };
1420
+ }
1421
+ await this.moveMemoryToPath(memory, targetPath);
1422
+ this.invalidateAllMemoriesCache();
1423
+ this.bumpMemoryStatusVersion();
1424
+ return { changed: true, targetPath };
1425
+ }
1426
+ get archiveDir() {
1427
+ return path.join(this.baseDir, "archive");
1428
+ }
1429
+ /**
1430
+ * Archive a memory by moving it from facts/ to archive/YYYY-MM-DD/.
1431
+ * Updates frontmatter with archived status before moving.
1432
+ * Returns the new file path on success, null on failure.
1433
+ */
1434
+ async archiveMemory(memory, lifecycle) {
1435
+ try {
1436
+ const now = lifecycle?.at ?? /* @__PURE__ */ new Date();
1437
+ const today = now.toISOString().slice(0, 10);
1438
+ const destDir = path.join(this.archiveDir, today);
1439
+ await mkdir(destDir, { recursive: true });
1440
+ const updatedFm = {
1441
+ ...memory.frontmatter,
1442
+ status: "archived",
1443
+ archivedAt: now.toISOString(),
1444
+ updated: now.toISOString()
1445
+ };
1446
+ const fileContent = `${serializeFrontmatter(updatedFm)}
1447
+
1448
+ ${memory.content}
1449
+ `;
1450
+ const destPath = path.join(destDir, path.basename(memory.path));
1451
+ await writeFile(destPath, fileContent, "utf-8");
1452
+ await unlink(memory.path);
1453
+ this.invalidateAllMemoriesCache();
1454
+ await this.appendGeneratedMemoryLifecycleEventFailOpen(
1455
+ "storage.archiveMemory",
1456
+ {
1457
+ memoryId: memory.frontmatter.id,
1458
+ eventType: "archived",
1459
+ timestamp: updatedFm.archivedAt ?? updatedFm.updated,
1460
+ actor: lifecycle?.actor ?? "storage.archiveMemory",
1461
+ reasonCode: lifecycle?.reasonCode,
1462
+ before: this.summarizeLifecycleState(memory.frontmatter, memory.path),
1463
+ after: this.summarizeLifecycleState(updatedFm, destPath),
1464
+ relatedMemoryIds: lifecycle?.relatedMemoryIds,
1465
+ correlationId: lifecycle?.correlationId
1466
+ },
1467
+ lifecycle?.ruleVersion
1468
+ );
1469
+ this.bumpMemoryStatusVersion();
1470
+ log.debug(`archived memory ${memory.frontmatter.id} \u2192 ${destPath}`);
1471
+ return destPath;
1472
+ } catch (err) {
1473
+ log.warn(`failed to archive memory ${memory.frontmatter.id}: ${err}`);
1474
+ return null;
1475
+ }
1476
+ }
1477
+ async readEntities() {
1478
+ try {
1479
+ const entries = await readdir(this.entitiesDir);
1480
+ return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", ""));
1481
+ } catch {
1482
+ return [];
1483
+ }
1484
+ }
1485
+ async readEntity(name) {
1486
+ try {
1487
+ return await readFile(path.join(this.entitiesDir, `${name}.md`), "utf-8");
1488
+ } catch {
1489
+ return "";
1490
+ }
1491
+ }
1492
+ /** Return sorted list of entity filenames (without .md extension) */
1493
+ async listEntityNames() {
1494
+ try {
1495
+ const entries = await readdir(this.entitiesDir);
1496
+ return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", "")).sort();
1497
+ } catch {
1498
+ return [];
1499
+ }
1500
+ }
1501
+ /**
1502
+ * Find an existing entity that fuzzy-matches the proposed name.
1503
+ * Returns the existing entity filename (without .md) or null if no match.
1504
+ *
1505
+ * Matching priority:
1506
+ * 1. Exact normalized match (handled by normalizeEntityName already)
1507
+ * 2. Dehyphenated match: "jane-doe" vs "janedoe"
1508
+ * 3. Substring containment: "handle-janedoe" contains "janedoe"
1509
+ * 4. Levenshtein ≤ 2 on dehyphenated names
1510
+ */
1511
+ async findMatchingEntity(proposedName, type) {
1512
+ const existing = await this.listEntityNames();
1513
+ if (existing.length === 0) return null;
1514
+ const typePrefix = `${type.toLowerCase()}-`;
1515
+ const proposedFull = normalizeEntityName(proposedName, type);
1516
+ const proposedNamePart = proposedFull.startsWith(typePrefix) ? proposedFull.slice(typePrefix.length) : proposedFull;
1517
+ const proposedDehyph = dehyphenate(proposedNamePart);
1518
+ const sameType = existing.filter((e) => e.startsWith(typePrefix));
1519
+ for (const entity of sameType) {
1520
+ const entityNamePart = entity.slice(typePrefix.length);
1521
+ const entityDehyph = dehyphenate(entityNamePart);
1522
+ if (entity === proposedFull) return entity;
1523
+ if (entityDehyph === proposedDehyph) return entity;
1524
+ const shorter = proposedDehyph.length <= entityDehyph.length ? proposedDehyph : entityDehyph;
1525
+ const longer = proposedDehyph.length > entityDehyph.length ? proposedDehyph : entityDehyph;
1526
+ if (shorter.length > 3 && shorter.length / longer.length > 0.6 && longer.includes(shorter)) {
1527
+ return entity;
1528
+ }
1529
+ if (proposedDehyph.length >= 4 && entityDehyph.length >= 4) {
1530
+ const dist = levenshtein(proposedDehyph, entityDehyph);
1531
+ if (dist <= 2) return entity;
1532
+ }
1533
+ }
1534
+ return null;
1535
+ }
1536
+ async invalidateMemory(id) {
1537
+ const memories = await this.readAllMemories();
1538
+ const memory = memories.find((m) => m.frontmatter.id === id);
1539
+ if (!memory) return false;
1540
+ try {
1541
+ await unlink(memory.path);
1542
+ this.invalidateAllMemoriesCache();
1543
+ this.bumpMemoryStatusVersion();
1544
+ log.debug(`invalidated memory ${id}`);
1545
+ return true;
1546
+ } catch {
1547
+ return false;
1548
+ }
1549
+ }
1550
+ async updateMemory(id, newContent, options) {
1551
+ const memories = await this.readAllMemories();
1552
+ const memory = memories.find((m) => m.frontmatter.id === id);
1553
+ if (!memory) return false;
1554
+ const mergedLineage = [
1555
+ ...memory.frontmatter.lineage ?? [],
1556
+ ...options?.lineage ?? []
1557
+ ].filter((v, i, a) => a.indexOf(v) === i);
1558
+ const updated = {
1559
+ ...memory.frontmatter,
1560
+ updated: (/* @__PURE__ */ new Date()).toISOString(),
1561
+ supersedes: options?.supersedes ?? memory.frontmatter.supersedes,
1562
+ lineage: mergedLineage.length > 0 ? mergedLineage : void 0
1563
+ };
1564
+ const sanitized = sanitizeMemoryContent(newContent);
1565
+ if (!sanitized.clean) {
1566
+ log.warn(`updated memory content sanitized for ${id}; violations=${sanitized.violations.join(", ")}`);
1567
+ }
1568
+ const fileContent = `${serializeFrontmatter(updated)}
1569
+
1570
+ ${sanitized.text}
1571
+ `;
1572
+ await writeFile(memory.path, fileContent, "utf-8");
1573
+ this.invalidateAllMemoriesCache();
1574
+ await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.updateMemory", {
1575
+ memoryId: id,
1576
+ eventType: "updated",
1577
+ timestamp: updated.updated,
1578
+ actor: options?.actor ?? "storage.updateMemory",
1579
+ before: this.summarizeLifecycleState(memory.frontmatter, memory.path),
1580
+ after: this.summarizeLifecycleState(updated, memory.path),
1581
+ relatedMemoryIds: [
1582
+ ...updated.supersedes ? [updated.supersedes] : [],
1583
+ ...(updated.lineage ?? []).filter(Boolean)
1584
+ ]
1585
+ });
1586
+ log.debug(`updated memory ${id}`);
1587
+ return true;
1588
+ }
1589
+ /**
1590
+ * Update frontmatter fields without changing memory content.
1591
+ * Returns false when the memory is not found.
1592
+ */
1593
+ async writeMemoryFrontmatter(memory, patch, lifecycle) {
1594
+ const beforeStatus = memory.frontmatter.status ?? "active";
1595
+ const updated = {
1596
+ ...memory.frontmatter,
1597
+ ...patch
1598
+ };
1599
+ const afterStatus = updated.status ?? "active";
1600
+ const fileContent = `${serializeFrontmatter(updated)}
1601
+
1602
+ ${memory.content}
1603
+ `;
1604
+ await writeFile(memory.path, fileContent, "utf-8");
1605
+ this.invalidateAllMemoriesCache();
1606
+ await this.appendGeneratedMemoryLifecycleEventFailOpen(
1607
+ "storage.writeMemoryFrontmatter",
1608
+ {
1609
+ memoryId: updated.id,
1610
+ eventType: this.frontmatterPatchEventType(memory.frontmatter, updated),
1611
+ timestamp: updated.updated ?? (/* @__PURE__ */ new Date()).toISOString(),
1612
+ actor: lifecycle?.actor ?? "storage.writeMemoryFrontmatter",
1613
+ reasonCode: lifecycle?.reasonCode,
1614
+ before: this.summarizeLifecycleState(memory.frontmatter, memory.path),
1615
+ after: this.summarizeLifecycleState(updated, memory.path),
1616
+ relatedMemoryIds: [
1617
+ ...lifecycle?.relatedMemoryIds ?? [],
1618
+ ...updated.supersededBy ? [updated.supersededBy] : [],
1619
+ ...updated.supersedes ? [updated.supersedes] : []
1620
+ ],
1621
+ correlationId: lifecycle?.correlationId
1622
+ },
1623
+ lifecycle?.ruleVersion
1624
+ );
1625
+ if (beforeStatus !== afterStatus) {
1626
+ this.bumpMemoryStatusVersion();
1627
+ }
1628
+ return true;
1629
+ }
1630
+ /**
1631
+ * Update frontmatter by memory ID.
1632
+ * Prefer writeMemoryFrontmatter(memory, patch) in batch loops to avoid full-corpus rescans.
1633
+ */
1634
+ async updateMemoryFrontmatter(id, patch) {
1635
+ const memories = await this.readAllMemories();
1636
+ const memory = memories.find((m) => m.frontmatter.id === id);
1637
+ if (!memory) return false;
1638
+ return this.writeMemoryFrontmatter(memory, patch);
1639
+ }
1640
+ /** Remove memories past their TTL expiresAt date */
1641
+ async cleanExpiredTTL() {
1642
+ const memories = await this.readAllMemories();
1643
+ const now = Date.now();
1644
+ const deleted = [];
1645
+ for (const m of memories) {
1646
+ if (!m.frontmatter.expiresAt) continue;
1647
+ const expiresAt = new Date(m.frontmatter.expiresAt).getTime();
1648
+ if (expiresAt < now) {
1649
+ try {
1650
+ await unlink(m.path);
1651
+ deleted.push(m);
1652
+ log.debug(`cleaned expired memory ${m.frontmatter.id} (TTL expired)`);
1653
+ } catch {
1654
+ }
1655
+ }
1656
+ }
1657
+ if (deleted.length > 0) {
1658
+ this.invalidateAllMemoriesCache();
1659
+ this.bumpMemoryStatusVersion();
1660
+ }
1661
+ return deleted;
1662
+ }
1663
+ async loadBuffer() {
1664
+ const bufferPath = path.join(this.stateDir, "buffer.json");
1665
+ try {
1666
+ const raw = await readFile(bufferPath, "utf-8");
1667
+ return JSON.parse(raw);
1668
+ } catch {
1669
+ return { turns: [], lastExtractionAt: null, extractionCount: 0 };
1670
+ }
1671
+ }
1672
+ async saveBuffer(state) {
1673
+ await this.ensureDirectories();
1674
+ const bufferPath = path.join(this.stateDir, "buffer.json");
1675
+ await writeFile(bufferPath, JSON.stringify(state, null, 2), "utf-8");
1676
+ }
1677
+ async loadMeta() {
1678
+ const metaPath = path.join(this.stateDir, "meta.json");
1679
+ try {
1680
+ const raw = await readFile(metaPath, "utf-8");
1681
+ return JSON.parse(raw);
1682
+ } catch {
1683
+ return {
1684
+ extractionCount: 0,
1685
+ lastExtractionAt: null,
1686
+ lastConsolidationAt: null,
1687
+ totalMemories: 0,
1688
+ totalEntities: 0
1689
+ };
1690
+ }
1691
+ }
1692
+ async saveMeta(state) {
1693
+ await this.ensureDirectories();
1694
+ const metaPath = path.join(this.stateDir, "meta.json");
1695
+ await writeFile(metaPath, JSON.stringify(state, null, 2), "utf-8");
1696
+ }
1697
+ async appendMemoryActionEvents(events) {
1698
+ if (events.length === 0) return 0;
1699
+ await this.ensureDirectories();
1700
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
1701
+ const payload = events.map((event) => {
1702
+ const normalized = {
1703
+ ...event,
1704
+ timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
1705
+ };
1706
+ return `${JSON.stringify(normalized)}
1707
+ `;
1708
+ }).join("");
1709
+ await appendFile(this.memoryActionsPath, payload, "utf-8");
1710
+ return events.length;
1711
+ }
1712
+ async appendMemoryLifecycleEvents(events) {
1713
+ if (events.length === 0) return 0;
1714
+ await this.ensureDirectories();
1715
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
1716
+ const payload = events.map((event) => {
1717
+ const normalized = {
1718
+ ...event,
1719
+ timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
1720
+ };
1721
+ return `${JSON.stringify(normalized)}
1722
+ `;
1723
+ }).join("");
1724
+ await appendFile(this.memoryLifecycleLedgerPath, payload, "utf-8");
1725
+ return events.length;
1726
+ }
1727
+ async appendBehaviorSignals(events) {
1728
+ if (events.length === 0) return 0;
1729
+ await this.ensureDirectories();
1730
+ let existingKeys = /* @__PURE__ */ new Set();
1731
+ try {
1732
+ const raw = await readFile(this.behaviorSignalsPath, "utf-8");
1733
+ const lines = raw.split("\n");
1734
+ for (const line of lines) {
1735
+ const row = line.trim();
1736
+ if (!row) continue;
1737
+ try {
1738
+ const parsed = JSON.parse(row);
1739
+ if (typeof parsed.memoryId === "string" && typeof parsed.signalHash === "string") {
1740
+ existingKeys.add(`${parsed.memoryId}:${parsed.signalHash}`);
1741
+ }
1742
+ } catch {
1743
+ }
1744
+ }
1745
+ } catch {
1746
+ existingKeys = /* @__PURE__ */ new Set();
1747
+ }
1748
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
1749
+ const deduped = [];
1750
+ for (const event of events) {
1751
+ const key = `${event.memoryId}:${event.signalHash}`;
1752
+ if (existingKeys.has(key)) continue;
1753
+ existingKeys.add(key);
1754
+ deduped.push({
1755
+ ...event,
1756
+ timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
1757
+ });
1758
+ }
1759
+ if (deduped.length === 0) return 0;
1760
+ const payload = deduped.map((event) => `${JSON.stringify(event)}
1761
+ `).join("");
1762
+ await appendFile(this.behaviorSignalsPath, payload, "utf-8");
1763
+ return deduped.length;
1764
+ }
1765
+ async appendReextractJobs(events) {
1766
+ if (events.length === 0) return 0;
1767
+ await this.ensureDirectories();
1768
+ const filePath = path.join(this.stateDir, "reextract-jobs.jsonl");
1769
+ const lines = events.map((event) => JSON.stringify(event)).join("\n") + "\n";
1770
+ try {
1771
+ await appendFile(filePath, lines, "utf-8");
1772
+ return events.length;
1773
+ } catch {
1774
+ return 0;
1775
+ }
1776
+ }
1777
+ async readReextractJobs(limit = 200) {
1778
+ const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(1e3, Math.floor(limit))) : 200;
1779
+ const filePath = path.join(this.stateDir, "reextract-jobs.jsonl");
1780
+ try {
1781
+ const raw = await readFile(filePath, "utf-8");
1782
+ const lines = raw.split("\n").filter((line) => line.trim().length > 0);
1783
+ const parsed = [];
1784
+ for (const line of lines) {
1785
+ try {
1786
+ const record = JSON.parse(line);
1787
+ if (typeof record.memoryId !== "string" || record.memoryId.length === 0 || typeof record.model !== "string" || record.model.length === 0 || typeof record.requestedAt !== "string" || record.requestedAt.length === 0 || record.source !== "cli-migrate") {
1788
+ continue;
1789
+ }
1790
+ parsed.push({
1791
+ memoryId: record.memoryId,
1792
+ model: record.model,
1793
+ requestedAt: record.requestedAt,
1794
+ source: "cli-migrate"
1795
+ });
1796
+ } catch {
1797
+ continue;
1798
+ }
1799
+ }
1800
+ return parsed.slice(-safeLimit);
1801
+ } catch {
1802
+ return [];
1803
+ }
1804
+ }
1805
+ async readBehaviorSignals(limit = 200) {
1806
+ const cappedLimit = Math.max(0, Math.floor(limit));
1807
+ if (cappedLimit === 0) return [];
1808
+ try {
1809
+ const raw = await readFile(this.behaviorSignalsPath, "utf-8");
1810
+ const out = [];
1811
+ const lines = raw.split("\n");
1812
+ for (let i = lines.length - 1; i >= 0 && out.length < cappedLimit; i -= 1) {
1813
+ const row = lines[i]?.trim();
1814
+ if (!row) continue;
1815
+ try {
1816
+ const parsed = JSON.parse(row);
1817
+ if (typeof parsed.timestamp === "string" && typeof parsed.namespace === "string" && typeof parsed.memoryId === "string" && typeof parsed.category === "string" && typeof parsed.signalType === "string" && typeof parsed.direction === "string" && typeof parsed.confidence === "number" && typeof parsed.signalHash === "string" && typeof parsed.source === "string") {
1818
+ out.push(parsed);
1819
+ }
1820
+ } catch {
1821
+ }
1822
+ }
1823
+ return out.reverse();
1824
+ } catch {
1825
+ return [];
1826
+ }
1827
+ }
1828
+ async readMemoryActionEvents(limit = 200) {
1829
+ const cappedLimit = Math.max(0, Math.floor(limit));
1830
+ if (cappedLimit === 0) return [];
1831
+ try {
1832
+ const raw = await readFile(this.memoryActionsPath, "utf-8");
1833
+ const out = [];
1834
+ const lines = raw.split("\n");
1835
+ for (let i = lines.length - 1; i >= 0 && out.length < cappedLimit; i -= 1) {
1836
+ const line = lines[i]?.trim();
1837
+ if (!line) continue;
1838
+ try {
1839
+ const parsed = JSON.parse(line);
1840
+ if (typeof parsed.timestamp === "string" && typeof parsed.action === "string" && typeof parsed.outcome === "string") {
1841
+ out.push(parsed);
1842
+ }
1843
+ } catch {
1844
+ }
1845
+ }
1846
+ return out.reverse();
1847
+ } catch {
1848
+ return [];
1849
+ }
1850
+ }
1851
+ async readAllMemoryLifecycleEvents() {
1852
+ try {
1853
+ const raw = await readFile(this.memoryLifecycleLedgerPath, "utf-8");
1854
+ const out = [];
1855
+ const lines = raw.split("\n");
1856
+ for (const line of lines) {
1857
+ const row = line.trim();
1858
+ if (!row) continue;
1859
+ try {
1860
+ const parsed = JSON.parse(row);
1861
+ if (typeof parsed.eventId === "string" && typeof parsed.memoryId === "string" && typeof parsed.eventType === "string" && typeof parsed.timestamp === "string" && typeof parsed.actor === "string" && typeof parsed.ruleVersion === "string") {
1862
+ out.push(parsed);
1863
+ }
1864
+ } catch {
1865
+ }
1866
+ }
1867
+ return sortMemoryLifecycleEvents(out);
1868
+ } catch {
1869
+ return [];
1870
+ }
1871
+ }
1872
+ async readMemoryLifecycleEvents(limit = 200) {
1873
+ const cappedLimit = Math.max(0, Math.floor(limit));
1874
+ if (cappedLimit === 0) return [];
1875
+ const events = await this.readAllMemoryLifecycleEvents();
1876
+ return events.slice(-cappedLimit);
1877
+ }
1878
+ async writeCompressionGuidelines(content) {
1879
+ await this.ensureDirectories();
1880
+ await writeFile(this.compressionGuidelinesPath, content, "utf-8");
1881
+ }
1882
+ async readCompressionGuidelines() {
1883
+ try {
1884
+ return await readFile(this.compressionGuidelinesPath, "utf-8");
1885
+ } catch {
1886
+ return null;
1887
+ }
1888
+ }
1889
+ async writeCompressionGuidelineDraft(content) {
1890
+ await this.ensureDirectories();
1891
+ await writeFile(this.compressionGuidelineDraftPath, content, "utf-8");
1892
+ }
1893
+ async readCompressionGuidelineDraft() {
1894
+ try {
1895
+ return await readFile(this.compressionGuidelineDraftPath, "utf-8");
1896
+ } catch {
1897
+ return null;
1898
+ }
1899
+ }
1900
+ async writeCompressionGuidelineOptimizerState(state) {
1901
+ await this.ensureDirectories();
1902
+ await writeFile(this.compressionGuidelineStatePath, `${JSON.stringify(state, null, 2)}
1903
+ `, "utf-8");
1904
+ }
1905
+ async writeCompressionGuidelineDraftState(state) {
1906
+ await this.ensureDirectories();
1907
+ await writeFile(this.compressionGuidelineDraftStatePath, `${JSON.stringify(state, null, 2)}
1908
+ `, "utf-8");
1909
+ }
1910
+ async readCompressionGuidelineOptimizerState() {
1911
+ return this.readCompressionGuidelineStateFile(this.compressionGuidelineStatePath);
1912
+ }
1913
+ async readCompressionGuidelineDraftState() {
1914
+ return this.readCompressionGuidelineStateFile(this.compressionGuidelineDraftStatePath);
1915
+ }
1916
+ async activateCompressionGuidelineDraft(options) {
1917
+ const [draftContent, draftState] = await Promise.all([
1918
+ this.readCompressionGuidelineDraft(),
1919
+ this.readCompressionGuidelineDraftState()
1920
+ ]);
1921
+ if (!draftContent || !draftState) return false;
1922
+ if (typeof options?.expectedContentHash === "string" && options.expectedContentHash.length > 0 && draftState.contentHash !== options.expectedContentHash) {
1923
+ return false;
1924
+ }
1925
+ if (typeof options?.expectedGuidelineVersion === "number" && Number.isFinite(options.expectedGuidelineVersion) && draftState.guidelineVersion !== options.expectedGuidelineVersion) {
1926
+ return false;
1927
+ }
1928
+ if (draftState.contentHash) {
1929
+ const contentHash = createHash("sha256").update(draftContent).digest("hex");
1930
+ if (contentHash !== draftState.contentHash) return false;
1931
+ }
1932
+ await this.writeCompressionGuidelines(draftContent);
1933
+ await this.writeCompressionGuidelineOptimizerState({
1934
+ ...draftState,
1935
+ activationState: "active"
1936
+ });
1937
+ await Promise.all([
1938
+ unlink(this.compressionGuidelineDraftPath).catch(() => void 0),
1939
+ unlink(this.compressionGuidelineDraftStatePath).catch(() => void 0)
1940
+ ]);
1941
+ return true;
1942
+ }
1943
+ async readCompressionGuidelineStateFile(filePath) {
1944
+ const isFiniteNonNegativeInteger = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
1945
+ const isValidActionSummary = (value) => {
1946
+ if (!value || typeof value !== "object") return false;
1947
+ const summary = value;
1948
+ return typeof summary.action === "string" && isFiniteNonNegativeInteger(summary.total) && summary.outcomes !== null && typeof summary.outcomes === "object" && isFiniteNonNegativeInteger(summary.outcomes.applied) && isFiniteNonNegativeInteger(summary.outcomes.skipped) && isFiniteNonNegativeInteger(summary.outcomes.failed) && summary.quality !== null && typeof summary.quality === "object" && isFiniteNonNegativeInteger(summary.quality.good) && isFiniteNonNegativeInteger(summary.quality.poor) && isFiniteNonNegativeInteger(summary.quality.unknown);
1949
+ };
1950
+ const isValidRuleUpdate = (value) => {
1951
+ if (!value || typeof value !== "object") return false;
1952
+ const rule = value;
1953
+ return typeof rule.action === "string" && typeof rule.delta === "number" && Number.isFinite(rule.delta) && (rule.direction === "increase" || rule.direction === "decrease" || rule.direction === "hold") && (rule.confidence === "low" || rule.confidence === "medium" || rule.confidence === "high") && Array.isArray(rule.notes) && rule.notes.every((note) => typeof note === "string");
1954
+ };
1955
+ try {
1956
+ const raw = await readFile(filePath, "utf-8");
1957
+ const parsed = JSON.parse(raw);
1958
+ const sourceWindow = parsed?.sourceWindow;
1959
+ const eventCounts = parsed?.eventCounts;
1960
+ const activationState = parsed?.activationState === "draft" || parsed?.activationState === "active" ? parsed.activationState : void 0;
1961
+ const contentHash = typeof parsed?.contentHash === "string" && parsed.contentHash.length > 0 ? parsed.contentHash : void 0;
1962
+ const actionSummaries = Array.isArray(parsed?.actionSummaries) ? parsed.actionSummaries.filter(isValidActionSummary) : void 0;
1963
+ const ruleUpdates = Array.isArray(parsed?.ruleUpdates) ? parsed.ruleUpdates.filter(isValidRuleUpdate) : void 0;
1964
+ if (!isFiniteNonNegativeInteger(parsed?.version) || typeof parsed?.updatedAt !== "string" || parsed.updatedAt.length === 0 || !sourceWindow || typeof sourceWindow.from !== "string" || sourceWindow.from.length === 0 || typeof sourceWindow.to !== "string" || sourceWindow.to.length === 0 || !eventCounts || !isFiniteNonNegativeInteger(eventCounts.total) || !isFiniteNonNegativeInteger(eventCounts.applied) || !isFiniteNonNegativeInteger(eventCounts.skipped) || !isFiniteNonNegativeInteger(eventCounts.failed) || !isFiniteNonNegativeInteger(parsed?.guidelineVersion)) {
1965
+ return null;
1966
+ }
1967
+ return {
1968
+ version: parsed.version,
1969
+ updatedAt: parsed.updatedAt,
1970
+ sourceWindow: {
1971
+ from: sourceWindow.from,
1972
+ to: sourceWindow.to
1973
+ },
1974
+ eventCounts: {
1975
+ total: eventCounts.total,
1976
+ applied: eventCounts.applied,
1977
+ skipped: eventCounts.skipped,
1978
+ failed: eventCounts.failed
1979
+ },
1980
+ guidelineVersion: parsed.guidelineVersion,
1981
+ ...contentHash ? { contentHash } : {},
1982
+ ...activationState ? { activationState } : {},
1983
+ ...actionSummaries ? { actionSummaries } : {},
1984
+ ...ruleUpdates ? { ruleUpdates } : {}
1985
+ };
1986
+ } catch {
1987
+ return null;
1988
+ }
1989
+ }
1990
+ async writeIdentityAnchor(content) {
1991
+ await this.ensureDirectories();
1992
+ await writeFile(this.identityAnchorPath, content, "utf-8");
1993
+ }
1994
+ async readIdentityAnchor() {
1995
+ try {
1996
+ return await readFile(this.identityAnchorPath, "utf-8");
1997
+ } catch {
1998
+ return null;
1999
+ }
2000
+ }
2001
+ async appendContinuityIncident(input) {
2002
+ await this.ensureDirectories();
2003
+ const now = /* @__PURE__ */ new Date();
2004
+ const nowIso = now.toISOString();
2005
+ const date = nowIso.slice(0, 10);
2006
+ const id = this.generateId("incident");
2007
+ const incident = createContinuityIncidentRecord(id, input, nowIso);
2008
+ const filePath = path.join(this.identityIncidentsDir, `${date}-${id}.md`);
2009
+ await writeFile(filePath, serializeContinuityIncident(incident), "utf-8");
2010
+ return { ...incident, filePath };
2011
+ }
2012
+ async readContinuityIncidents(limit = 200, state = "all") {
2013
+ const normalizedLimit = Number.isFinite(limit) ? Math.floor(limit) : 0;
2014
+ const cappedLimit = Math.max(0, normalizedLimit);
2015
+ if (cappedLimit === 0) return [];
2016
+ try {
2017
+ const candidates = await this.readContinuityIncidentFileNames();
2018
+ const incidents = [];
2019
+ for (const file of candidates) {
2020
+ if (incidents.length >= cappedLimit) break;
2021
+ const filePath = path.join(this.identityIncidentsDir, file);
2022
+ try {
2023
+ const raw = await readFile(filePath, "utf-8");
2024
+ const parsed = parseContinuityIncident(raw);
2025
+ if (!parsed) continue;
2026
+ if (state !== "all" && parsed.state !== state) continue;
2027
+ incidents.push({ ...parsed, filePath });
2028
+ } catch {
2029
+ }
2030
+ }
2031
+ return incidents;
2032
+ } catch {
2033
+ return [];
2034
+ }
2035
+ }
2036
+ async closeContinuityIncident(id, closure) {
2037
+ const directFilePath = await this.findContinuityIncidentFilePathById(id);
2038
+ const target = directFilePath ? await this.readContinuityIncidentFile(directFilePath) : null;
2039
+ if (!target || !directFilePath) return null;
2040
+ if (target.state === "closed") return target;
2041
+ const closed = closeContinuityIncidentRecord(target, closure, (/* @__PURE__ */ new Date()).toISOString());
2042
+ await writeFile(directFilePath, serializeContinuityIncident(closed), "utf-8");
2043
+ return { ...closed, filePath: directFilePath };
2044
+ }
2045
+ async writeIdentityAudit(period, key, content) {
2046
+ await this.ensureDirectories();
2047
+ const safeKey = this.sanitizeIdentityAuditKey(key);
2048
+ const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
2049
+ const filePath = path.join(dir, `${safeKey}.md`);
2050
+ await writeFile(filePath, content, "utf-8");
2051
+ return filePath;
2052
+ }
2053
+ async readIdentityAudit(period, key) {
2054
+ try {
2055
+ const safeKey = this.sanitizeIdentityAuditKey(key);
2056
+ const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
2057
+ return await readFile(path.join(dir, `${safeKey}.md`), "utf-8");
2058
+ } catch {
2059
+ return null;
2060
+ }
2061
+ }
2062
+ async writeIdentityImprovementLoops(content) {
2063
+ await this.ensureDirectories();
2064
+ await writeFile(this.identityImprovementLoopsPath, content, "utf-8");
2065
+ }
2066
+ async readIdentityImprovementLoops() {
2067
+ try {
2068
+ return await readFile(this.identityImprovementLoopsPath, "utf-8");
2069
+ } catch {
2070
+ return null;
2071
+ }
2072
+ }
2073
+ async readIdentityImprovementLoopRegister() {
2074
+ const raw = await this.readIdentityImprovementLoops();
2075
+ if (!raw) return [];
2076
+ return parseContinuityImprovementLoops(raw);
2077
+ }
2078
+ async upsertIdentityImprovementLoop(input) {
2079
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
2080
+ const raw = await this.readIdentityImprovementLoops();
2081
+ const { markdown, loop } = upsertContinuityLoopInMarkdown(raw, input, nowIso);
2082
+ await this.writeIdentityImprovementLoops(markdown);
2083
+ return loop;
2084
+ }
2085
+ async reviewIdentityImprovementLoop(id, input) {
2086
+ const raw = await this.readIdentityImprovementLoops();
2087
+ const { markdown, loop } = reviewContinuityLoopInMarkdown(raw, id, input, (/* @__PURE__ */ new Date()).toISOString());
2088
+ if (!loop) return null;
2089
+ await this.writeIdentityImprovementLoops(markdown);
2090
+ return loop;
2091
+ }
2092
+ // ---------------------------------------------------------------------------
2093
+ // Question storage
2094
+ // ---------------------------------------------------------------------------
2095
+ generateId(prefix = "m") {
2096
+ const ts = Date.now().toString(36);
2097
+ const rand = Math.random().toString(36).slice(2, 4);
2098
+ return `${prefix}-${ts}-${rand}`;
2099
+ }
2100
+ async readContinuityIncidentFileNames() {
2101
+ const files = await readdir(this.identityIncidentsDir);
2102
+ return files.filter((file) => file.endsWith(".md")).sort().reverse();
2103
+ }
2104
+ async readContinuityIncidentFile(filePath) {
2105
+ try {
2106
+ const raw = await readFile(filePath, "utf-8");
2107
+ const parsed = parseContinuityIncident(raw);
2108
+ return parsed ? { ...parsed, filePath } : null;
2109
+ } catch {
2110
+ return null;
2111
+ }
2112
+ }
2113
+ async findContinuityIncidentFilePathById(id) {
2114
+ const fileNames = await this.readContinuityIncidentFileNames();
2115
+ const directMatch = fileNames.find((name) => name.endsWith(`-${id}.md`));
2116
+ if (directMatch) {
2117
+ const directPath = path.join(this.identityIncidentsDir, directMatch);
2118
+ const parsed = await this.readContinuityIncidentFile(directPath);
2119
+ if (parsed?.id === id) return directPath;
2120
+ }
2121
+ for (const fileName of fileNames) {
2122
+ const filePath = path.join(this.identityIncidentsDir, fileName);
2123
+ const parsed = await this.readContinuityIncidentFile(filePath);
2124
+ if (parsed?.id === id) return filePath;
2125
+ }
2126
+ return null;
2127
+ }
2128
+ sanitizeIdentityAuditKey(key) {
2129
+ const trimmed = key.trim();
2130
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(trimmed) || trimmed.includes("..")) {
2131
+ throw new Error("Invalid identity audit key");
2132
+ }
2133
+ return trimmed;
2134
+ }
2135
+ async writeQuestion(question, context, priority) {
2136
+ await mkdir(this.questionsDir, { recursive: true });
2137
+ const id = this.generateId("q");
2138
+ const frontmatter = {
2139
+ id,
2140
+ created: (/* @__PURE__ */ new Date()).toISOString(),
2141
+ priority,
2142
+ resolved: false
2143
+ };
2144
+ const content = `---
2145
+ ${Object.entries(frontmatter).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join("\n")}
2146
+ ---
2147
+
2148
+ ${question}
2149
+
2150
+ **Context:** ${context}
2151
+ `;
2152
+ const filePath = path.join(this.questionsDir, `${id}.md`);
2153
+ await writeFile(filePath, content, "utf-8");
2154
+ log.debug(`wrote question ${id} to ${filePath}`);
2155
+ this.invalidateQuestionsCache();
2156
+ return id;
2157
+ }
2158
+ async readQuestions(opts) {
2159
+ const cacheKey = this.questionsDir;
2160
+ const cached = _StorageManager.questionsCache.get(cacheKey);
2161
+ if (cached && Date.now() - cached.loadedAt < _StorageManager.QUESTIONS_CACHE_TTL_MS) {
2162
+ try {
2163
+ const dirStat = await stat(this.questionsDir);
2164
+ if (dirStat.mtimeMs <= cached.loadedAt) {
2165
+ const all = cached.questions;
2166
+ return opts?.unresolvedOnly ? all.filter((q) => !q.resolved) : all;
2167
+ }
2168
+ } catch {
2169
+ }
2170
+ }
2171
+ try {
2172
+ const files = await readdir(this.questionsDir);
2173
+ const questions = [];
2174
+ for (const file of files) {
2175
+ if (!file.endsWith(".md")) continue;
2176
+ const filePath = path.join(this.questionsDir, file);
2177
+ const raw = await readFile(filePath, "utf-8");
2178
+ const parsed = this.parseQuestionFile(raw, filePath);
2179
+ if (parsed) {
2180
+ questions.push(parsed);
2181
+ }
2182
+ }
2183
+ const sorted = questions.sort((a, b) => b.priority - a.priority);
2184
+ _StorageManager.questionsCache.set(cacheKey, { questions: sorted, loadedAt: Date.now() });
2185
+ return opts?.unresolvedOnly ? sorted.filter((q) => !q.resolved) : sorted;
2186
+ } catch {
2187
+ return [];
2188
+ }
2189
+ }
2190
+ /** Invalidate the questions cache (call after writing a question). */
2191
+ invalidateQuestionsCache() {
2192
+ _StorageManager.questionsCache.delete(this.questionsDir);
2193
+ }
2194
+ parseQuestionFile(raw, filePath) {
2195
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n\n([\s\S]*)$/);
2196
+ if (!match) return null;
2197
+ const frontmatterStr = match[1];
2198
+ const body = match[2].trim();
2199
+ const id = this.extractFrontmatterValue(frontmatterStr, "id") ?? path.basename(filePath, ".md");
2200
+ const created = this.extractFrontmatterValue(frontmatterStr, "created") ?? "";
2201
+ const priority = parseFloat(
2202
+ this.extractFrontmatterValue(frontmatterStr, "priority") ?? "0.5"
2203
+ );
2204
+ const resolved = this.extractFrontmatterValue(frontmatterStr, "resolved") === "true";
2205
+ const contextMatch = body.match(/\*\*Context:\*\*\s*(.*)/);
2206
+ const question = contextMatch ? body.slice(0, contextMatch.index).trim() : body;
2207
+ const context = contextMatch ? contextMatch[1].trim() : "";
2208
+ return { id, question, context, priority, resolved, created, filePath };
2209
+ }
2210
+ extractFrontmatterValue(frontmatter, key) {
2211
+ const match = frontmatter.match(
2212
+ new RegExp(`^${key}:\\s*"?([^"\\n]*)"?`, "m")
2213
+ );
2214
+ return match ? match[1] : null;
2215
+ }
2216
+ async resolveQuestion(id) {
2217
+ const questions = await this.readQuestions();
2218
+ const q = questions.find((q2) => q2.id === id);
2219
+ if (!q) return false;
2220
+ let raw = await readFile(q.filePath, "utf-8");
2221
+ raw = raw.replace(/resolved: false/, "resolved: true");
2222
+ raw = raw.replace(
2223
+ /---\n\n/,
2224
+ `resolvedAt: "${(/* @__PURE__ */ new Date()).toISOString()}"
2225
+ ---
2226
+
2227
+ `
2228
+ );
2229
+ await writeFile(q.filePath, raw, "utf-8");
2230
+ log.debug(`resolved question ${id}`);
2231
+ return true;
2232
+ }
2233
+ // ---------------------------------------------------------------------------
2234
+ // Identity file
2235
+ // ---------------------------------------------------------------------------
2236
+ async readIdentity(workspaceDir, namespace) {
2237
+ const identityPath = this.identityFilePath(workspaceDir, namespace);
2238
+ try {
2239
+ return await readFile(identityPath, "utf-8");
2240
+ } catch {
2241
+ return "";
2242
+ }
2243
+ }
2244
+ async writeIdentity(workspaceDir, content, namespace) {
2245
+ const identityPath = this.identityFilePath(workspaceDir, namespace);
2246
+ await writeFile(identityPath, content, "utf-8");
2247
+ log.debug(`wrote consolidated IDENTITY.md (${content.length} chars)`);
2248
+ }
2249
+ /** Max size for IDENTITY.md before we stop appending reflections (15KB leaves room under 20KB gateway limit) */
2250
+ static IDENTITY_MAX_BYTES = 15e3;
2251
+ /** Minimum interval between reflections (1 hour) */
2252
+ static REFLECTION_COOLDOWN_MS = 60 * 60 * 1e3;
2253
+ async appendToIdentity(workspaceDir, reflection, opts) {
2254
+ const identityPath = this.identityFilePath(workspaceDir, opts?.namespace);
2255
+ let existing = "";
2256
+ try {
2257
+ existing = await readFile(identityPath, "utf-8");
2258
+ } catch {
2259
+ }
2260
+ const hygiene = opts?.hygiene;
2261
+ const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(path.basename(identityPath));
2262
+ if (rotateEnabled) {
2263
+ const maxBytes = hygiene.rotateMaxBytes;
2264
+ if (existing.length > maxBytes) {
2265
+ const archiveDir = path.join(workspaceDir, hygiene.archiveDir);
2266
+ const { newContent } = await rotateMarkdownFileToArchive({
2267
+ filePath: identityPath,
2268
+ archiveDir,
2269
+ archivePrefix: "IDENTITY",
2270
+ keepTailChars: hygiene.rotateKeepTailChars
2271
+ });
2272
+ await writeFile(identityPath, newContent, "utf-8");
2273
+ existing = newContent;
2274
+ log.info(
2275
+ `rotated IDENTITY.md to archive (size=${existing.length} chars, maxBytes=${maxBytes})`
2276
+ );
2277
+ }
2278
+ } else {
2279
+ if (existing.length > _StorageManager.IDENTITY_MAX_BYTES) {
2280
+ log.debug(`IDENTITY.md is ${existing.length} chars (limit ${_StorageManager.IDENTITY_MAX_BYTES}); skipping reflection`);
2281
+ return;
2282
+ }
2283
+ }
2284
+ const lastMatch = existing.match(/## Reflection — (\S+)\s*$/m);
2285
+ if (lastMatch) {
2286
+ const allMatches = [...existing.matchAll(/## Reflection — (\S+)/g)];
2287
+ if (allMatches.length > 0) {
2288
+ const lastTimestamp = allMatches[allMatches.length - 1][1];
2289
+ const elapsed = Date.now() - new Date(lastTimestamp).getTime();
2290
+ if (elapsed < _StorageManager.REFLECTION_COOLDOWN_MS) {
2291
+ log.debug(`reflection cooldown: ${Math.round(elapsed / 1e3)}s since last (need ${_StorageManager.REFLECTION_COOLDOWN_MS / 1e3}s)`);
2292
+ return;
2293
+ }
2294
+ }
2295
+ }
2296
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2297
+ const section = `
2298
+
2299
+ ## Reflection \u2014 ${timestamp}
2300
+
2301
+ ${reflection}
2302
+ `;
2303
+ await writeFile(identityPath, existing + section, "utf-8");
2304
+ log.debug(`appended reflection to ${identityPath}`);
2305
+ }
2306
+ async readIdentityReflections() {
2307
+ try {
2308
+ return await readFile(this.identityReflectionsPath, "utf-8");
2309
+ } catch {
2310
+ return null;
2311
+ }
2312
+ }
2313
+ async writeIdentityReflections(content) {
2314
+ await mkdir(this.identityDir, { recursive: true });
2315
+ await writeFile(this.identityReflectionsPath, content, "utf-8");
2316
+ }
2317
+ async appendIdentityReflection(reflection) {
2318
+ let existing = "";
2319
+ try {
2320
+ existing = await readFile(this.identityReflectionsPath, "utf-8");
2321
+ } catch {
2322
+ }
2323
+ if (existing.length > _StorageManager.IDENTITY_MAX_BYTES) {
2324
+ log.debug(
2325
+ `identity/reflections.md is ${existing.length} chars (limit ${_StorageManager.IDENTITY_MAX_BYTES}); skipping reflection`
2326
+ );
2327
+ return;
2328
+ }
2329
+ const allMatches = [...existing.matchAll(/## Reflection — (\S+)/g)];
2330
+ if (allMatches.length > 0) {
2331
+ const lastTimestamp = allMatches[allMatches.length - 1][1];
2332
+ const elapsed = Date.now() - new Date(lastTimestamp).getTime();
2333
+ if (elapsed < _StorageManager.REFLECTION_COOLDOWN_MS) {
2334
+ log.debug(
2335
+ `reflection cooldown: ${Math.round(elapsed / 1e3)}s since last (need ${_StorageManager.REFLECTION_COOLDOWN_MS / 1e3}s)`
2336
+ );
2337
+ return;
2338
+ }
2339
+ }
2340
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2341
+ const section = `${existing.trimEnd().length > 0 ? "\n\n" : ""}## Reflection \u2014 ${timestamp}
2342
+
2343
+ ${reflection}
2344
+ `;
2345
+ await mkdir(this.identityDir, { recursive: true });
2346
+ await writeFile(this.identityReflectionsPath, `${existing.trimEnd()}${section}`, "utf-8");
2347
+ log.debug(`appended namespace-local reflection to ${this.identityReflectionsPath}`);
2348
+ }
2349
+ // ---------------------------------------------------------------------------
2350
+ // Entity mutation helpers (Knowledge Graph v7.0)
2351
+ // ---------------------------------------------------------------------------
2352
+ /**
2353
+ * Add a relationship to an entity file.
2354
+ * Deduplicates by target+label.
2355
+ */
2356
+ async addEntityRelationship(name, rel) {
2357
+ const filePath = path.join(this.entitiesDir, `${name}.md`);
2358
+ let entity;
2359
+ try {
2360
+ const content = await readFile(filePath, "utf-8");
2361
+ entity = parseEntityFile(content);
2362
+ } catch {
2363
+ log.debug(`addEntityRelationship: entity file ${name}.md not found`);
2364
+ return;
2365
+ }
2366
+ const exists = entity.relationships.some(
2367
+ (r) => r.target === rel.target && r.label === rel.label
2368
+ );
2369
+ if (exists) return;
2370
+ entity.relationships.push(rel);
2371
+ entity.updated = (/* @__PURE__ */ new Date()).toISOString();
2372
+ await writeFile(filePath, serializeEntityFile(entity), "utf-8");
2373
+ this.invalidateKnowledgeIndexCache();
2374
+ }
2375
+ /**
2376
+ * Add an activity entry to an entity file.
2377
+ * Prepends to the beginning, prunes oldest entries beyond maxEntries.
2378
+ */
2379
+ async addEntityActivity(name, entry, maxEntries) {
2380
+ const filePath = path.join(this.entitiesDir, `${name}.md`);
2381
+ let entity;
2382
+ try {
2383
+ const content = await readFile(filePath, "utf-8");
2384
+ entity = parseEntityFile(content);
2385
+ } catch {
2386
+ log.debug(`addEntityActivity: entity file ${name}.md not found`);
2387
+ return;
2388
+ }
2389
+ entity.activity.unshift(entry);
2390
+ if (entity.activity.length > maxEntries) {
2391
+ entity.activity = entity.activity.slice(0, maxEntries);
2392
+ }
2393
+ entity.updated = (/* @__PURE__ */ new Date()).toISOString();
2394
+ await writeFile(filePath, serializeEntityFile(entity), "utf-8");
2395
+ this.invalidateKnowledgeIndexCache();
2396
+ }
2397
+ /**
2398
+ * Add an alias to an entity file. Deduplicates.
2399
+ */
2400
+ async addEntityAlias(name, alias) {
2401
+ const filePath = path.join(this.entitiesDir, `${name}.md`);
2402
+ let entity;
2403
+ try {
2404
+ const content = await readFile(filePath, "utf-8");
2405
+ entity = parseEntityFile(content);
2406
+ } catch {
2407
+ log.debug(`addEntityAlias: entity file ${name}.md not found`);
2408
+ return;
2409
+ }
2410
+ if (entity.aliases.includes(alias)) return;
2411
+ entity.aliases.push(alias);
2412
+ entity.updated = (/* @__PURE__ */ new Date()).toISOString();
2413
+ await writeFile(filePath, serializeEntityFile(entity), "utf-8");
2414
+ this.invalidateKnowledgeIndexCache();
2415
+ }
2416
+ /**
2417
+ * Set or update the summary of an entity file.
2418
+ */
2419
+ async updateEntitySummary(name, summary) {
2420
+ const filePath = path.join(this.entitiesDir, `${name}.md`);
2421
+ let entity;
2422
+ try {
2423
+ const content = await readFile(filePath, "utf-8");
2424
+ entity = parseEntityFile(content);
2425
+ } catch {
2426
+ log.debug(`updateEntitySummary: entity file ${name}.md not found`);
2427
+ return;
2428
+ }
2429
+ entity.summary = summary;
2430
+ entity.updated = (/* @__PURE__ */ new Date()).toISOString();
2431
+ await writeFile(filePath, serializeEntityFile(entity), "utf-8");
2432
+ this.invalidateKnowledgeIndexCache();
2433
+ this.bumpMemoryStatusVersion();
2434
+ }
2435
+ // ---------------------------------------------------------------------------
2436
+ // Scoring + Knowledge Index (Knowledge Graph v7.0)
2437
+ // ---------------------------------------------------------------------------
2438
+ /**
2439
+ * Read all entity files and return lightweight EntityFile objects.
2440
+ * Parsing is fast (~50-100ms for ~1,800 files) since entity files are small.
2441
+ */
2442
+ async readAllEntityFiles() {
2443
+ const currentVersion = this.getMemoryStatusVersion();
2444
+ const cached = getCachedEntities(this.baseDir, currentVersion);
2445
+ if (cached) return cached;
2446
+ try {
2447
+ const entries = await readdir(this.entitiesDir);
2448
+ const mdFiles = entries.filter((e) => e.endsWith(".md"));
2449
+ if (mdFiles.length === 0) return [];
2450
+ const BATCH_SIZE = 100;
2451
+ const entities = [];
2452
+ for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {
2453
+ const batch = mdFiles.slice(i, i + BATCH_SIZE);
2454
+ const results = await Promise.all(
2455
+ batch.map(
2456
+ (entry) => readFile(path.join(this.entitiesDir, entry), "utf-8").catch(() => null)
2457
+ )
2458
+ );
2459
+ for (const content of results) {
2460
+ if (content !== null) entities.push(parseEntityFile(content));
2461
+ }
2462
+ }
2463
+ setCachedEntities(this.baseDir, entities, currentVersion);
2464
+ return entities;
2465
+ } catch {
2466
+ return [];
2467
+ }
2468
+ }
2469
+ /**
2470
+ * Score an entity based on recency, frequency, activity, type priority,
2471
+ * and relationship density.
2472
+ *
2473
+ * score = recency*0.40 + frequency*0.25 + activity*0.15 + typePriority*0.10 + relationshipDensity*0.10
2474
+ */
2475
+ static scoreEntity(entity, now) {
2476
+ const updated = entity.updated ? new Date(entity.updated).getTime() : 0;
2477
+ const daysSince = Math.max(0, (now.getTime() - updated) / (1e3 * 60 * 60 * 24));
2478
+ const recency = 1 / (1 + daysSince / 7);
2479
+ const frequency = Math.min(entity.facts.length / 20, 1);
2480
+ const activityScore = Math.min(entity.activity.length / 10, 1);
2481
+ const TYPE_PRIORITY = {
2482
+ person: 1,
2483
+ project: 0.8,
2484
+ company: 0.7,
2485
+ tool: 0.6,
2486
+ place: 0.5,
2487
+ other: 0.3
2488
+ };
2489
+ const typePriority = TYPE_PRIORITY[entity.type.toLowerCase()] ?? 0.3;
2490
+ const relDensity = Math.min(entity.relationships.length / 8, 1);
2491
+ return recency * 0.4 + frequency * 0.25 + activityScore * 0.15 + typePriority * 0.1 + relDensity * 0.1;
2492
+ }
2493
+ /**
2494
+ * Build the Knowledge Index: a compact markdown table of top-scored entities.
2495
+ * Respects maxEntities and maxChars limits from config.
2496
+ */
2497
+ async buildKnowledgeIndex(config, overrides) {
2498
+ const useDefaultLimits = overrides?.maxEntities === void 0 && overrides?.maxChars === void 0;
2499
+ if (useDefaultLimits && this.knowledgeIndexCache && Date.now() - this.knowledgeIndexCache.builtAt < _StorageManager.KNOWLEDGE_INDEX_CACHE_TTL_MS) {
2500
+ return { result: this.knowledgeIndexCache.result, cached: true };
2501
+ }
2502
+ const entities = await this.readAllEntityFiles();
2503
+ if (entities.length === 0) {
2504
+ if (useDefaultLimits) this.knowledgeIndexCache = { result: "", builtAt: Date.now() };
2505
+ return { result: "", cached: false };
2506
+ }
2507
+ const now = /* @__PURE__ */ new Date();
2508
+ const scored = entities.map((e) => ({
2509
+ name: e.name,
2510
+ type: e.type,
2511
+ score: _StorageManager.scoreEntity(e, now),
2512
+ factCount: e.facts.length,
2513
+ summary: e.summary,
2514
+ topRelationships: e.relationships.slice(0, 3).map((r) => r.target)
2515
+ }));
2516
+ scored.sort((a, b) => b.score - a.score);
2517
+ const maxEntities = typeof overrides?.maxEntities === "number" ? Math.max(0, Math.floor(overrides.maxEntities)) : config.knowledgeIndexMaxEntities;
2518
+ const topN = scored.slice(0, maxEntities);
2519
+ if (topN.length === 0) {
2520
+ if (useDefaultLimits) this.knowledgeIndexCache = { result: "", builtAt: Date.now() };
2521
+ return { result: "", cached: false };
2522
+ }
2523
+ const header = "## Knowledge Index\n\n| Entity | Type | Summary | Connected to |\n|--------|------|---------|-------------|";
2524
+ const rows = [];
2525
+ let totalChars = header.length;
2526
+ const maxChars = typeof overrides?.maxChars === "number" ? Math.max(0, Math.floor(overrides.maxChars)) : config.knowledgeIndexMaxChars;
2527
+ for (const entity of topN) {
2528
+ const summary = entity.summary || `${entity.factCount} facts`;
2529
+ const connected = entity.topRelationships.length > 0 ? entity.topRelationships.join(", ") : "\u2014";
2530
+ const row = `| ${entity.name} | ${entity.type} | ${summary} | ${connected} |`;
2531
+ if (totalChars + row.length + 1 > maxChars) break;
2532
+ rows.push(row);
2533
+ totalChars += row.length + 1;
2534
+ }
2535
+ const result = rows.length === 0 ? "" : `${header}
2536
+ ${rows.join("\n")}
2537
+ `;
2538
+ if (useDefaultLimits) this.knowledgeIndexCache = { result, builtAt: Date.now() };
2539
+ return { result, cached: false };
2540
+ }
2541
+ /** Invalidate the Knowledge Index cache (call after entity mutations). */
2542
+ invalidateKnowledgeIndexCache() {
2543
+ this.knowledgeIndexCache = null;
2544
+ }
2545
+ // ---------------------------------------------------------------------------
2546
+ // Commitment decay
2547
+ // ---------------------------------------------------------------------------
2548
+ /** Max lines for profile.md before LLM consolidation triggers */
2549
+ static PROFILE_MAX_LINES = 300;
2550
+ /**
2551
+ * Merge fragmented entity files that resolve to the same canonical name.
2552
+ * Preserves relationships, activity, aliases, and summary from all fragments.
2553
+ * Returns count of files merged.
2554
+ */
2555
+ async mergeFragmentedEntities() {
2556
+ let merged = 0;
2557
+ try {
2558
+ const entries = await readdir(this.entitiesDir);
2559
+ const mdFiles = entries.filter((e) => e.endsWith(".md"));
2560
+ const groups = /* @__PURE__ */ new Map();
2561
+ for (const file of mdFiles) {
2562
+ const baseName = file.replace(".md", "");
2563
+ const dashIdx = baseName.indexOf("-");
2564
+ if (dashIdx === -1) continue;
2565
+ const type = baseName.slice(0, dashIdx);
2566
+ const restOfName = baseName.slice(dashIdx + 1);
2567
+ const canonical = normalizeEntityName(restOfName, type);
2568
+ if (!groups.has(canonical)) groups.set(canonical, []);
2569
+ groups.get(canonical).push(file);
2570
+ }
2571
+ for (const [canonical, files] of groups) {
2572
+ if (files.length <= 1) continue;
2573
+ const mergedEntity = {
2574
+ name: "",
2575
+ type: "other",
2576
+ updated: "",
2577
+ facts: [],
2578
+ summary: void 0,
2579
+ relationships: [],
2580
+ activity: [],
2581
+ aliases: []
2582
+ };
2583
+ for (const file of files) {
2584
+ const filePath = path.join(this.entitiesDir, file);
2585
+ try {
2586
+ const content = await readFile(filePath, "utf-8");
2587
+ const parsed = parseEntityFile(content);
2588
+ if (!mergedEntity.type || mergedEntity.type === "other") {
2589
+ mergedEntity.type = parsed.type;
2590
+ }
2591
+ if (!mergedEntity.updated || parsed.updated > mergedEntity.updated) {
2592
+ mergedEntity.updated = parsed.updated;
2593
+ }
2594
+ if (parsed.name.length > mergedEntity.name.length) {
2595
+ mergedEntity.name = parsed.name;
2596
+ }
2597
+ if (!mergedEntity.summary && parsed.summary) {
2598
+ mergedEntity.summary = parsed.summary;
2599
+ }
2600
+ mergedEntity.facts.push(...parsed.facts);
2601
+ mergedEntity.relationships.push(...parsed.relationships);
2602
+ mergedEntity.activity.push(...parsed.activity);
2603
+ mergedEntity.aliases.push(...parsed.aliases);
2604
+ } catch {
2605
+ }
2606
+ }
2607
+ mergedEntity.facts = [...new Set(mergedEntity.facts)];
2608
+ const relKeys = /* @__PURE__ */ new Set();
2609
+ mergedEntity.relationships = mergedEntity.relationships.filter((r) => {
2610
+ const key = `${r.target}::${r.label}`;
2611
+ if (relKeys.has(key)) return false;
2612
+ relKeys.add(key);
2613
+ return true;
2614
+ });
2615
+ const actKeys = /* @__PURE__ */ new Set();
2616
+ mergedEntity.activity = mergedEntity.activity.filter((a) => {
2617
+ const key = `${a.date}::${a.note}`;
2618
+ if (actKeys.has(key)) return false;
2619
+ actKeys.add(key);
2620
+ return true;
2621
+ }).sort((a, b) => b.date.localeCompare(a.date));
2622
+ mergedEntity.aliases = [...new Set(mergedEntity.aliases)];
2623
+ if (!mergedEntity.name) {
2624
+ const dashIdx = canonical.indexOf("-");
2625
+ mergedEntity.name = dashIdx !== -1 ? canonical.slice(dashIdx + 1) : canonical;
2626
+ }
2627
+ mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
2628
+ const canonicalPath = path.join(this.entitiesDir, `${canonical}.md`);
2629
+ await writeFile(canonicalPath, serializeEntityFile(mergedEntity), "utf-8");
2630
+ for (const file of files) {
2631
+ const filePath = path.join(this.entitiesDir, file);
2632
+ if (filePath !== canonicalPath) {
2633
+ try {
2634
+ await unlink(filePath);
2635
+ merged++;
2636
+ log.debug(`merged entity ${file} \u2192 ${canonical}.md`);
2637
+ } catch {
2638
+ }
2639
+ }
2640
+ }
2641
+ }
2642
+ } catch {
2643
+ }
2644
+ return merged;
2645
+ }
2646
+ async cleanExpiredCommitments(decayDays) {
2647
+ const memories = await this.readAllMemories();
2648
+ const cutoff = Date.now() - decayDays * 24 * 60 * 60 * 1e3;
2649
+ const deleted = [];
2650
+ for (const m of memories) {
2651
+ if (m.frontmatter.category !== "commitment") continue;
2652
+ const isResolved = m.frontmatter.tags.some(
2653
+ (t) => t === "fulfilled" || t === "expired"
2654
+ );
2655
+ if (!isResolved) continue;
2656
+ const updatedAt = new Date(m.frontmatter.updated).getTime();
2657
+ if (updatedAt < cutoff) {
2658
+ try {
2659
+ await unlink(m.path);
2660
+ deleted.push(m);
2661
+ log.debug(`cleaned expired commitment ${m.frontmatter.id}`);
2662
+ } catch {
2663
+ }
2664
+ }
2665
+ }
2666
+ if (deleted.length > 0) {
2667
+ this.bumpMemoryStatusVersion();
2668
+ }
2669
+ return deleted;
2670
+ }
2671
+ // ---------------------------------------------------------------------------
2672
+ // Access Tracking (Phase 1A)
2673
+ // ---------------------------------------------------------------------------
2674
+ /**
2675
+ * Flush batched access tracking updates to disk.
2676
+ * Called during consolidation or when buffer exceeds max size.
2677
+ */
2678
+ async flushAccessTracking(entries) {
2679
+ if (entries.length === 0) return 0;
2680
+ const memories = await this.readAllMemories();
2681
+ const memoryMap = new Map(memories.map((m) => [m.frontmatter.id, m]));
2682
+ let updated = 0;
2683
+ for (const entry of entries) {
2684
+ const memory = memoryMap.get(entry.memoryId);
2685
+ if (!memory) continue;
2686
+ const newFm = {
2687
+ ...memory.frontmatter,
2688
+ accessCount: entry.newCount,
2689
+ lastAccessed: entry.lastAccessed
2690
+ };
2691
+ const fileContent = `${serializeFrontmatter(newFm)}
2692
+
2693
+ ${memory.content}
2694
+ `;
2695
+ try {
2696
+ await writeFile(memory.path, fileContent, "utf-8");
2697
+ updated++;
2698
+ } catch (err) {
2699
+ log.debug(`failed to update access tracking for ${entry.memoryId}: ${err}`);
2700
+ }
2701
+ }
2702
+ if (updated > 0) {
2703
+ log.debug(`flushed access tracking for ${updated} memories`);
2704
+ }
2705
+ return updated;
2706
+ }
2707
+ /**
2708
+ * Get a memory by its ID.
2709
+ */
2710
+ async getMemoryById(id) {
2711
+ const memories = await this.readAllMemories();
2712
+ return memories.find((m) => m.frontmatter.id === id) ?? null;
2713
+ }
2714
+ async getProjectedMemoryState(id) {
2715
+ const projected = readProjectedMemoryState(this.baseDir, id);
2716
+ if (projected) return projected;
2717
+ const active = await this.getMemoryById(id);
2718
+ if (active) return this.toProjectedCurrentState(active, "active");
2719
+ const archived = (await this.readArchivedMemories()).find((memory) => memory.frontmatter.id === id);
2720
+ if (!archived) return null;
2721
+ return this.toProjectedCurrentState(archived, "archived");
2722
+ }
2723
+ async browseProjectedMemories(options) {
2724
+ return readProjectedMemoryBrowse(this.baseDir, options);
2725
+ }
2726
+ async getProjectedGovernanceRecord() {
2727
+ return readProjectedGovernanceRecord(this.baseDir);
2728
+ }
2729
+ toProjectedCurrentState(memory, fallbackStatus) {
2730
+ const pathRel = toMemoryPathRel(this.baseDir, memory.path);
2731
+ return {
2732
+ memoryId: memory.frontmatter.id,
2733
+ category: memory.frontmatter.category,
2734
+ status: inferCurrentStateStatus(memory.frontmatter, pathRel, fallbackStatus),
2735
+ lifecycleState: memory.frontmatter.lifecycleState,
2736
+ path: memory.path,
2737
+ pathRel,
2738
+ created: memory.frontmatter.created,
2739
+ updated: memory.frontmatter.updated,
2740
+ archivedAt: memory.frontmatter.archivedAt,
2741
+ supersededAt: memory.frontmatter.supersededAt,
2742
+ entityRef: memory.frontmatter.entityRef,
2743
+ source: memory.frontmatter.source,
2744
+ confidence: memory.frontmatter.confidence,
2745
+ confidenceTier: memory.frontmatter.confidenceTier,
2746
+ memoryKind: memory.frontmatter.memoryKind,
2747
+ accessCount: memory.frontmatter.accessCount,
2748
+ lastAccessed: memory.frontmatter.lastAccessed,
2749
+ tags: normalizeProjectionTags(memory.frontmatter.tags),
2750
+ preview: normalizeProjectionPreview(memory.content)
2751
+ };
2752
+ }
2753
+ async getMemoryTimeline(memoryId, limit = 200) {
2754
+ const cappedLimit = Math.max(0, Math.floor(limit));
2755
+ if (cappedLimit === 0) return [];
2756
+ const projected = readProjectedMemoryTimeline(this.baseDir, memoryId, cappedLimit);
2757
+ if (projected && projected.length > 0) return projected;
2758
+ const events = await this.readAllMemoryLifecycleEvents();
2759
+ return events.filter((event) => event.memoryId === memoryId).slice(-cappedLimit);
2760
+ }
2761
+ // ---------------------------------------------------------------------------
2762
+ // Chunking (Phase 2A)
2763
+ // ---------------------------------------------------------------------------
2764
+ /**
2765
+ * Write a memory chunk with parent reference.
2766
+ * Chunk IDs follow format: {parentId}-chunk-{index}
2767
+ */
2768
+ async writeChunk(parentId, chunkIndex, chunkTotal, category, content, options = {}) {
2769
+ await this.ensureDirectories();
2770
+ const now = /* @__PURE__ */ new Date();
2771
+ const today = now.toISOString().slice(0, 10);
2772
+ const id = `${parentId}-chunk-${chunkIndex}`;
2773
+ const conf = options.confidence ?? 0.8;
2774
+ const tier = confidenceTier(conf);
2775
+ const fm = {
2776
+ id,
2777
+ category,
2778
+ created: now.toISOString(),
2779
+ updated: now.toISOString(),
2780
+ source: options.source ?? "chunking",
2781
+ confidence: conf,
2782
+ confidenceTier: tier,
2783
+ tags: options.tags ?? [],
2784
+ entityRef: options.entityRef,
2785
+ importance: options.importance,
2786
+ parentId,
2787
+ chunkIndex,
2788
+ chunkTotal,
2789
+ intentGoal: options.intentGoal,
2790
+ intentActionType: options.intentActionType,
2791
+ intentEntityTypes: options.intentEntityTypes,
2792
+ memoryKind: options.memoryKind
2793
+ };
2794
+ const sanitized = sanitizeMemoryContent(content);
2795
+ if (!sanitized.clean) {
2796
+ log.warn(`chunk content sanitized for ${id}; violations=${sanitized.violations.join(", ")}`);
2797
+ }
2798
+ const fileContent = `${serializeFrontmatter(fm)}
2799
+
2800
+ ${sanitized.text}
2801
+ `;
2802
+ let filePath;
2803
+ if (category === "correction") {
2804
+ filePath = path.join(this.correctionsDir, `${id}.md`);
2805
+ } else {
2806
+ filePath = path.join(this.factsDir, today, `${id}.md`);
2807
+ }
2808
+ await writeFile(filePath, fileContent, "utf-8");
2809
+ log.debug(`wrote chunk ${id} (${chunkIndex + 1}/${chunkTotal}) to ${filePath}`);
2810
+ return id;
2811
+ }
2812
+ /**
2813
+ * Get all chunks for a given parent memory ID.
2814
+ * Returns chunks sorted by chunkIndex.
2815
+ */
2816
+ async getChunksForParent(parentId) {
2817
+ const memories = await this.readAllMemories();
2818
+ return memories.filter((m) => m.frontmatter.parentId === parentId).sort((a, b) => (a.frontmatter.chunkIndex ?? 0) - (b.frontmatter.chunkIndex ?? 0));
2819
+ }
2820
+ // ---------------------------------------------------------------------------
2821
+ // Contradiction Detection (Phase 2B)
2822
+ // ---------------------------------------------------------------------------
2823
+ /**
2824
+ * Mark a memory as superseded by another.
2825
+ * Updates the old memory's status and adds the supersededBy link.
2826
+ */
2827
+ async supersedeMemory(oldMemoryId, newMemoryId, reason) {
2828
+ const memories = await this.readAllMemories();
2829
+ const oldMemory = memories.find((m) => m.frontmatter.id === oldMemoryId);
2830
+ if (!oldMemory) return false;
2831
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2832
+ const updatedFm = {
2833
+ ...oldMemory.frontmatter,
2834
+ status: "superseded",
2835
+ supersededBy: newMemoryId,
2836
+ supersededAt: now,
2837
+ updated: now
2838
+ };
2839
+ const fileContent = `${serializeFrontmatter(updatedFm)}
2840
+
2841
+ ${oldMemory.content}
2842
+ `;
2843
+ try {
2844
+ await writeFile(oldMemory.path, fileContent, "utf-8");
2845
+ await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.supersedeMemory", {
2846
+ memoryId: oldMemoryId,
2847
+ eventType: "superseded",
2848
+ timestamp: now,
2849
+ actor: "storage.supersedeMemory",
2850
+ reasonCode: reason,
2851
+ before: this.summarizeLifecycleState(oldMemory.frontmatter, oldMemory.path),
2852
+ after: this.summarizeLifecycleState(updatedFm, oldMemory.path),
2853
+ relatedMemoryIds: [newMemoryId]
2854
+ });
2855
+ this.bumpMemoryStatusVersion();
2856
+ log.debug(`superseded memory ${oldMemoryId} by ${newMemoryId}: ${reason}`);
2857
+ await this.writeMemory("correction", `Superseded: ${oldMemory.content}
2858
+
2859
+ Reason: ${reason}`, {
2860
+ confidence: 1,
2861
+ tags: ["supersession", "auto-resolved"],
2862
+ source: "contradiction-detection",
2863
+ lineage: [oldMemoryId, newMemoryId]
2864
+ });
2865
+ return true;
2866
+ } catch (err) {
2867
+ log.error(`failed to supersede memory ${oldMemoryId}:`, err);
2868
+ return false;
2869
+ }
2870
+ }
2871
+ // ---------------------------------------------------------------------------
2872
+ // Memory Summarization (Phase 4A)
2873
+ // ---------------------------------------------------------------------------
2874
+ get summariesDir() {
2875
+ return path.join(this.baseDir, "summaries");
2876
+ }
2877
+ /**
2878
+ * Write a memory summary.
2879
+ */
2880
+ async writeSummary(summary) {
2881
+ await mkdir(this.summariesDir, { recursive: true });
2882
+ const filePath = path.join(this.summariesDir, `${summary.id}.json`);
2883
+ await writeFile(filePath, JSON.stringify(summary, null, 2), "utf-8");
2884
+ log.debug(`wrote summary ${summary.id}`);
2885
+ }
2886
+ /**
2887
+ * Get all summaries.
2888
+ */
2889
+ async readSummaries() {
2890
+ try {
2891
+ const files = await readdir(this.summariesDir);
2892
+ const summaries = [];
2893
+ for (const file of files) {
2894
+ if (!file.endsWith(".json")) continue;
2895
+ const filePath = path.join(this.summariesDir, file);
2896
+ const raw = await readFile(filePath, "utf-8");
2897
+ summaries.push(JSON.parse(raw));
2898
+ }
2899
+ return summaries;
2900
+ } catch {
2901
+ return [];
2902
+ }
2903
+ }
2904
+ /**
2905
+ * Archive memories (mark as archived, not delete).
2906
+ */
2907
+ async archiveMemories(memoryIds, summaryId) {
2908
+ const memories = await this.readAllMemories();
2909
+ const memoryMap = new Map(memories.map((m) => [m.frontmatter.id, m]));
2910
+ let archived = 0;
2911
+ for (const id of memoryIds) {
2912
+ const memory = memoryMap.get(id);
2913
+ if (!memory) continue;
2914
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2915
+ const updatedFm = {
2916
+ ...memory.frontmatter,
2917
+ status: "archived",
2918
+ archivedAt: now,
2919
+ updated: now
2920
+ };
2921
+ const fileContent = `${serializeFrontmatter(updatedFm)}
2922
+
2923
+ ${memory.content}
2924
+ `;
2925
+ try {
2926
+ await writeFile(memory.path, fileContent, "utf-8");
2927
+ await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.archiveMemories", {
2928
+ memoryId: id,
2929
+ eventType: "archived",
2930
+ timestamp: updatedFm.archivedAt ?? updatedFm.updated,
2931
+ actor: "storage.archiveMemories",
2932
+ reasonCode: `summary:${summaryId}`,
2933
+ before: this.summarizeLifecycleState(memory.frontmatter, memory.path),
2934
+ after: this.summarizeLifecycleState(updatedFm, memory.path),
2935
+ relatedMemoryIds: [summaryId]
2936
+ });
2937
+ archived++;
2938
+ } catch {
2939
+ }
2940
+ }
2941
+ if (archived > 0) {
2942
+ this.bumpMemoryStatusVersion();
2943
+ log.debug(`archived ${archived} memories for summary ${summaryId}`);
2944
+ }
2945
+ return archived;
2946
+ }
2947
+ // ---------------------------------------------------------------------------
2948
+ // Topic Extraction (Phase 4B)
2949
+ // ---------------------------------------------------------------------------
2950
+ /**
2951
+ * Save topic scores to meta.json.
2952
+ */
2953
+ async saveTopics(topics) {
2954
+ const metaPath = path.join(this.stateDir, "topics.json");
2955
+ await mkdir(this.stateDir, { recursive: true });
2956
+ await writeFile(metaPath, JSON.stringify({ topics, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf-8");
2957
+ log.debug(`saved ${topics.length} topic scores`);
2958
+ }
2959
+ /**
2960
+ * Load topic scores from meta.json.
2961
+ */
2962
+ async loadTopics() {
2963
+ const metaPath = path.join(this.stateDir, "topics.json");
2964
+ try {
2965
+ const raw = await readFile(metaPath, "utf-8");
2966
+ return JSON.parse(raw);
2967
+ } catch {
2968
+ return { topics: [], updatedAt: null };
2969
+ }
2970
+ }
2971
+ /**
2972
+ * Add links to an existing memory.
2973
+ */
2974
+ async addLinksToMemory(memoryId, links, lifecycle) {
2975
+ const memories = await this.readAllMemories();
2976
+ const memory = memories.find((m) => m.frontmatter.id === memoryId);
2977
+ if (!memory) return false;
2978
+ const existingLinks = memory.frontmatter.links ?? [];
2979
+ const mergedLinks = [...existingLinks];
2980
+ for (const link of links) {
2981
+ if (!mergedLinks.some((l) => l.targetId === link.targetId && l.linkType === link.linkType)) {
2982
+ mergedLinks.push(link);
2983
+ }
2984
+ }
2985
+ try {
2986
+ await this.writeMemoryFrontmatter(
2987
+ memory,
2988
+ {
2989
+ links: mergedLinks,
2990
+ updated: (/* @__PURE__ */ new Date()).toISOString()
2991
+ },
2992
+ lifecycle
2993
+ );
2994
+ log.debug(`added ${links.length} links to memory ${memoryId}`);
2995
+ return true;
2996
+ } catch (err) {
2997
+ log.error(`failed to add links to memory ${memoryId}:`, err);
2998
+ return false;
2999
+ }
3000
+ }
3001
+ summarizeLifecycleState(frontmatter, filePath) {
3002
+ return {
3003
+ category: frontmatter.category,
3004
+ path: filePath,
3005
+ status: frontmatter.status ?? "active",
3006
+ lifecycleState: frontmatter.lifecycleState
3007
+ };
3008
+ }
3009
+ frontmatterPatchEventType(before, after) {
3010
+ const beforeStatus = before.status ?? "active";
3011
+ const afterStatus = after.status ?? "active";
3012
+ if (beforeStatus !== "archived" && afterStatus === "archived") return "archived";
3013
+ if (beforeStatus !== "superseded" && afterStatus === "superseded") return "superseded";
3014
+ if (beforeStatus !== "rejected" && afterStatus === "rejected") return "rejected";
3015
+ if (beforeStatus !== "active" && afterStatus === "active") {
3016
+ return "restored";
3017
+ }
3018
+ return "updated";
3019
+ }
3020
+ async appendGeneratedMemoryLifecycleEvent(input, ruleVersion = "memory-lifecycle-ledger.v1") {
3021
+ await this.appendMemoryLifecycleEvents([
3022
+ {
3023
+ ...input,
3024
+ eventId: this.generateId("mle"),
3025
+ ruleVersion
3026
+ }
3027
+ ]);
3028
+ }
3029
+ async appendGeneratedMemoryLifecycleEventFailOpen(operation, input, ruleVersion) {
3030
+ try {
3031
+ await this.appendGeneratedMemoryLifecycleEvent(input, ruleVersion);
3032
+ } catch (appendErr) {
3033
+ log.warn(`${operation} completed but failed to append lifecycle event: ${appendErr}`);
3034
+ }
3035
+ }
3036
+ };
3037
+
3038
+ export {
3039
+ normalizeEntityName,
3040
+ ContentHashIndex,
3041
+ parseEntityFile,
3042
+ serializeEntityFile,
3043
+ StorageManager
3044
+ };
3045
+ //# sourceMappingURL=chunk-QWUUMMIK.js.map