@remnic/core 9.3.754 → 9.3.756

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 (306) hide show
  1. package/dist/access-admin-ops-surface.d.ts +5 -5
  2. package/dist/access-admin-ops-surface.js +34 -34
  3. package/dist/access-boundary.d.ts +5 -5
  4. package/dist/access-boundary.js +35 -35
  5. package/dist/access-cli.js +83 -83
  6. package/dist/access-http.d.ts +5 -5
  7. package/dist/access-http.js +44 -44
  8. package/dist/access-lcm-surface.d.ts +5 -5
  9. package/dist/access-lcm-surface.js +34 -34
  10. package/dist/access-mcp.d.ts +5 -5
  11. package/dist/access-mcp.js +41 -41
  12. package/dist/access-observe-write-surface.d.ts +225 -0
  13. package/dist/access-observe-write-surface.js +128 -0
  14. package/dist/access-operations-batch.js +38 -38
  15. package/dist/access-operations.d.ts +5 -5
  16. package/dist/access-operations.js +40 -40
  17. package/dist/access-recall-surface.d.ts +5 -5
  18. package/dist/access-recall-surface.js +34 -34
  19. package/dist/access-schema.d.ts +10 -10
  20. package/dist/access-schema.js +3 -3
  21. package/dist/{access-service-DKRV1zxh.d.ts → access-service-CJwcDoQp.d.ts} +7 -8
  22. package/dist/access-service.d.ts +5 -5
  23. package/dist/access-service.js +34 -34
  24. package/dist/access-surface-catalog.d.ts +5 -5
  25. package/dist/action-confidence.d.ts +1 -1
  26. package/dist/active-memory-bridge.d.ts +1 -1
  27. package/dist/active-recall.d.ts +1 -1
  28. package/dist/active-recall.js +1 -1
  29. package/dist/adapters/index.js +4 -4
  30. package/dist/adapters/registry.js +2 -2
  31. package/dist/{auto-sync-G4YKBPOU.js → auto-sync-RZO5ZPHR.js} +3 -3
  32. package/dist/behavior-learner.d.ts +1 -1
  33. package/dist/behavior-signals.d.ts +1 -1
  34. package/dist/bootstrap.d.ts +4 -4
  35. package/dist/briefing.d.ts +1 -1
  36. package/dist/briefing.js +7 -7
  37. package/dist/buffer-surprise-report.d.ts +1 -1
  38. package/dist/buffer.d.ts +1 -1
  39. package/dist/calibration.d.ts +1 -1
  40. package/dist/capabilities.d.ts +1 -1
  41. package/dist/{capsule-crypto-CZJSLEFG.js → capsule-crypto-FFWJ4X4A.js} +2 -2
  42. package/dist/{catalog-BEmNVRN0.d.ts → catalog-DaKAFbSJ.d.ts} +1 -1
  43. package/dist/causal-behavior.d.ts +1 -1
  44. package/dist/causal-behavior.js +2 -2
  45. package/dist/causal-chain.js +2 -2
  46. package/dist/causal-consolidation.d.ts +1 -1
  47. package/dist/causal-consolidation.js +15 -15
  48. package/dist/causal-retrieval.js +2 -2
  49. package/dist/causal-trajectory-graph.d.ts +1 -1
  50. package/dist/causal-trajectory.js +1 -1
  51. package/dist/{chunk-M5QKGHCR.js → chunk-3I2IASNK.js} +2 -2
  52. package/dist/{chunk-JBO6UBIE.js → chunk-4AC57RCM.js} +4 -4
  53. package/dist/{chunk-3DOD6ICJ.js → chunk-4I45UZ4V.js} +5 -5
  54. package/dist/{chunk-TAPSNIMA.js → chunk-5CEJH5ZN.js} +4 -4
  55. package/dist/{chunk-CXCZ4LAI.js → chunk-5VWRYFQE.js} +7 -7
  56. package/dist/{chunk-TXALJRYO.js → chunk-5ZKT7WTR.js} +7 -7
  57. package/dist/{chunk-PYVZKEZZ.js → chunk-6DA5BBLS.js} +120 -120
  58. package/dist/{chunk-WS6GLJ7P.js → chunk-6DXXIXED.js} +4 -4
  59. package/dist/{chunk-THZ4WR5L.js → chunk-7APVFQ34.js} +1182 -1009
  60. package/dist/chunk-7APVFQ34.js.map +1 -0
  61. package/dist/{chunk-OH66EBCS.js → chunk-7CFGXWYX.js} +2 -2
  62. package/dist/{chunk-TJATYB3F.js → chunk-A6XCHI53.js} +5 -5
  63. package/dist/{chunk-2MHCWXX6.js → chunk-BTH6MQJT.js} +7 -7
  64. package/dist/{chunk-G7LCG56O.js → chunk-CXW6Y7SI.js} +4 -4
  65. package/dist/{chunk-L4AS7TIG.js → chunk-DWDDQLSZ.js} +2300 -2239
  66. package/dist/chunk-DWDDQLSZ.js.map +1 -0
  67. package/dist/{chunk-DS4QM6I7.js → chunk-EEKDRAWX.js} +4 -4
  68. package/dist/{chunk-GAH5755F.js → chunk-EF3VFQC4.js} +2 -2
  69. package/dist/{chunk-437S3G37.js → chunk-EJJQDP3H.js} +1 -1
  70. package/dist/{chunk-V4C4SFZB.js → chunk-EVVLGYHR.js} +3 -3
  71. package/dist/{chunk-ZJMSTFOR.js → chunk-GBXP62CL.js} +10 -10
  72. package/dist/{chunk-7AL5WNPW.js → chunk-GG6G2RYR.js} +9 -9
  73. package/dist/{chunk-BERWPOXU.js → chunk-H2T4OJNI.js} +4 -4
  74. package/dist/{chunk-2MR3MFQB.js → chunk-JTYM4HPA.js} +2 -2
  75. package/dist/{chunk-LD53WPMU.js → chunk-LQ6JI4VH.js} +4 -4
  76. package/dist/{chunk-GPFXPC2V.js → chunk-NG2NHB4C.js} +7 -7
  77. package/dist/{chunk-COZEVVXZ.js → chunk-NLXKSM46.js} +2 -2
  78. package/dist/{chunk-VSU2NZGC.js → chunk-OLAVQS6S.js} +6 -6
  79. package/dist/{chunk-ER3CCWK2.js → chunk-ORAK3LNZ.js} +2 -2
  80. package/dist/{chunk-WUVUTOUV.js → chunk-PBZQVVLG.js} +5 -5
  81. package/dist/{chunk-PJQMISPD.js → chunk-PPAEJXVF.js} +5 -5
  82. package/dist/{chunk-KURUULFF.js → chunk-PRX77NPC.js} +5 -5
  83. package/dist/{chunk-24463PIV.js → chunk-PS572XYO.js} +2 -2
  84. package/dist/{chunk-U2SMR57B.js → chunk-REZ4TUD2.js} +10 -10
  85. package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
  86. package/dist/{chunk-V5UV7BOA.js → chunk-SQNED75R.js} +1 -1
  87. package/dist/{chunk-FLVL4L3L.js → chunk-TCKP4EHY.js} +2 -2
  88. package/dist/{chunk-YRBXBHU7.js → chunk-TXV3I2O6.js} +7 -7
  89. package/dist/{chunk-CJ276MDI.js → chunk-UNV6ZGTS.js} +2 -2
  90. package/dist/{chunk-2BFLDH5F.js → chunk-WEPWY4HB.js} +2 -2
  91. package/dist/{chunk-YQGHW5ML.js → chunk-WRFKZEO6.js} +4 -4
  92. package/dist/{chunk-7CAJLUNW.js → chunk-Y4TAFMUE.js} +6 -6
  93. package/dist/{chunk-TIFF32QK.js → chunk-YMGQXBPS.js} +2 -2
  94. package/dist/{chunk-T422SYM6.js → chunk-ZUDM75KG.js} +5 -5
  95. package/dist/{chunk-4DVYRPLL.js → chunk-ZUHD6XZZ.js} +43 -43
  96. package/dist/{chunk-BQ6J6P3K.js → chunk-ZW5SZ3TH.js} +12 -12
  97. package/dist/{cli-D-xw8Sp1.d.ts → cli-cZyANcmY.d.ts} +3 -3
  98. package/dist/cli.d.ts +6 -6
  99. package/dist/cli.js +59 -59
  100. package/dist/compounding/engine.d.ts +1 -1
  101. package/dist/compounding/engine.js +8 -8
  102. package/dist/compounding/preference-consolidator.d.ts +1 -1
  103. package/dist/compression-optimizer.d.ts +1 -1
  104. package/dist/config.d.ts +1 -1
  105. package/dist/config.js +1 -1
  106. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  107. package/dist/connectors/codex-materialize-runner.js +8 -8
  108. package/dist/connectors/codex-materialize.d.ts +1 -1
  109. package/dist/connectors/index.d.ts +1 -1
  110. package/dist/connectors/index.js +9 -9
  111. package/dist/consolidation-provenance-check.d.ts +1 -1
  112. package/dist/consolidation-undo.d.ts +1 -1
  113. package/dist/contradiction/index.d.ts +1 -1
  114. package/dist/contradiction/index.js +2 -2
  115. package/dist/{contradiction-scan-2SODRNUF.js → contradiction-scan-3FJYWS3G.js} +3 -3
  116. package/dist/contradiction-scan-3FJYWS3G.js.map +1 -0
  117. package/dist/conversation-index/backend.d.ts +1 -1
  118. package/dist/conversation-index/backend.js +2 -2
  119. package/dist/conversation-index/chunker.d.ts +1 -1
  120. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  121. package/dist/conversation-index/indexer.d.ts +1 -1
  122. package/dist/conversation-index/search.d.ts +1 -1
  123. package/dist/day-summary.d.ts +1 -1
  124. package/dist/delinearize.d.ts +1 -1
  125. package/dist/direct-answer-wiring.d.ts +1 -1
  126. package/dist/direct-answer.d.ts +1 -1
  127. package/dist/embedding-fallback.d.ts +1 -1
  128. package/dist/enrichment/index.d.ts +1 -1
  129. package/dist/entity-retrieval.d.ts +1 -1
  130. package/dist/entity-retrieval.js +8 -8
  131. package/dist/entity-schema.d.ts +1 -1
  132. package/dist/explicit-capture.d.ts +4 -4
  133. package/dist/extraction-faithfulness.d.ts +1 -1
  134. package/dist/extraction-judge-telemetry.d.ts +1 -1
  135. package/dist/extraction-judge-training.d.ts +1 -1
  136. package/dist/extraction-judge.d.ts +1 -1
  137. package/dist/extraction.d.ts +1 -1
  138. package/dist/extraction.js +2 -2
  139. package/dist/fallback-llm.d.ts +1 -1
  140. package/dist/graph-dashboard-diff.d.ts +1 -1
  141. package/dist/graph-dashboard-key.d.ts +1 -1
  142. package/dist/graph-dashboard-parser.d.ts +1 -1
  143. package/dist/graph-edge-reinforcement.d.ts +1 -1
  144. package/dist/graph-snapshot.d.ts +1 -1
  145. package/dist/graph.d.ts +1 -1
  146. package/dist/identity-continuity.d.ts +1 -1
  147. package/dist/importance.d.ts +1 -1
  148. package/dist/index.d.ts +9 -9
  149. package/dist/index.js +126 -126
  150. package/dist/intent.d.ts +1 -1
  151. package/dist/lcm/engine.d.ts +1 -1
  152. package/dist/lcm/engine.js +2 -2
  153. package/dist/lcm/index.d.ts +1 -1
  154. package/dist/lcm/index.js +2 -2
  155. package/dist/lcm/tools.d.ts +1 -1
  156. package/dist/lifecycle.d.ts +1 -1
  157. package/dist/live-connectors-runner.d.ts +1 -1
  158. package/dist/local-llm.d.ts +1 -1
  159. package/dist/local-model-endpoint.d.ts +1 -1
  160. package/dist/maintenance/memory-governance.d.ts +1 -1
  161. package/dist/maintenance/memory-governance.js +8 -8
  162. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
  163. package/dist/maintenance/rebuild-memory-projection.js +9 -9
  164. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  165. package/dist/memory-action-policy.d.ts +1 -1
  166. package/dist/memory-cache.d.ts +1 -1
  167. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  168. package/dist/memory-projection-store.d.ts +1 -1
  169. package/dist/memory-provenance.d.ts +1 -1
  170. package/dist/memory-worth-outcomes.d.ts +1 -1
  171. package/dist/models-json.d.ts +1 -1
  172. package/dist/namespaces/migrate.d.ts +2 -2
  173. package/dist/namespaces/migrate.js +18 -18
  174. package/dist/namespaces/principal.d.ts +1 -1
  175. package/dist/namespaces/search.d.ts +1 -1
  176. package/dist/namespaces/search.js +9 -9
  177. package/dist/namespaces/storage.d.ts +2 -2
  178. package/dist/namespaces/storage.js +9 -9
  179. package/dist/native-knowledge.d.ts +1 -1
  180. package/dist/operator-toolkit.d.ts +1 -1
  181. package/dist/operator-toolkit.js +24 -24
  182. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  183. package/dist/orchestration/maintenance.d.ts +2 -2
  184. package/dist/orchestration/maintenance.js +12 -12
  185. package/dist/{orchestrator-D7ZGOQLX.d.ts → orchestrator-Xm6sK5jw.d.ts} +3 -3
  186. package/dist/orchestrator.d.ts +4 -4
  187. package/dist/orchestrator.js +82 -82
  188. package/dist/patterns-cli.d.ts +1 -1
  189. package/dist/policy-runtime.d.ts +1 -1
  190. package/dist/provenance.d.ts +1 -1
  191. package/dist/qmd-recall-cache.d.ts +1 -1
  192. package/dist/qmd.d.ts +1 -1
  193. package/dist/recall-disclosure-escalation.d.ts +1 -1
  194. package/dist/recall-explain-renderer.d.ts +1 -1
  195. package/dist/recall-planner-llm.d.ts +1 -1
  196. package/dist/recall-state.d.ts +1 -1
  197. package/dist/recall-tag-filter.d.ts +1 -1
  198. package/dist/recall-xray-cli.d.ts +1 -1
  199. package/dist/recall-xray-renderer.d.ts +1 -1
  200. package/dist/recall-xray.d.ts +1 -1
  201. package/dist/resolve-auth-token.d.ts +1 -1
  202. package/dist/resume-bundles.js +2 -2
  203. package/dist/retrieval-agents.d.ts +1 -1
  204. package/dist/retrieval-tiers.d.ts +1 -1
  205. package/dist/routing/engine.d.ts +1 -1
  206. package/dist/routing/store.d.ts +1 -1
  207. package/dist/search/embed-helper.d.ts +1 -1
  208. package/dist/search/factory.d.ts +1 -1
  209. package/dist/search/factory.js +8 -8
  210. package/dist/search/index.d.ts +1 -1
  211. package/dist/search/index.js +8 -8
  212. package/dist/search/lancedb-backend.d.ts +1 -1
  213. package/dist/search/lancedb-backend.js +2 -2
  214. package/dist/search/meilisearch-backend.d.ts +1 -1
  215. package/dist/search/meilisearch-backend.js +2 -2
  216. package/dist/search/noop-backend.d.ts +1 -1
  217. package/dist/search/orama-backend.d.ts +1 -1
  218. package/dist/search/orama-backend.js +2 -2
  219. package/dist/search/port.d.ts +1 -1
  220. package/dist/search/remote-backend.d.ts +1 -1
  221. package/dist/{semantic-consolidation-7Sndms2M.d.ts → semantic-consolidation-bfXJNKVf.d.ts} +1 -1
  222. package/dist/semantic-consolidation.d.ts +2 -2
  223. package/dist/semantic-consolidation.js +11 -11
  224. package/dist/semantic-rule-promotion.js +7 -7
  225. package/dist/semantic-rule-verifier.d.ts +1 -1
  226. package/dist/semantic-rule-verifier.js +8 -8
  227. package/dist/session-observer-bands.d.ts +1 -1
  228. package/dist/session-observer-state.d.ts +1 -1
  229. package/dist/shared-context/manager.d.ts +3 -3
  230. package/dist/signal.d.ts +1 -1
  231. package/dist/storage.d.ts +69 -97
  232. package/dist/storage.js +22 -6
  233. package/dist/summarizer.d.ts +1 -1
  234. package/dist/summarizer.js +2 -2
  235. package/dist/summary-snapshot.d.ts +1 -1
  236. package/dist/temporal-supersession.d.ts +1 -1
  237. package/dist/temporal-validity.d.ts +1 -1
  238. package/dist/threading.d.ts +1 -1
  239. package/dist/tier-migration.d.ts +1 -1
  240. package/dist/tier-routing.d.ts +1 -1
  241. package/dist/topics.d.ts +1 -1
  242. package/dist/transcript.d.ts +1 -1
  243. package/dist/transfer/backup.js +2 -2
  244. package/dist/transfer/capsule-export.js +2 -2
  245. package/dist/transfer/capsule-import.js +2 -2
  246. package/dist/transfer/types.d.ts +12 -12
  247. package/dist/trust-score-stage.d.ts +1 -1
  248. package/dist/trust-score.d.ts +1 -1
  249. package/dist/{types-DTEFxFuL.d.ts → types-D1iBqhJA.d.ts} +1 -1
  250. package/dist/types.d.ts +1 -1
  251. package/dist/utility-runtime.d.ts +1 -1
  252. package/dist/verified-recall.js +8 -8
  253. package/package.json +2 -2
  254. package/src/access-observe-write-surface.ts +1007 -0
  255. package/src/access-service.ts +48 -794
  256. package/src/orchestration/self-deps.ts +9 -1
  257. package/src/storage/entity-store.ts +674 -0
  258. package/src/storage/memory-read-store.ts +756 -0
  259. package/src/storage.ts +111 -1191
  260. package/dist/chunk-L4AS7TIG.js.map +0 -1
  261. package/dist/chunk-THZ4WR5L.js.map +0 -1
  262. /package/dist/{capsule-crypto-CZJSLEFG.js.map → access-observe-write-surface.js.map} +0 -0
  263. /package/dist/{auto-sync-G4YKBPOU.js.map → auto-sync-RZO5ZPHR.js.map} +0 -0
  264. /package/dist/{contradiction-scan-2SODRNUF.js.map → capsule-crypto-FFWJ4X4A.js.map} +0 -0
  265. /package/dist/{chunk-M5QKGHCR.js.map → chunk-3I2IASNK.js.map} +0 -0
  266. /package/dist/{chunk-JBO6UBIE.js.map → chunk-4AC57RCM.js.map} +0 -0
  267. /package/dist/{chunk-3DOD6ICJ.js.map → chunk-4I45UZ4V.js.map} +0 -0
  268. /package/dist/{chunk-TAPSNIMA.js.map → chunk-5CEJH5ZN.js.map} +0 -0
  269. /package/dist/{chunk-CXCZ4LAI.js.map → chunk-5VWRYFQE.js.map} +0 -0
  270. /package/dist/{chunk-TXALJRYO.js.map → chunk-5ZKT7WTR.js.map} +0 -0
  271. /package/dist/{chunk-PYVZKEZZ.js.map → chunk-6DA5BBLS.js.map} +0 -0
  272. /package/dist/{chunk-WS6GLJ7P.js.map → chunk-6DXXIXED.js.map} +0 -0
  273. /package/dist/{chunk-OH66EBCS.js.map → chunk-7CFGXWYX.js.map} +0 -0
  274. /package/dist/{chunk-TJATYB3F.js.map → chunk-A6XCHI53.js.map} +0 -0
  275. /package/dist/{chunk-2MHCWXX6.js.map → chunk-BTH6MQJT.js.map} +0 -0
  276. /package/dist/{chunk-G7LCG56O.js.map → chunk-CXW6Y7SI.js.map} +0 -0
  277. /package/dist/{chunk-DS4QM6I7.js.map → chunk-EEKDRAWX.js.map} +0 -0
  278. /package/dist/{chunk-GAH5755F.js.map → chunk-EF3VFQC4.js.map} +0 -0
  279. /package/dist/{chunk-437S3G37.js.map → chunk-EJJQDP3H.js.map} +0 -0
  280. /package/dist/{chunk-V4C4SFZB.js.map → chunk-EVVLGYHR.js.map} +0 -0
  281. /package/dist/{chunk-ZJMSTFOR.js.map → chunk-GBXP62CL.js.map} +0 -0
  282. /package/dist/{chunk-7AL5WNPW.js.map → chunk-GG6G2RYR.js.map} +0 -0
  283. /package/dist/{chunk-BERWPOXU.js.map → chunk-H2T4OJNI.js.map} +0 -0
  284. /package/dist/{chunk-2MR3MFQB.js.map → chunk-JTYM4HPA.js.map} +0 -0
  285. /package/dist/{chunk-LD53WPMU.js.map → chunk-LQ6JI4VH.js.map} +0 -0
  286. /package/dist/{chunk-GPFXPC2V.js.map → chunk-NG2NHB4C.js.map} +0 -0
  287. /package/dist/{chunk-COZEVVXZ.js.map → chunk-NLXKSM46.js.map} +0 -0
  288. /package/dist/{chunk-VSU2NZGC.js.map → chunk-OLAVQS6S.js.map} +0 -0
  289. /package/dist/{chunk-ER3CCWK2.js.map → chunk-ORAK3LNZ.js.map} +0 -0
  290. /package/dist/{chunk-WUVUTOUV.js.map → chunk-PBZQVVLG.js.map} +0 -0
  291. /package/dist/{chunk-PJQMISPD.js.map → chunk-PPAEJXVF.js.map} +0 -0
  292. /package/dist/{chunk-KURUULFF.js.map → chunk-PRX77NPC.js.map} +0 -0
  293. /package/dist/{chunk-24463PIV.js.map → chunk-PS572XYO.js.map} +0 -0
  294. /package/dist/{chunk-U2SMR57B.js.map → chunk-REZ4TUD2.js.map} +0 -0
  295. /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
  296. /package/dist/{chunk-V5UV7BOA.js.map → chunk-SQNED75R.js.map} +0 -0
  297. /package/dist/{chunk-FLVL4L3L.js.map → chunk-TCKP4EHY.js.map} +0 -0
  298. /package/dist/{chunk-YRBXBHU7.js.map → chunk-TXV3I2O6.js.map} +0 -0
  299. /package/dist/{chunk-CJ276MDI.js.map → chunk-UNV6ZGTS.js.map} +0 -0
  300. /package/dist/{chunk-2BFLDH5F.js.map → chunk-WEPWY4HB.js.map} +0 -0
  301. /package/dist/{chunk-YQGHW5ML.js.map → chunk-WRFKZEO6.js.map} +0 -0
  302. /package/dist/{chunk-7CAJLUNW.js.map → chunk-Y4TAFMUE.js.map} +0 -0
  303. /package/dist/{chunk-TIFF32QK.js.map → chunk-YMGQXBPS.js.map} +0 -0
  304. /package/dist/{chunk-T422SYM6.js.map → chunk-ZUDM75KG.js.map} +0 -0
  305. /package/dist/{chunk-4DVYRPLL.js.map → chunk-ZUHD6XZZ.js.map} +0 -0
  306. /package/dist/{chunk-BQ6J6P3K.js.map → chunk-ZW5SZ3TH.js.map} +0 -0
@@ -16,18 +16,24 @@ import {
16
16
  import {
17
17
  supersessionKeysForFact
18
18
  } from "./chunk-TKZ3AL5V.js";
19
- import {
20
- stripAttributesSuffix
21
- } from "./chunk-SVOZFLIQ.js";
22
19
  import {
23
20
  DEFAULT_CITATION_FORMAT,
24
21
  hasCitation,
25
22
  stripCitationForTemplate
26
23
  } from "./chunk-J6A3CX5N.js";
24
+ import {
25
+ stripAttributesSuffix
26
+ } from "./chunk-SVOZFLIQ.js";
27
27
  import {
28
28
  SPECULATIVE_TTL_DAYS,
29
29
  confidenceTier
30
30
  } from "./chunk-JS2HKKOK.js";
31
+ import {
32
+ inferMemoryStatus,
33
+ isArchivedMemoryPath,
34
+ sortMemoryLifecycleEvents,
35
+ toMemoryPathRel
36
+ } from "./chunk-RULE4VG5.js";
31
37
  import {
32
38
  normalizeProjectionPreview,
33
39
  normalizeProjectionTags
@@ -43,15 +49,6 @@ import {
43
49
  invalidateAllForDir,
44
50
  setCachedEntities
45
51
  } from "./chunk-AWJ2FHCF.js";
46
- import {
47
- inferMemoryStatus,
48
- isArchivedMemoryPath,
49
- sortMemoryLifecycleEvents,
50
- toMemoryPathRel
51
- } from "./chunk-RULE4VG5.js";
52
- import {
53
- rotateMarkdownFileToArchive
54
- } from "./chunk-DM2T26WE.js";
55
52
  import {
56
53
  closeContinuityIncidentRecord,
57
54
  createContinuityIncidentRecord,
@@ -61,10 +58,17 @@ import {
61
58
  serializeContinuityIncident,
62
59
  upsertContinuityLoopInMarkdown
63
60
  } from "./chunk-LDXUBPMO.js";
61
+ import {
62
+ rotateMarkdownFileToArchive
63
+ } from "./chunk-DM2T26WE.js";
64
64
  import {
65
65
  parseFaithfulnessField,
66
66
  serializeFaithfulnessFields
67
67
  } from "./chunk-2OZ2JEUP.js";
68
+ import {
69
+ computeContentHash,
70
+ normalizeContent
71
+ } from "./chunk-W5H6ZRPT.js";
68
72
  import {
69
73
  isConsolidationOperator,
70
74
  isValidDerivedFromEntry
@@ -72,24 +76,20 @@ import {
72
76
  import {
73
77
  createVersion
74
78
  } from "./chunk-VF4XKTX3.js";
75
- import {
76
- computeContentHash,
77
- normalizeContent
78
- } from "./chunk-W5H6ZRPT.js";
79
79
  import {
80
80
  parseProvenanceSources,
81
81
  parseProvenanceTag,
82
82
  reconcileProvenanceRead,
83
83
  serializeProvenanceFields
84
84
  } from "./chunk-OEHCMALU.js";
85
+ import {
86
+ sanitizeMemoryContent
87
+ } from "./chunk-5GKPFL3S.js";
85
88
  import {
86
89
  matchEntitySchemaSection,
87
90
  normalizeEntityStructuredSection,
88
91
  sortStructuredSectionsBySchema
89
92
  } from "./chunk-4DJQYKMN.js";
90
- import {
91
- sanitizeMemoryContent
92
- } from "./chunk-5GKPFL3S.js";
93
93
  import {
94
94
  log
95
95
  } from "./chunk-NU3CSQ4H.js";
@@ -112,10 +112,935 @@ import {
112
112
  } from "./chunk-VS2IYZRU.js";
113
113
 
114
114
  // src/storage.ts
115
- import { access, lstat, readdir, readFile, realpath, stat, writeFile, mkdir, unlink, appendFile, open } from "fs/promises";
116
- import { appendFileSync, createReadStream, mkdirSync, readFileSync, statSync } from "fs";
117
- import { createHash } from "crypto";
118
- import path from "path";
115
+ import { access, readdir as readdir3, readFile, stat as stat2, writeFile, mkdir, unlink as unlink2, appendFile, open } from "fs/promises";
116
+ import { appendFileSync, createReadStream, mkdirSync, readFileSync, statSync } from "fs";
117
+ import { createHash } from "crypto";
118
+ import path3 from "path";
119
+
120
+ // src/storage/memory-read-store.ts
121
+ import { lstat, readdir, realpath, stat } from "fs/promises";
122
+ import path from "path";
123
+ var MemoryReadStore = class {
124
+ constructor(deps) {
125
+ this.deps = deps;
126
+ }
127
+ deps;
128
+ /**
129
+ * List stored transcript days, newest first, optionally scoped to one
130
+ * source. Non-transcript files in the tree are ignored.
131
+ */
132
+ async listWearableTranscriptDays(sourceId) {
133
+ const days = [];
134
+ let sources;
135
+ if (sourceId !== void 0) {
136
+ sources = [sourceId];
137
+ } else {
138
+ try {
139
+ const entries = await readdir(this.deps.wearablesDir, { withFileTypes: true });
140
+ sources = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
141
+ } catch (err) {
142
+ if (err.code === "ENOENT") return [];
143
+ throw err;
144
+ }
145
+ }
146
+ for (const source of sources) {
147
+ if (!/^[a-z][a-z0-9-]{0,63}$/.test(source)) continue;
148
+ let entries;
149
+ try {
150
+ entries = await readdir(path.join(this.deps.wearablesDir, source));
151
+ } catch (err) {
152
+ if (err.code === "ENOENT") continue;
153
+ throw err;
154
+ }
155
+ for (const entry of entries) {
156
+ if (!entry.endsWith(".md")) continue;
157
+ const date = entry.slice(0, -3);
158
+ if (!isValidTranscriptDate(date)) continue;
159
+ days.push({ source, date });
160
+ }
161
+ }
162
+ days.sort((a, b) => {
163
+ if (a.date > b.date) return -1;
164
+ if (a.date < b.date) return 1;
165
+ if (a.source < b.source) return -1;
166
+ if (a.source > b.source) return 1;
167
+ return 0;
168
+ });
169
+ return days;
170
+ }
171
+ async readAllArtifactsCached() {
172
+ if (this.deps.artifactIndexCache && Date.now() - this.deps.artifactIndexCache.loadedAtMs <= this.deps.storageManagerClass.ARTIFACT_INDEX_CACHE_TTL_MS && this.deps.artifactIndexCache.writeVersion === this.deps.getArtifactWriteVersion()) {
173
+ return this.deps.artifactIndexCache.memories;
174
+ }
175
+ const scanArtifacts = async () => {
176
+ const artifacts = [];
177
+ const readDir = async (dir) => {
178
+ try {
179
+ const entries = await readdir(dir, { withFileTypes: true });
180
+ for (const entry of entries) {
181
+ const fullPath = path.join(dir, entry.name);
182
+ if (entry.isDirectory()) {
183
+ await readDir(fullPath);
184
+ continue;
185
+ }
186
+ if (!entry.name.endsWith(".md")) continue;
187
+ const memory = await this.deps.readMemoryByPath(fullPath);
188
+ if (!memory) continue;
189
+ artifacts.push(memory);
190
+ }
191
+ } catch {
192
+ }
193
+ };
194
+ await readDir(this.deps.artifactsDir);
195
+ return artifacts;
196
+ };
197
+ const MAX_REBUILD_RETRIES = 2;
198
+ let latestArtifacts = [];
199
+ for (let attempt = 0; attempt <= MAX_REBUILD_RETRIES; attempt += 1) {
200
+ const versionBefore = this.deps.getArtifactWriteVersion();
201
+ const artifacts = await scanArtifacts();
202
+ const versionAfter = this.deps.getArtifactWriteVersion();
203
+ latestArtifacts = artifacts;
204
+ if (versionAfter === versionBefore) {
205
+ this.deps.artifactIndexCache = { memories: artifacts, loadedAtMs: Date.now(), writeVersion: versionAfter };
206
+ return artifacts;
207
+ }
208
+ }
209
+ this.deps.artifactIndexCache = null;
210
+ return latestArtifacts;
211
+ }
212
+ async collectActiveMemoryPaths() {
213
+ const filePaths = [];
214
+ let memoryRootReal;
215
+ try {
216
+ memoryRootReal = await realpath(this.deps.baseDir);
217
+ } catch {
218
+ return filePaths;
219
+ }
220
+ const collectPaths = async (dir) => {
221
+ let entries;
222
+ try {
223
+ const dirStat = await lstat(dir);
224
+ if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) return;
225
+ assertPathInsideRoot(memoryRootReal, await realpath(dir), dir);
226
+ entries = await readdir(dir, { withFileTypes: true });
227
+ } catch {
228
+ return;
229
+ }
230
+ const subdirs = [];
231
+ for (const entry of entries) {
232
+ if (entry.isSymbolicLink()) continue;
233
+ const fullPath = path.join(dir, entry.name);
234
+ if (entry.isDirectory()) {
235
+ subdirs.push(fullPath);
236
+ } else if (entry.name.endsWith(".md")) {
237
+ try {
238
+ assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
239
+ filePaths.push(fullPath);
240
+ } catch {
241
+ }
242
+ }
243
+ }
244
+ for (const subdir of subdirs) {
245
+ await collectPaths(subdir);
246
+ }
247
+ };
248
+ for (const dir of RECALL_FALLBACK_DIRS) {
249
+ await collectPaths(path.join(this.deps.baseDir, dir));
250
+ }
251
+ return filePaths;
252
+ }
253
+ async readMemoriesWindow(options = {}) {
254
+ const allPaths = await this.deps.collectActiveMemoryPaths();
255
+ const sortedPaths = this.deps.orderWindowPaths(allPaths);
256
+ const maxMemories = typeof options.maxMemories === "number" && Number.isFinite(options.maxMemories) ? Math.max(1, Math.floor(options.maxMemories)) : void 0;
257
+ const maxCandidatePaths = maxMemories === void 0 ? void 0 : maxMemories * 2;
258
+ const updatedAfterMs = options.updatedAfter?.getTime();
259
+ const normalizedBatchSize = this.deps.normalizeMemoryReadBatchSize(options.batchSize);
260
+ const memories = [];
261
+ const selectedPaths = [];
262
+ for (let i = 0; i < sortedPaths.length; i += normalizedBatchSize) {
263
+ if (maxMemories !== void 0 && (memories.length >= maxMemories || maxCandidatePaths !== void 0 && selectedPaths.length >= maxCandidatePaths)) {
264
+ return { memories, filePaths: selectedPaths };
265
+ }
266
+ const batchPaths = sortedPaths.slice(i, i + normalizedBatchSize);
267
+ const candidateBatchPaths = updatedAfterMs === void 0 ? batchPaths : await this.deps.filterWindowPathsByUpdatedAfter(batchPaths, updatedAfterMs);
268
+ const remainingSlots = maxMemories === void 0 ? void 0 : Math.max(0, maxMemories - memories.length);
269
+ const remainingInspectionBudget = maxCandidatePaths === void 0 ? void 0 : Math.max(0, maxCandidatePaths - selectedPaths.length);
270
+ const { memories: batchMemories, filePaths: parsedCandidatePaths } = remainingSlots === void 0 ? {
271
+ memories: await this.deps.readParsedMemoriesFromPaths(candidateBatchPaths, normalizedBatchSize),
272
+ filePaths: candidateBatchPaths
273
+ } : await this.deps.readWindowBoundedBatch(
274
+ candidateBatchPaths,
275
+ remainingSlots,
276
+ remainingInspectionBudget ?? remainingSlots,
277
+ normalizedBatchSize
278
+ );
279
+ selectedPaths.push(...parsedCandidatePaths);
280
+ for (const memory of batchMemories) {
281
+ memories.push(memory);
282
+ if (maxMemories !== void 0 && memories.length >= maxMemories) {
283
+ return { memories, filePaths: selectedPaths };
284
+ }
285
+ }
286
+ }
287
+ return { memories, filePaths: selectedPaths };
288
+ }
289
+ /**
290
+ * Read all memories from the cold tier by scanning the entire cold/ root
291
+ * tree. Previously this only scanned cold/facts/ and cold/corrections/, but
292
+ * structuredAttributes can appear on any MemoryCategory (preference, decision,
293
+ * entity, etc.). buildTierMemoryPath now routes each category to its own
294
+ * cold/<dir>/ subtree via the shared categoryDirName() chokepoint (issue
295
+ * #1546), so cold decisions/preferences/... live outside cold/facts/.
296
+ * Scanning the full coldRoot covers every category dir and guards against
297
+ * files placed in unexpected subdirectories during manual operations or future
298
+ * refactors.
299
+ *
300
+ * Broadened in PR #402 round-6 (Finding UTsP): scanning only facts/ and
301
+ * corrections/ was a narrower-than-necessary subset of the cold directory
302
+ * tree. Correctness trumps the minor performance difference — cold scans
303
+ * already happen at most once per supersession write.
304
+ *
305
+ * Used by applyTemporalSupersession so that memories already demoted to
306
+ * cold/ can still be marked superseded when a newer hot fact arrives.
307
+ *
308
+ * Cached with a TTL (Finding UOGi, PR #402 round-6): back-to-back
309
+ * structured-attribute writes in the same burst reuse the cached result
310
+ * instead of re-scanning the cold tree on every call. The cache is
311
+ * invalidated whenever a write calls invalidateAllMemoriesCache() (which
312
+ * covers any hot→cold demotion that changes cold-tier contents) and
313
+ * expires after COLD_SCAN_CACHE_TTL_MS as a safety net.
314
+ */
315
+ async readAllColdMemories() {
316
+ const coldRoot = this.deps.resolveTierRootDir("cold");
317
+ const currentColdVersion = this.deps.readColdWriteVersion();
318
+ const cached = this.deps.storageManagerClass.coldMemoriesCache.get(coldRoot);
319
+ if (cached && Date.now() - cached.loadedAt < this.deps.storageManagerClass.COLD_SCAN_CACHE_TTL_MS && cached.coldVersion === currentColdVersion) {
320
+ return cached.memories;
321
+ }
322
+ const filePaths = [];
323
+ const collectPaths = async (dir) => {
324
+ try {
325
+ const entries = await readdir(dir, { withFileTypes: true });
326
+ const subdirs = [];
327
+ for (const entry of entries) {
328
+ const fullPath = path.join(dir, entry.name);
329
+ if (entry.isDirectory()) {
330
+ subdirs.push(fullPath);
331
+ } else if (entry.name.endsWith(".md")) {
332
+ filePaths.push(fullPath);
333
+ }
334
+ }
335
+ for (const subdir of subdirs) {
336
+ await collectPaths(subdir);
337
+ }
338
+ } catch {
339
+ }
340
+ };
341
+ await collectPaths(coldRoot);
342
+ const memories = await this.deps.readParsedMemoriesFromPaths(filePaths, 50);
343
+ this.deps.storageManagerClass.coldMemoriesCache.set(coldRoot, { memories, loadedAt: Date.now(), coldVersion: currentColdVersion });
344
+ return memories;
345
+ }
346
+ /** Read a single memory file by its absolute path. Returns null if unreadable. */
347
+ async readMemoryByPath(filePath) {
348
+ try {
349
+ const raw = await readMaybeEncryptedFile(filePath, this.deps._secureStoreKey, this.deps.baseDir);
350
+ const parsed = parseFrontmatter(raw);
351
+ if (parsed) {
352
+ return {
353
+ path: filePath,
354
+ frontmatter: normalizeFrontmatterForPath(
355
+ parsed.frontmatter,
356
+ toMemoryPathRel(this.deps.baseDir, filePath),
357
+ parsed.content
358
+ ),
359
+ content: parsed.content
360
+ };
361
+ }
362
+ const normalizedPath = filePath.split(path.sep).join("/");
363
+ if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
364
+ const entity = parseEntityFile(raw, this.deps.entitySchemas);
365
+ if (!entity.name) return null;
366
+ const nameWithoutExt = path.basename(filePath, ".md");
367
+ const fileMtime = entity.updated || await stat(filePath).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
368
+ return {
369
+ path: filePath,
370
+ frontmatter: {
371
+ id: nameWithoutExt,
372
+ category: "entity",
373
+ created: fileMtime,
374
+ updated: fileMtime,
375
+ source: "entity_extraction",
376
+ confidence: 0.9,
377
+ confidenceTier: confidenceTier(0.9),
378
+ tags: entity.type ? [entity.type] : []
379
+ },
380
+ content: raw
381
+ };
382
+ }
383
+ return null;
384
+ } catch (err) {
385
+ if (err instanceof SecureStoreLockedError) throw err;
386
+ return null;
387
+ }
388
+ }
389
+ /**
390
+ * Append a batch of `BUFFER_SURPRISE` telemetry events (issue #563 PR 3).
391
+ *
392
+ * Each event records a single buffer flush decision driven by the
393
+ * surprise gate. The ledger is consumed by
394
+ * `reportBufferSurpriseDistribution` (Doctor report) and by downstream
395
+ * benchmark analysis. This method is fire-and-forget by contract:
396
+ * callers log but do not fail the hot path if the append throws.
397
+ */
398
+ async appendBufferSurpriseEvents(events) {
399
+ if (events.length === 0) return 0;
400
+ await this.deps.ensureDirectories();
401
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
402
+ const payload = events.map((event) => {
403
+ const normalized = {
404
+ ...event,
405
+ event: "BUFFER_SURPRISE",
406
+ timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
407
+ };
408
+ return `${JSON.stringify(normalized)}
409
+ `;
410
+ }).join("");
411
+ await this.deps.appendStorageSecureFile(this.deps.bufferSurpriseLedgerPath, payload);
412
+ return events.length;
413
+ }
414
+ /**
415
+ * Read the buffer-surprise ledger, most recent rows last.
416
+ *
417
+ * `limit` bounds the number of **valid rows** returned (not the
418
+ * number of raw lines parsed). We parse every row, discard malformed
419
+ * ones, then take the tail — so a partial/truncated trailing line
420
+ * (the common failure mode after an interrupted append) cannot hide
421
+ * otherwise-valid recent data above it.
422
+ *
423
+ * Non-positive / non-integer / non-finite limits return `[]` rather
424
+ * than the entire file, matching the other ledger readers in this
425
+ * class and protecting against `slice(-0.5)` → `slice(-0)` silently
426
+ * devolving into an unbounded parse.
427
+ *
428
+ * # Performance note
429
+ *
430
+ * For very large ledgers (issue #563 follow-up), a tail-first reader
431
+ * would avoid parsing the full file when only a recent window is
432
+ * needed. We keep the full-scan implementation here because:
433
+ *
434
+ * - the ledger is opt-in (flag off by default), so early deployments
435
+ * accumulate rows slowly;
436
+ * - telemetry rows are small (~200 bytes), so even 100k rows parse
437
+ * in well under a second;
438
+ * - the governance archive/cleanup flow can trim the ledger when
439
+ * size becomes a concern, reusing the existing maintenance hooks.
440
+ *
441
+ * Swap to a chunked tail-reader if production logs show this is a
442
+ * hot path — leaving that work for a follow-up keeps this PR scoped
443
+ * to correctness, not optimization.
444
+ */
445
+ async readBufferSurpriseEvents(options = {}) {
446
+ let raw;
447
+ try {
448
+ raw = await this.deps.readStorageSecureFile(this.deps.bufferSurpriseLedgerPath);
449
+ } catch (err) {
450
+ if (err instanceof SecureStoreLockedError) throw err;
451
+ const code = err.code;
452
+ if (code === "ENOENT") return [];
453
+ throw err;
454
+ }
455
+ let effectiveLimit = null;
456
+ if (options.limit !== void 0) {
457
+ if (typeof options.limit !== "number" || !Number.isFinite(options.limit) || options.limit <= 0) {
458
+ return [];
459
+ }
460
+ const floored = Math.floor(options.limit);
461
+ if (floored <= 0) return [];
462
+ effectiveLimit = floored;
463
+ }
464
+ const lines = raw.split("\n");
465
+ const events = [];
466
+ for (const line of lines) {
467
+ const trimmed = line.trim();
468
+ if (trimmed.length === 0) continue;
469
+ try {
470
+ const parsed = JSON.parse(trimmed);
471
+ if (isValidBufferSurpriseEvent(parsed)) {
472
+ events.push(parsed);
473
+ }
474
+ } catch {
475
+ }
476
+ }
477
+ events.sort(
478
+ (left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp)
479
+ );
480
+ if (effectiveLimit === null) return events;
481
+ return events.slice(-effectiveLimit);
482
+ }
483
+ async readCompressionGuidelineStateFile(filePath) {
484
+ const isFiniteNonNegativeInteger = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
485
+ const isValidActionSummary = (value) => {
486
+ if (!value || typeof value !== "object") return false;
487
+ const summary = value;
488
+ 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);
489
+ };
490
+ const isValidRuleUpdate = (value) => {
491
+ if (!value || typeof value !== "object") return false;
492
+ const rule = value;
493
+ 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");
494
+ };
495
+ try {
496
+ const raw = await this.deps.readStorageSecureFile(filePath);
497
+ const parsed = JSON.parse(raw);
498
+ const sourceWindow = parsed?.sourceWindow;
499
+ const eventCounts = parsed?.eventCounts;
500
+ const activationState = parsed?.activationState === "draft" || parsed?.activationState === "active" ? parsed.activationState : void 0;
501
+ const contentHash = typeof parsed?.contentHash === "string" && parsed.contentHash.length > 0 ? parsed.contentHash : void 0;
502
+ const actionSummaries = Array.isArray(parsed?.actionSummaries) ? parsed.actionSummaries.filter(isValidActionSummary) : void 0;
503
+ const ruleUpdates = Array.isArray(parsed?.ruleUpdates) ? parsed.ruleUpdates.filter(isValidRuleUpdate) : void 0;
504
+ 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)) {
505
+ return null;
506
+ }
507
+ return {
508
+ version: parsed.version,
509
+ updatedAt: parsed.updatedAt,
510
+ sourceWindow: {
511
+ from: sourceWindow.from,
512
+ to: sourceWindow.to
513
+ },
514
+ eventCounts: {
515
+ total: eventCounts.total,
516
+ applied: eventCounts.applied,
517
+ skipped: eventCounts.skipped,
518
+ failed: eventCounts.failed
519
+ },
520
+ guidelineVersion: parsed.guidelineVersion,
521
+ ...contentHash ? { contentHash } : {},
522
+ ...activationState ? { activationState } : {},
523
+ ...actionSummaries ? { actionSummaries } : {},
524
+ ...ruleUpdates ? { ruleUpdates } : {}
525
+ };
526
+ } catch (err) {
527
+ if (err instanceof SecureStoreLockedError) throw err;
528
+ if (!isErrnoCode(err, "ENOENT")) throw err;
529
+ return null;
530
+ }
531
+ }
532
+ async readQuestions(opts) {
533
+ const cacheKey = this.deps.questionsDir;
534
+ const cached = this.deps.storageManagerClass.questionsCache.get(cacheKey);
535
+ if (cached && Date.now() - cached.loadedAt < this.deps.storageManagerClass.QUESTIONS_CACHE_TTL_MS) {
536
+ try {
537
+ const dirStat = await stat(this.deps.questionsDir);
538
+ if (dirStat.mtimeMs <= cached.loadedAt) {
539
+ const all = cached.questions;
540
+ return opts?.unresolvedOnly ? all.filter((q) => !q.resolved) : all;
541
+ }
542
+ } catch {
543
+ }
544
+ }
545
+ try {
546
+ const files = await readdir(this.deps.questionsDir);
547
+ const questions = [];
548
+ for (const file of files) {
549
+ if (!file.endsWith(".md")) continue;
550
+ const filePath = path.join(this.deps.questionsDir, file);
551
+ const raw = await readMaybeEncryptedFile(filePath, this.deps._secureStoreKey, this.deps.baseDir);
552
+ const parsed = this.deps.parseQuestionFile(raw, filePath);
553
+ if (parsed) {
554
+ questions.push(parsed);
555
+ }
556
+ }
557
+ const sorted = questions.sort((a, b) => b.priority - a.priority);
558
+ this.deps.storageManagerClass.questionsCache.set(cacheKey, { questions: sorted, loadedAt: Date.now() });
559
+ return opts?.unresolvedOnly ? sorted.filter((q) => !q.resolved) : sorted;
560
+ } catch {
561
+ return [];
562
+ }
563
+ }
564
+ };
565
+
566
+ // src/orchestration/self-deps.ts
567
+ function selfDeps(self) {
568
+ const source = self;
569
+ return new Proxy(/* @__PURE__ */ Object.create(null), {
570
+ get(_target, prop) {
571
+ const value = source[prop];
572
+ return typeof value === "function" && !Object.prototype.hasOwnProperty.call(value, "prototype") ? value.bind(self) : value;
573
+ },
574
+ set(_target, prop, value) {
575
+ if (!(prop in source)) return false;
576
+ source[prop] = value;
577
+ return true;
578
+ },
579
+ has(_target, prop) {
580
+ return prop in source;
581
+ }
582
+ });
583
+ }
584
+
585
+ // src/storage/entity-store.ts
586
+ import { readdir as readdir2, unlink } from "fs/promises";
587
+ import path2 from "path";
588
+ var EntityStore = class {
589
+ constructor(deps) {
590
+ this.deps = deps;
591
+ }
592
+ deps;
593
+ /** Normalize an entity name using this store's alias table. */
594
+ normalizeEntityName(raw, type) {
595
+ return normalizeEntityName(raw, type, this.deps.userAliases);
596
+ }
597
+ async writeEntity(name, type, facts, options = {}) {
598
+ await this.deps.ensureDirectories();
599
+ if (typeof name !== "string" || !name.trim() || typeof type !== "string" || !type.trim()) {
600
+ log.warn("writeEntity: invalid entity payload, skipping", {
601
+ nameType: typeof name,
602
+ typeType: typeof type
603
+ });
604
+ return "";
605
+ }
606
+ const safeFacts = Array.isArray(facts) ? [...new Set(
607
+ facts.filter((fact) => typeof fact === "string").map((fact) => fact.trim()).filter((fact) => fact.length > 0)
608
+ )] : [];
609
+ let normalized = this.deps.normalizeEntityName(name, type);
610
+ const match = await this.deps.findMatchingEntity(name, type);
611
+ if (match && match !== normalized) {
612
+ log.debug(`fuzzy match: "${normalized}" \u2192 existing "${match}"`);
613
+ normalized = match;
614
+ }
615
+ const filePath = path2.join(this.deps.entitiesDir, `${normalized}.md`);
616
+ let entity = {
617
+ name,
618
+ type,
619
+ created: "",
620
+ updated: (/* @__PURE__ */ new Date()).toISOString(),
621
+ facts: [],
622
+ summary: void 0,
623
+ synthesis: void 0,
624
+ synthesisUpdatedAt: void 0,
625
+ synthesisVersion: void 0,
626
+ synthesisStructuredFactCount: void 0,
627
+ synthesisStructuredFactDigest: void 0,
628
+ timeline: [],
629
+ relationships: [],
630
+ activity: [],
631
+ aliases: []
632
+ };
633
+ try {
634
+ const existing = await this.deps.readStorageSecureFile(filePath);
635
+ entity = parseEntityFile(existing, this.deps.entitySchemas);
636
+ } catch (err) {
637
+ if (err instanceof SecureStoreLockedError) throw err;
638
+ if (!isErrnoCode(err, "ENOENT")) throw err;
639
+ }
640
+ const timestamp = options.timestamp?.trim() || (/* @__PURE__ */ new Date()).toISOString();
641
+ const source = options.source?.trim() || void 0;
642
+ const sessionKey = options.sessionKey?.trim() || void 0;
643
+ const principal = options.principal?.trim() || void 0;
644
+ const structuredSectionMap = new Map(
645
+ (entity.structuredSections ?? []).map((section) => [section.key, {
646
+ ...section,
647
+ facts: [...section.facts]
648
+ }])
649
+ );
650
+ for (const section of options.structuredSections ?? []) {
651
+ const normalizedSection = normalizeEntityStructuredSection(type, section, this.deps.entitySchemas);
652
+ const normalizedFacts = normalizeStructuredSectionFacts(section.facts);
653
+ if (normalizedFacts.length === 0) continue;
654
+ const existingSection = structuredSectionMap.get(normalizedSection.key);
655
+ if (!existingSection) {
656
+ structuredSectionMap.set(normalizedSection.key, {
657
+ key: normalizedSection.key,
658
+ title: normalizedSection.title,
659
+ facts: normalizedFacts
660
+ });
661
+ continue;
662
+ }
663
+ existingSection.facts = normalizeStructuredSectionFacts([...existingSection.facts, ...normalizedFacts]);
664
+ if (!existingSection.title.trim() && normalizedSection.title.trim()) {
665
+ existingSection.title = normalizedSection.title;
666
+ }
667
+ }
668
+ for (const fact of safeFacts) {
669
+ const nextEntry = {
670
+ timestamp,
671
+ text: fact,
672
+ ...source ? { source } : {},
673
+ ...sessionKey ? { sessionKey } : {},
674
+ ...principal ? { principal } : {}
675
+ };
676
+ const alreadyPresent = entity.timeline.some(
677
+ (entry) => entry.timestamp === nextEntry.timestamp && entry.text === nextEntry.text && entry.source === nextEntry.source && entry.sessionKey === nextEntry.sessionKey && entry.principal === nextEntry.principal
678
+ );
679
+ if (alreadyPresent) continue;
680
+ entity.timeline.push(nextEntry);
681
+ }
682
+ entity.structuredSections = sortStructuredSectionsBySchema(
683
+ type,
684
+ Array.from(structuredSectionMap.values()).filter((section) => section.facts.length > 0),
685
+ this.deps.entitySchemas
686
+ );
687
+ entity.facts = compileEntityFacts(entity.timeline, entity.structuredSections);
688
+ entity.summary = entity.synthesis || entity.summary;
689
+ entity.name = name;
690
+ entity.type = type;
691
+ entity.created = entity.created || timestamp;
692
+ entity.updated = (/* @__PURE__ */ new Date()).toISOString();
693
+ await this.deps.snapshotBeforeWrite(filePath, "write");
694
+ await this.deps.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.deps.entitySchemas));
695
+ this.deps.invalidateKnowledgeIndexCache();
696
+ this.deps.bumpMemoryStatusVersion();
697
+ log.debug(`wrote entity ${normalized}`);
698
+ return normalized;
699
+ }
700
+ async readEntity(name) {
701
+ const filePath = this.deps.resolveEntityFilePath(name);
702
+ if (filePath === null) {
703
+ return "";
704
+ }
705
+ try {
706
+ return await this.deps.readStorageSecureFile(filePath);
707
+ } catch (err) {
708
+ if (err instanceof SecureStoreLockedError) throw err;
709
+ if (!isErrnoCode(err, "ENOENT")) throw err;
710
+ return "";
711
+ }
712
+ }
713
+ /** Return sorted list of entity filenames (without .md extension) */
714
+ async listEntityNames() {
715
+ try {
716
+ const entries = await readdir2(this.deps.entitiesDir);
717
+ return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", "")).sort();
718
+ } catch {
719
+ return [];
720
+ }
721
+ }
722
+ /**
723
+ * Set or rewrite the synthesis layer of an entity file.
724
+ */
725
+ async updateEntitySynthesis(name, synthesis, options = {}) {
726
+ const filePath = path2.join(this.deps.entitiesDir, `${name}.md`);
727
+ let entity;
728
+ try {
729
+ const content = await this.deps.readStorageSecureFile(filePath);
730
+ entity = parseEntityFile(content, this.deps.entitySchemas);
731
+ } catch (err) {
732
+ if (err instanceof SecureStoreLockedError) throw err;
733
+ if (!isErrnoCode(err, "ENOENT")) throw err;
734
+ log.debug(`updateEntitySynthesis: entity file ${name}.md not found`);
735
+ return;
736
+ }
737
+ const updatedAt = options.updatedAt?.trim() || entity.synthesisUpdatedAt?.trim() || void 0;
738
+ const entityUpdatedAt = options.entityUpdatedAt?.trim() || updatedAt || entity.updated || (/* @__PURE__ */ new Date()).toISOString();
739
+ const synthesisTimelineCount = Number.isInteger(options.synthesisTimelineCount) && (options.synthesisTimelineCount ?? 0) >= 0 ? options.synthesisTimelineCount : void 0;
740
+ const synthesisStructuredFactCount = Number.isInteger(options.synthesisStructuredFactCount) && (options.synthesisStructuredFactCount ?? 0) >= 0 ? options.synthesisStructuredFactCount : countEntityStructuredFacts(entity);
741
+ const synthesisStructuredFactDigest = options.synthesisStructuredFactDigest?.trim() || fingerprintEntityStructuredFacts(entity);
742
+ entity.synthesis = synthesis.trim();
743
+ entity.summary = entity.synthesis;
744
+ entity.synthesisUpdatedAt = updatedAt;
745
+ entity.synthesisTimelineCount = synthesisTimelineCount;
746
+ entity.synthesisStructuredFactCount = synthesisStructuredFactCount;
747
+ entity.synthesisStructuredFactDigest = synthesisStructuredFactDigest;
748
+ entity.synthesisVersion = Math.max(0, entity.synthesisVersion ?? 0) + (options.incrementVersion === false ? 0 : 1);
749
+ entity.updated = entityUpdatedAt;
750
+ await this.deps.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.deps.entitySchemas));
751
+ await this.deps.removeEntitySynthesisQueueEntries([
752
+ .../* @__PURE__ */ new Set([name, this.deps.normalizeEntityName(entity.name, entity.type)])
753
+ ]);
754
+ this.deps.invalidateKnowledgeIndexCache();
755
+ this.deps.bumpMemoryStatusVersion();
756
+ }
757
+ /**
758
+ * Read all entity files and return lightweight EntityFile objects.
759
+ * Parsing is fast (~50-100ms for ~1,800 files) since entity files are small.
760
+ */
761
+ async readAllEntityFiles() {
762
+ const currentVersion = this.deps.getMemoryStatusVersion();
763
+ const schemaCacheKey = buildEntitySchemaCacheKey(this.deps.entitySchemas);
764
+ const cacheKey = `${this.deps.getEntityCacheSecureStoreKey()}\0${schemaCacheKey}`;
765
+ const cached = getCachedEntities(this.deps.baseDir, currentVersion, cacheKey);
766
+ if (cached) return cached;
767
+ try {
768
+ const entries = await readdir2(this.deps.entitiesDir);
769
+ const mdFiles = entries.filter((e) => e.endsWith(".md"));
770
+ if (mdFiles.length === 0) return [];
771
+ const BATCH_SIZE = 100;
772
+ const entities = [];
773
+ for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {
774
+ const batch = mdFiles.slice(i, i + BATCH_SIZE);
775
+ const results = await Promise.all(
776
+ batch.map(async (entry) => {
777
+ try {
778
+ return await this.deps.readStorageSecureFile(path2.join(this.deps.entitiesDir, entry));
779
+ } catch (err) {
780
+ if (err instanceof SecureStoreLockedError) throw err;
781
+ if (!isErrnoCode(err, "ENOENT")) throw err;
782
+ return null;
783
+ }
784
+ })
785
+ );
786
+ for (const content of results) {
787
+ if (content !== null) entities.push(parseEntityFile(content, this.deps.entitySchemas));
788
+ }
789
+ }
790
+ setCachedEntities(this.deps.baseDir, entities, currentVersion, cacheKey);
791
+ return entities;
792
+ } catch (err) {
793
+ if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
794
+ return [];
795
+ }
796
+ }
797
+ /**
798
+ * Build the Knowledge Index: a compact markdown table of top-scored entities.
799
+ * Respects maxEntities and maxChars limits from config.
800
+ */
801
+ async buildKnowledgeIndex(config, overrides) {
802
+ const useDefaultLimits = overrides?.maxEntities === void 0 && overrides?.maxChars === void 0;
803
+ if (useDefaultLimits && this.deps.knowledgeIndexCache && Date.now() - this.deps.knowledgeIndexCache.builtAt < this.deps.storageManagerClass.KNOWLEDGE_INDEX_CACHE_TTL_MS) {
804
+ return { result: this.deps.knowledgeIndexCache.result, cached: true };
805
+ }
806
+ const entities = await this.deps.readAllEntityFiles();
807
+ if (entities.length === 0) {
808
+ if (useDefaultLimits) this.deps.knowledgeIndexCache = { result: "", builtAt: Date.now() };
809
+ return { result: "", cached: false };
810
+ }
811
+ const now = /* @__PURE__ */ new Date();
812
+ const scored = entities.map((e) => ({
813
+ name: e.name,
814
+ type: e.type,
815
+ score: this.deps.storageManagerClass.scoreEntity(e, now),
816
+ factCount: e.facts.length,
817
+ summary: e.synthesis ?? e.summary,
818
+ topRelationships: e.relationships.slice(0, 3).map((r) => r.target)
819
+ }));
820
+ scored.sort((a, b) => b.score - a.score);
821
+ const maxEntities = typeof overrides?.maxEntities === "number" ? Math.max(0, Math.floor(overrides.maxEntities)) : config.knowledgeIndexMaxEntities;
822
+ const topN = scored.slice(0, maxEntities);
823
+ if (topN.length === 0) {
824
+ if (useDefaultLimits) this.deps.knowledgeIndexCache = { result: "", builtAt: Date.now() };
825
+ return { result: "", cached: false };
826
+ }
827
+ const header = "## Knowledge Index\n\n| Entity | Type | Summary | Connected to |\n|--------|------|---------|-------------|";
828
+ const rows = [];
829
+ let totalChars = header.length;
830
+ const maxChars = typeof overrides?.maxChars === "number" ? Math.max(0, Math.floor(overrides.maxChars)) : config.knowledgeIndexMaxChars;
831
+ for (const entity of topN) {
832
+ const summary = entity.summary || `${entity.factCount} facts`;
833
+ const connected = entity.topRelationships.length > 0 ? entity.topRelationships.join(", ") : "\u2014";
834
+ const row = `| ${entity.name} | ${entity.type} | ${summary} | ${connected} |`;
835
+ if (totalChars + row.length + 1 > maxChars) break;
836
+ rows.push(row);
837
+ totalChars += row.length + 1;
838
+ }
839
+ const result = rows.length === 0 ? "" : `${header}
840
+ ${rows.join("\n")}
841
+ `;
842
+ if (useDefaultLimits) this.deps.knowledgeIndexCache = { result, builtAt: Date.now() };
843
+ return { result, cached: false };
844
+ }
845
+ /**
846
+ * Merge fragmented entity files that resolve to the same canonical name.
847
+ * Preserves relationships, activity, aliases, and summary from all fragments.
848
+ * Returns count of files merged.
849
+ */
850
+ async mergeFragmentedEntities() {
851
+ let merged = 0;
852
+ try {
853
+ const entries = await readdir2(this.deps.entitiesDir);
854
+ const mdFiles = entries.filter((e) => e.endsWith(".md"));
855
+ const groups = /* @__PURE__ */ new Map();
856
+ for (const file of mdFiles) {
857
+ const baseName = file.replace(".md", "");
858
+ const dashIdx = baseName.indexOf("-");
859
+ if (dashIdx === -1) continue;
860
+ const type = baseName.slice(0, dashIdx);
861
+ const restOfName = baseName.slice(dashIdx + 1);
862
+ const canonical = this.deps.normalizeEntityName(restOfName, type);
863
+ if (!groups.has(canonical)) groups.set(canonical, []);
864
+ groups.get(canonical).push(file);
865
+ }
866
+ for (const [canonical, files] of groups) {
867
+ if (files.length <= 1) continue;
868
+ const mergedEntity = {
869
+ name: "",
870
+ type: "other",
871
+ created: "",
872
+ updated: "",
873
+ extraFrontmatterLines: [],
874
+ preSectionLines: [],
875
+ facts: [],
876
+ summary: void 0,
877
+ synthesis: void 0,
878
+ synthesisUpdatedAt: void 0,
879
+ synthesisTimelineCount: void 0,
880
+ synthesisStructuredFactCount: void 0,
881
+ synthesisStructuredFactDigest: void 0,
882
+ synthesisVersion: void 0,
883
+ timeline: [],
884
+ relationships: [],
885
+ activity: [],
886
+ aliases: [],
887
+ structuredSections: [],
888
+ extraSections: []
889
+ };
890
+ for (const file of files) {
891
+ const filePath = path2.join(this.deps.entitiesDir, file);
892
+ try {
893
+ const content = await this.deps.readStorageSecureFile(filePath);
894
+ const parsed = parseEntityFile(content, this.deps.entitySchemas);
895
+ if (!mergedEntity.type || mergedEntity.type === "other") {
896
+ mergedEntity.type = parsed.type;
897
+ }
898
+ if (!mergedEntity.updated || compareEntityTimestamps(parsed.updated, mergedEntity.updated) > 0) {
899
+ mergedEntity.updated = parsed.updated;
900
+ }
901
+ const parsedCreated = parsed.created || parsed.updated;
902
+ const mergedCreated = mergedEntity.created?.trim() || "";
903
+ const parsedCreatedMs = parsedCreated ? Date.parse(parsedCreated) : Number.NaN;
904
+ const mergedCreatedMs = mergedCreated ? Date.parse(mergedCreated) : Number.NaN;
905
+ const parsedCreatedIsValid = Number.isFinite(parsedCreatedMs);
906
+ const mergedCreatedIsValid = Number.isFinite(mergedCreatedMs);
907
+ if (parsedCreated && (!mergedCreated || parsedCreatedIsValid && !mergedCreatedIsValid || parsedCreatedIsValid && mergedCreatedIsValid && parsedCreatedMs < mergedCreatedMs || !parsedCreatedIsValid && !mergedCreatedIsValid && compareEntityTimestamps(parsedCreated, mergedCreated) < 0)) {
908
+ mergedEntity.created = parsedCreated;
909
+ }
910
+ if (parsed.name.length > mergedEntity.name.length) {
911
+ mergedEntity.name = parsed.name;
912
+ }
913
+ const parsedSynthesisUpdatedAt = parsed.synthesisUpdatedAt?.trim() || void 0;
914
+ const mergedSynthesisUpdatedAt = mergedEntity.synthesisUpdatedAt?.trim() || void 0;
915
+ if (parsed.synthesis && (!mergedEntity.synthesis || !mergedSynthesisUpdatedAt && Boolean(parsedSynthesisUpdatedAt) || Boolean(mergedSynthesisUpdatedAt) && Boolean(parsedSynthesisUpdatedAt) && compareEntityTimestamps(parsedSynthesisUpdatedAt, mergedSynthesisUpdatedAt) > 0)) {
916
+ mergedEntity.synthesis = parsed.synthesis;
917
+ mergedEntity.summary = parsed.synthesis;
918
+ mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
919
+ mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
920
+ mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
921
+ mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
922
+ mergedEntity.synthesisVersion = parsed.synthesisVersion;
923
+ } else if (!mergedEntity.summary && parsed.summary) {
924
+ mergedEntity.summary = parsed.summary;
925
+ mergedEntity.synthesis = parsed.summary;
926
+ mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
927
+ mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
928
+ mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
929
+ mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
930
+ mergedEntity.synthesisVersion = parsed.synthesisVersion;
931
+ }
932
+ mergedEntity.timeline.push(...parsed.timeline);
933
+ mergedEntity.relationships.push(...parsed.relationships);
934
+ mergedEntity.activity.push(...parsed.activity);
935
+ mergedEntity.aliases.push(...parsed.aliases);
936
+ const mergedStructuredSectionMap = new Map(
937
+ (mergedEntity.structuredSections ?? []).map((section) => [section.key, {
938
+ ...section,
939
+ facts: [...section.facts]
940
+ }])
941
+ );
942
+ for (const section of parsed.structuredSections ?? []) {
943
+ const existingSection = mergedStructuredSectionMap.get(section.key);
944
+ if (!existingSection) {
945
+ mergedStructuredSectionMap.set(section.key, {
946
+ key: section.key,
947
+ title: section.title,
948
+ facts: [...new Set(section.facts.map((fact) => fact.trim()).filter((fact) => fact.length > 0))]
949
+ });
950
+ continue;
951
+ }
952
+ const mergedFacts = new Set(existingSection.facts.map((fact) => fact.trim()));
953
+ for (const fact of section.facts) {
954
+ const trimmed = fact.trim();
955
+ if (!trimmed) continue;
956
+ mergedFacts.add(trimmed);
957
+ }
958
+ existingSection.facts = Array.from(mergedFacts);
959
+ if (!existingSection.title.trim() && section.title.trim()) {
960
+ existingSection.title = section.title;
961
+ }
962
+ }
963
+ mergedEntity.structuredSections = Array.from(mergedStructuredSectionMap.values());
964
+ mergedEntity.extraFrontmatterLines.push(...parsed.extraFrontmatterLines ?? []);
965
+ mergedEntity.preSectionLines.push(...parsed.preSectionLines ?? []);
966
+ mergedEntity.extraSections.push(...(parsed.extraSections ?? []).map((section) => ({
967
+ title: section.title,
968
+ lines: [...section.lines]
969
+ })));
970
+ } catch (err) {
971
+ if (err instanceof SecureStoreLockedError) throw err;
972
+ if (!isErrnoCode(err, "ENOENT")) throw err;
973
+ }
974
+ }
975
+ const timelineKeys = /* @__PURE__ */ new Set();
976
+ mergedEntity.timeline = mergedEntity.timeline.filter((entry) => {
977
+ const key = JSON.stringify([
978
+ entry.timestamp,
979
+ entry.source ?? "",
980
+ entry.sessionKey ?? "",
981
+ entry.principal ?? "",
982
+ entry.text
983
+ ]);
984
+ if (timelineKeys.has(key)) return false;
985
+ timelineKeys.add(key);
986
+ return true;
987
+ });
988
+ const relKeys = /* @__PURE__ */ new Set();
989
+ mergedEntity.relationships = mergedEntity.relationships.filter((r) => {
990
+ const key = `${r.target}::${r.label}`;
991
+ if (relKeys.has(key)) return false;
992
+ relKeys.add(key);
993
+ return true;
994
+ });
995
+ const actKeys = /* @__PURE__ */ new Set();
996
+ mergedEntity.activity = mergedEntity.activity.filter((a) => {
997
+ const key = `${a.date}::${a.note}`;
998
+ if (actKeys.has(key)) return false;
999
+ actKeys.add(key);
1000
+ return true;
1001
+ }).sort((a, b) => b.date.localeCompare(a.date));
1002
+ mergedEntity.aliases = [...new Set(mergedEntity.aliases)];
1003
+ mergedEntity.structuredSections = sortStructuredSectionsBySchema(
1004
+ mergedEntity.type,
1005
+ mergedEntity.structuredSections ?? [],
1006
+ this.deps.entitySchemas
1007
+ );
1008
+ mergedEntity.facts = compileEntityFacts(mergedEntity.timeline, mergedEntity.structuredSections);
1009
+ const extraSectionKeys = /* @__PURE__ */ new Set();
1010
+ mergedEntity.extraSections = (mergedEntity.extraSections ?? []).filter((section) => {
1011
+ const key = `${section.title}::${section.lines.join("\n")}`;
1012
+ if (extraSectionKeys.has(key)) return false;
1013
+ extraSectionKeys.add(key);
1014
+ return true;
1015
+ });
1016
+ if (!mergedEntity.name) {
1017
+ const dashIdx = canonical.indexOf("-");
1018
+ mergedEntity.name = dashIdx !== -1 ? canonical.slice(dashIdx + 1) : canonical;
1019
+ }
1020
+ mergedEntity.created = mergedEntity.created || mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
1021
+ mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
1022
+ const canonicalPath = path2.join(this.deps.entitiesDir, `${canonical}.md`);
1023
+ await this.deps.writeStorageSecureFile(canonicalPath, serializeEntityFile(mergedEntity, this.deps.entitySchemas));
1024
+ for (const file of files) {
1025
+ const filePath = path2.join(this.deps.entitiesDir, file);
1026
+ if (filePath !== canonicalPath) {
1027
+ try {
1028
+ await unlink(filePath);
1029
+ merged++;
1030
+ log.debug(`merged entity ${file} \u2192 ${canonical}.md`);
1031
+ } catch {
1032
+ }
1033
+ }
1034
+ }
1035
+ }
1036
+ } catch (err) {
1037
+ if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
1038
+ }
1039
+ return merged;
1040
+ }
1041
+ };
1042
+
1043
+ // src/storage.ts
119
1044
  var ARTIFACT_SEARCH_STOPWORDS = /* @__PURE__ */ new Set([
120
1045
  "a",
121
1046
  "an",
@@ -720,17 +1645,17 @@ var KNOWN_ENTITY_FILENAME_PREFIXES = /* @__PURE__ */ new Set([
720
1645
  "topic"
721
1646
  ]);
722
1647
  function inferEntityTypeFromFilename(pathRel) {
723
- const basename = path.basename(pathRel, ".md").toLowerCase();
1648
+ const basename = path3.basename(pathRel, ".md").toLowerCase();
724
1649
  const separator = basename.indexOf("-");
725
1650
  if (separator <= 0) return void 0;
726
1651
  const candidate = basename.slice(0, separator);
727
1652
  return KNOWN_ENTITY_FILENAME_PREFIXES.has(candidate) ? candidate : void 0;
728
1653
  }
729
1654
  function normalizeFrontmatterForPath(frontmatter, pathRel, content = "") {
730
- const normalizedPath = pathRel.split(path.sep).join("/");
1655
+ const normalizedPath = pathRel.split(path3.sep).join("/");
731
1656
  let normalizedFrontmatter = frontmatter;
732
1657
  if (normalizedPath === "entities" || normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/")) {
733
- const basename = path.basename(pathRel, ".md");
1658
+ const basename = path3.basename(pathRel, ".md");
734
1659
  const inferredType = inferEntityTypeFromContent(content) || inferEntityTypeFromFilename(pathRel) || "entity";
735
1660
  const existingTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
736
1661
  normalizedFrontmatter = {
@@ -797,8 +1722,8 @@ var ContentHashIndex = class _ContentHashIndex {
797
1722
  secureStoreKeyProvider;
798
1723
  secureStoreWriteKeyProvider;
799
1724
  memoryDir;
800
- constructor(stateDir, secureStoreKeyProvider = () => null, secureStoreWriteKeyProvider = secureStoreKeyProvider, memoryDir = path.dirname(stateDir)) {
801
- this.filePath = path.join(stateDir, "fact-hashes.txt");
1725
+ constructor(stateDir, secureStoreKeyProvider = () => null, secureStoreWriteKeyProvider = secureStoreKeyProvider, memoryDir = path3.dirname(stateDir)) {
1726
+ this.filePath = path3.join(stateDir, "fact-hashes.txt");
802
1727
  this.secureStoreKeyProvider = secureStoreKeyProvider;
803
1728
  this.secureStoreWriteKeyProvider = secureStoreWriteKeyProvider;
804
1729
  this.memoryDir = memoryDir;
@@ -845,7 +1770,7 @@ var ContentHashIndex = class _ContentHashIndex {
845
1770
  /** Persist index to disk if changed. */
846
1771
  async save() {
847
1772
  if (!this.dirty) return;
848
- await mkdir(path.dirname(this.filePath), { recursive: true });
1773
+ await mkdir(path3.dirname(this.filePath), { recursive: true });
849
1774
  await writeMaybeEncryptedFile(
850
1775
  this.filePath,
851
1776
  [...this.hashes].join("\n") + "\n",
@@ -1699,7 +2624,7 @@ function isValidBufferSurpriseEvent(value) {
1699
2624
  }
1700
2625
  return true;
1701
2626
  }
1702
- var StorageManager = class _StorageManager {
2627
+ var StorageManager3 = class _StorageManager {
1703
2628
  constructor(baseDir, entitySchemas) {
1704
2629
  this.baseDir = baseDir;
1705
2630
  this.entitySchemas = entitySchemas;
@@ -1708,9 +2633,11 @@ var StorageManager = class _StorageManager {
1708
2633
  baseDir;
1709
2634
  entitySchemas;
1710
2635
  knowledgeIndexCache = null;
2636
+ artifactIndexCache = null;
2637
+ /** Read by storage/entity-store.ts (decomposition), hence not `private`. */
1711
2638
  static KNOWLEDGE_INDEX_CACHE_TTL_MS = 6e5;
1712
2639
  // 10 minutes (entity mutations invalidate)
1713
- artifactIndexCache = null;
2640
+ /** Read by storage/memory-read-store.ts (decomposition). */
1714
2641
  static ARTIFACT_INDEX_CACHE_TTL_MS = 6e4;
1715
2642
  // 1 minute
1716
2643
  static artifactWriteVersionByDir = /* @__PURE__ */ new Map();
@@ -1739,14 +2666,18 @@ var StorageManager = class _StorageManager {
1739
2666
  // on every cold-tier write, making the cache correct across process boundaries
1740
2667
  // (gateway + CLI). After Finding UTsP broadened the scan to the entire cold/
1741
2668
  // subtree, amortizing across back-to-back writes is even more important.
2669
+ /** Read by storage/memory-read-store.ts (decomposition). */
1742
2670
  static COLD_SCAN_CACHE_TTL_MS = 3e4;
1743
2671
  // 30 seconds
2672
+ /** Read by storage/memory-read-store.ts (decomposition). */
1744
2673
  static coldMemoriesCache = /* @__PURE__ */ new Map();
1745
2674
  // Cache for readQuestions() — avoids serially re-reading tens of thousands of
1746
2675
  // question files on every recall. 60-second TTL is intentionally short so that
1747
2676
  // newly written questions surface quickly.
2677
+ /** Read by storage/memory-read-store.ts (decomposition). */
1748
2678
  static QUESTIONS_CACHE_TTL_MS = 6e4;
1749
2679
  // 1 minute
2680
+ /** Read by storage/memory-read-store.ts (decomposition). */
1750
2681
  static questionsCache = /* @__PURE__ */ new Map();
1751
2682
  factHashIndex = null;
1752
2683
  factHashIndexLoadPromise = null;
@@ -1905,7 +2836,7 @@ var StorageManager = class _StorageManager {
1905
2836
  void 0,
1906
2837
  this.baseDir
1907
2838
  );
1908
- const rel = path.relative(this.baseDir, filePath).split(path.sep).join("/");
2839
+ const rel = path3.relative(this.baseDir, filePath).split(path3.sep).join("/");
1909
2840
  return `${rel}:${version.versionId}`;
1910
2841
  } catch (err) {
1911
2842
  log.warn(
@@ -1914,19 +2845,51 @@ var StorageManager = class _StorageManager {
1914
2845
  return null;
1915
2846
  }
1916
2847
  }
2848
+ /**
2849
+ * The LIVE class object of this instance (issue #1809 review of the
2850
+ * storage decomposition): extracted store modules must read/write the
2851
+ * shared static caches (questionsCache, coldMemoriesCache, ...) through
2852
+ * the SAME class binding the host methods use. Referencing the imported
2853
+ * `StorageManager` symbol from a split bundle chunk can yield a second
2854
+ * class copy with its own statics, split-braining the caches
2855
+ * (resolveQuestion clears copy A while readQuestions reads copy B).
2856
+ */
2857
+ get storageManagerClass() {
2858
+ return this.constructor;
2859
+ }
1917
2860
  /** The root directory of this storage instance. */
2861
+ /** MemoryReadStore (storage.ts decomposition). Lazy; selfDeps live wiring. */
2862
+ _memoryReadStore;
2863
+ get memoryReadStore() {
2864
+ if (!this._memoryReadStore) {
2865
+ this._memoryReadStore = new MemoryReadStore(
2866
+ selfDeps(this)
2867
+ );
2868
+ }
2869
+ return this._memoryReadStore;
2870
+ }
2871
+ /** EntityStore (storage.ts decomposition). Lazy; selfDeps live wiring. */
2872
+ _entityStore;
2873
+ get entityStore() {
2874
+ if (!this._entityStore) {
2875
+ this._entityStore = new EntityStore(
2876
+ selfDeps(this)
2877
+ );
2878
+ }
2879
+ return this._entityStore;
2880
+ }
1918
2881
  get dir() {
1919
2882
  return this.baseDir;
1920
2883
  }
1921
2884
  identityFilePath(workspaceDir, namespace) {
1922
2885
  const rawNamespace = typeof namespace === "string" ? namespace.trim() : "";
1923
- if (!rawNamespace) return path.join(workspaceDir, "IDENTITY.md");
2886
+ if (!rawNamespace) return path3.join(workspaceDir, "IDENTITY.md");
1924
2887
  const safeNamespace = rawNamespace.replace(/[^a-zA-Z0-9._-]/g, "-");
1925
- return path.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
2888
+ return path3.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
1926
2889
  }
1927
2890
  versionFilePath(kind) {
1928
2891
  const fileName = kind === "memory-status" ? ".memory-status-version.log" : kind === "artifact-write" ? ".artifact-write-version.log" : ".cold-write-version.log";
1929
- return path.join(this.stateDir, fileName);
2892
+ return path3.join(this.stateDir, fileName);
1930
2893
  }
1931
2894
  bumpSharedVersion(kind, fallbackMap) {
1932
2895
  const filePath = this.versionFilePath(kind);
@@ -1973,7 +2936,7 @@ var StorageManager = class _StorageManager {
1973
2936
  // memories.
1974
2937
  // -------------------------------------------------------------------------
1975
2938
  get wearablesDir() {
1976
- return path.join(this.baseDir, WEARABLES_DIR_NAME);
2939
+ return path3.join(this.baseDir, WEARABLES_DIR_NAME);
1977
2940
  }
1978
2941
  /**
1979
2942
  * Resolve the on-disk path for a source/day transcript. Throws on
@@ -1991,7 +2954,7 @@ var StorageManager = class _StorageManager {
1991
2954
  `invalid wearable transcript date '${String(date)}' \u2014 expected YYYY-MM-DD`
1992
2955
  );
1993
2956
  }
1994
- return path.join(this.wearablesDir, sourceId, `${date}.md`);
2957
+ return path3.join(this.wearablesDir, sourceId, `${date}.md`);
1995
2958
  }
1996
2959
  async writeWearableDayTranscript(sourceId, date, serialized) {
1997
2960
  const targetPath = this.wearableTranscriptPath(sourceId, date);
@@ -2000,55 +2963,17 @@ var StorageManager = class _StorageManager {
2000
2963
  /** Read a stored day transcript; null when the day has no file. */
2001
2964
  async readWearableDayTranscript(sourceId, date) {
2002
2965
  const targetPath = this.wearableTranscriptPath(sourceId, date);
2003
- try {
2004
- return await readMaybeEncryptedFile(targetPath, this._secureStoreKey, this.baseDir);
2005
- } catch (err) {
2006
- if (err.code === "ENOENT") return null;
2007
- throw err;
2008
- }
2009
- }
2010
- /**
2011
- * List stored transcript days, newest first, optionally scoped to one
2012
- * source. Non-transcript files in the tree are ignored.
2013
- */
2014
- async listWearableTranscriptDays(sourceId) {
2015
- const days = [];
2016
- let sources;
2017
- if (sourceId !== void 0) {
2018
- sources = [sourceId];
2019
- } else {
2020
- try {
2021
- const entries = await readdir(this.wearablesDir, { withFileTypes: true });
2022
- sources = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
2023
- } catch (err) {
2024
- if (err.code === "ENOENT") return [];
2025
- throw err;
2026
- }
2027
- }
2028
- for (const source of sources) {
2029
- if (!/^[a-z][a-z0-9-]{0,63}$/.test(source)) continue;
2030
- let entries;
2031
- try {
2032
- entries = await readdir(path.join(this.wearablesDir, source));
2033
- } catch (err) {
2034
- if (err.code === "ENOENT") continue;
2035
- throw err;
2036
- }
2037
- for (const entry of entries) {
2038
- if (!entry.endsWith(".md")) continue;
2039
- const date = entry.slice(0, -3);
2040
- if (!isValidTranscriptDate(date)) continue;
2041
- days.push({ source, date });
2042
- }
2043
- }
2044
- days.sort((a, b) => {
2045
- if (a.date > b.date) return -1;
2046
- if (a.date < b.date) return 1;
2047
- if (a.source < b.source) return -1;
2048
- if (a.source > b.source) return 1;
2049
- return 0;
2050
- });
2051
- return days;
2966
+ try {
2967
+ return await readMaybeEncryptedFile(targetPath, this._secureStoreKey, this.baseDir);
2968
+ } catch (err) {
2969
+ if (err.code === "ENOENT") return null;
2970
+ throw err;
2971
+ }
2972
+ }
2973
+ async listWearableTranscriptDays(sourceId) {
2974
+ return this.memoryReadStore.listWearableTranscriptDays(
2975
+ sourceId
2976
+ );
2052
2977
  }
2053
2978
  /**
2054
2979
  * Locate a wearable-sourced memory by exact (trimmed) content,
@@ -2116,25 +3041,25 @@ var StorageManager = class _StorageManager {
2116
3041
  });
2117
3042
  }
2118
3043
  get factsDir() {
2119
- return path.join(this.baseDir, "facts");
3044
+ return path3.join(this.baseDir, "facts");
2120
3045
  }
2121
3046
  get correctionsDir() {
2122
- return path.join(this.baseDir, "corrections");
3047
+ return path3.join(this.baseDir, "corrections");
2123
3048
  }
2124
3049
  get proceduresDir() {
2125
- return path.join(this.baseDir, "procedures");
3050
+ return path3.join(this.baseDir, "procedures");
2126
3051
  }
2127
3052
  get reasoningTracesDir() {
2128
- return path.join(this.baseDir, "reasoning-traces");
3053
+ return path3.join(this.baseDir, "reasoning-traces");
2129
3054
  }
2130
3055
  get entitiesDir() {
2131
- return path.join(this.baseDir, "entities");
3056
+ return path3.join(this.baseDir, "entities");
2132
3057
  }
2133
3058
  resolveEntityFilePath(name) {
2134
3059
  if (typeof name !== "string") return null;
2135
- const filePath = path.resolve(this.entitiesDir, `${name}.md`);
2136
- const relative = path.relative(this.entitiesDir, filePath);
2137
- if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) {
3060
+ const filePath = path3.resolve(this.entitiesDir, `${name}.md`);
3061
+ const relative = path3.relative(this.entitiesDir, filePath);
3062
+ if (relative === "" || relative.startsWith("..") || path3.isAbsolute(relative)) {
2138
3063
  return null;
2139
3064
  }
2140
3065
  return filePath;
@@ -2149,10 +3074,10 @@ var StorageManager = class _StorageManager {
2149
3074
  return writeMaybeEncryptedFileFromChunks(filePath, chunks, this.resolveWriteKey(), {}, this.baseDir).then(() => this.notifyCatalogWrite());
2150
3075
  }
2151
3076
  assertManagedStoragePath(filePath, method) {
2152
- const resolved = path.resolve(filePath);
2153
- const base = path.resolve(this.baseDir);
2154
- const rel = path.relative(base, resolved);
2155
- if (rel === "" || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
3077
+ const resolved = path3.resolve(filePath);
3078
+ const base = path3.resolve(this.baseDir);
3079
+ const rel = path3.relative(base, resolved);
3080
+ if (rel === "" || rel === ".." || rel.startsWith(`..${path3.sep}`) || path3.isAbsolute(rel)) {
2156
3081
  throw new Error(`${method}: file path escapes memory dir`);
2157
3082
  }
2158
3083
  return resolved;
@@ -2163,8 +3088,8 @@ var StorageManager = class _StorageManager {
2163
3088
  }
2164
3089
  async digestOfflineSyncFile(filePath) {
2165
3090
  const target = this.assertManagedStoragePath(filePath, "storage.digestOfflineSyncFile");
2166
- const st = await stat(target);
2167
- const relPath = path.relative(this.baseDir, target).split(path.sep).join("/");
3091
+ const st = await stat2(target);
3092
+ const relPath = path3.relative(this.baseDir, target).split(path3.sep).join("/");
2168
3093
  const cache = await this.loadOfflineSyncDigestCache();
2169
3094
  const cached = cache.get(relPath);
2170
3095
  if (cached && cached.statBytes === st.size && cached.mtimeMs === st.mtimeMs && cached.ctimeMs === st.ctimeMs && !cached.encrypted) {
@@ -2225,7 +3150,7 @@ var StorageManager = class _StorageManager {
2225
3150
  const bytes = typeof record.bytes === "number" ? record.bytes : NaN;
2226
3151
  const sha256 = typeof record.sha256 === "string" ? record.sha256 : "";
2227
3152
  const encrypted = record.encrypted === true;
2228
- if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") || path.isAbsolute(cachePath) || !Number.isFinite(statBytes) || !Number.isFinite(mtimeMs) || !Number.isFinite(ctimeMs) || !Number.isFinite(bytes) || !/^[a-f0-9]{64}$/i.test(sha256)) {
3153
+ if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") || path3.isAbsolute(cachePath) || !Number.isFinite(statBytes) || !Number.isFinite(mtimeMs) || !Number.isFinite(ctimeMs) || !Number.isFinite(bytes) || !/^[a-f0-9]{64}$/i.test(sha256)) {
2229
3154
  continue;
2230
3155
  }
2231
3156
  cache.set(cachePath, { statBytes, mtimeMs, ctimeMs, encrypted, sha256, bytes });
@@ -2267,7 +3192,7 @@ var StorageManager = class _StorageManager {
2267
3192
  const cache = this.offlineSyncDigestCache;
2268
3193
  if (!cache) return;
2269
3194
  const entries = [...cache.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([entryPath, entry]) => ({ path: entryPath, ...entry }));
2270
- await mkdir(path.dirname(this.offlineSyncDigestCachePath), { recursive: true });
3195
+ await mkdir(path3.dirname(this.offlineSyncDigestCachePath), { recursive: true });
2271
3196
  await writeFile(
2272
3197
  this.offlineSyncDigestCachePath,
2273
3198
  `${JSON.stringify({ version: 1, entries })}
@@ -2306,7 +3231,7 @@ var StorageManager = class _StorageManager {
2306
3231
  }
2307
3232
  async deleteOfflineSyncFile(filePath) {
2308
3233
  const target = this.assertManagedStoragePath(filePath, "storage.deleteOfflineSyncFile");
2309
- await unlink(target).catch((error) => {
3234
+ await unlink2(target).catch((error) => {
2310
3235
  if (isErrnoCode(error, "ENOENT")) return;
2311
3236
  throw error;
2312
3237
  });
@@ -2316,14 +3241,14 @@ var StorageManager = class _StorageManager {
2316
3241
  this.invalidateAllMemoriesCache();
2317
3242
  this.invalidateKnowledgeIndexCache();
2318
3243
  this.factHashIndexAuthoritative = false;
2319
- await unlink(this.factHashIndexReadyPath).catch((error) => {
3244
+ await unlink2(this.factHashIndexReadyPath).catch((error) => {
2320
3245
  if (isErrnoCode(error, "ENOENT")) return;
2321
3246
  throw error;
2322
3247
  });
2323
- if (filePath.includes(`${path.sep}cold${path.sep}`)) {
3248
+ if (filePath.includes(`${path3.sep}cold${path3.sep}`)) {
2324
3249
  this.invalidateColdMemoriesCache();
2325
3250
  }
2326
- if (filePath.includes(`${path.sep}artifacts${path.sep}`)) {
3251
+ if (filePath.includes(`${path3.sep}artifacts${path3.sep}`)) {
2327
3252
  this.bumpArtifactWriteVersion();
2328
3253
  }
2329
3254
  this.bumpMemoryStatusVersion();
@@ -2351,7 +3276,7 @@ var StorageManager = class _StorageManager {
2351
3276
  }
2352
3277
  async appendStorageSecureFileUnlocked(filePath, content) {
2353
3278
  const writeKey = this.resolveWriteKey();
2354
- await mkdir(path.dirname(filePath), { recursive: true });
3279
+ await mkdir(path3.dirname(filePath), { recursive: true });
2355
3280
  if (writeKey === null) {
2356
3281
  try {
2357
3282
  if (isEncryptedFile(await readFile(filePath))) {
@@ -2377,16 +3302,16 @@ var StorageManager = class _StorageManager {
2377
3302
  this.notifyCatalogWrite();
2378
3303
  }
2379
3304
  get stateDir() {
2380
- return path.join(this.baseDir, "state");
3305
+ return path3.join(this.baseDir, "state");
2381
3306
  }
2382
3307
  get offlineSyncDigestCachePath() {
2383
- return path.join(this.baseDir, ".offline-sync", "digest-cache.v1.json");
3308
+ return path3.join(this.baseDir, ".offline-sync", "digest-cache.v1.json");
2384
3309
  }
2385
3310
  get entitySynthesisQueuePath() {
2386
- return path.join(this.stateDir, "entity-synthesis-queue.json");
3311
+ return path3.join(this.stateDir, "entity-synthesis-queue.json");
2387
3312
  }
2388
3313
  get factHashIndexReadyPath() {
2389
- return path.join(this.stateDir, "fact-hashes.ready");
3314
+ return path3.join(this.stateDir, "fact-hashes.ready");
2390
3315
  }
2391
3316
  // ── Tombstone store access (issue #1579) ─────────────────────────────────
2392
3317
  /**
@@ -2396,7 +3321,7 @@ var StorageManager = class _StorageManager {
2396
3321
  * helpers are injected into the store).
2397
3322
  */
2398
3323
  get tombstonesPath() {
2399
- return path.join(this.stateDir, "tombstones.jsonl");
3324
+ return path3.join(this.stateDir, "tombstones.jsonl");
2400
3325
  }
2401
3326
  /**
2402
3327
  * Configure the tombstone invariant for this storage instance. Installed
@@ -2595,7 +3520,7 @@ var StorageManager = class _StorageManager {
2595
3520
  );
2596
3521
  }
2597
3522
  await factHashIndex.save();
2598
- await mkdir(path.dirname(this.factHashIndexReadyPath), { recursive: true });
3523
+ await mkdir(path3.dirname(this.factHashIndexReadyPath), { recursive: true });
2599
3524
  await writeFile(this.factHashIndexReadyPath, "v1\n", "utf-8");
2600
3525
  this.factHashIndexAuthoritative = true;
2601
3526
  })().finally(() => {
@@ -2604,55 +3529,55 @@ var StorageManager = class _StorageManager {
2604
3529
  await this.factHashIndexAuthoritativePromise;
2605
3530
  }
2606
3531
  get questionsDir() {
2607
- return path.join(this.baseDir, "questions");
3532
+ return path3.join(this.baseDir, "questions");
2608
3533
  }
2609
3534
  get artifactsDir() {
2610
- return path.join(this.baseDir, "artifacts");
3535
+ return path3.join(this.baseDir, "artifacts");
2611
3536
  }
2612
3537
  get identityDir() {
2613
- return path.join(this.baseDir, "identity");
3538
+ return path3.join(this.baseDir, "identity");
2614
3539
  }
2615
3540
  get identityAnchorPath() {
2616
- return path.join(this.identityDir, "identity-anchor.md");
3541
+ return path3.join(this.identityDir, "identity-anchor.md");
2617
3542
  }
2618
3543
  get identityIncidentsDir() {
2619
- return path.join(this.identityDir, "incidents");
3544
+ return path3.join(this.identityDir, "incidents");
2620
3545
  }
2621
3546
  get identityAuditsWeeklyDir() {
2622
- return path.join(this.identityDir, "audits", "weekly");
3547
+ return path3.join(this.identityDir, "audits", "weekly");
2623
3548
  }
2624
3549
  get identityAuditsMonthlyDir() {
2625
- return path.join(this.identityDir, "audits", "monthly");
3550
+ return path3.join(this.identityDir, "audits", "monthly");
2626
3551
  }
2627
3552
  get identityImprovementLoopsPath() {
2628
- return path.join(this.identityDir, "improvement-loops.md");
3553
+ return path3.join(this.identityDir, "improvement-loops.md");
2629
3554
  }
2630
3555
  get identityReflectionsPath() {
2631
- return path.join(this.identityDir, "reflections.md");
3556
+ return path3.join(this.identityDir, "reflections.md");
2632
3557
  }
2633
3558
  get profilePath() {
2634
- return path.join(this.baseDir, "profile.md");
3559
+ return path3.join(this.baseDir, "profile.md");
2635
3560
  }
2636
3561
  get memoryActionsPath() {
2637
- return path.join(this.stateDir, "memory-actions.jsonl");
3562
+ return path3.join(this.stateDir, "memory-actions.jsonl");
2638
3563
  }
2639
3564
  get memoryLifecycleLedgerPath() {
2640
- return path.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
3565
+ return path3.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
2641
3566
  }
2642
3567
  get compressionGuidelinesPath() {
2643
- return path.join(this.stateDir, "compression-guidelines.md");
3568
+ return path3.join(this.stateDir, "compression-guidelines.md");
2644
3569
  }
2645
3570
  get compressionGuidelineDraftPath() {
2646
- return path.join(this.stateDir, "compression-guidelines.draft.md");
3571
+ return path3.join(this.stateDir, "compression-guidelines.draft.md");
2647
3572
  }
2648
3573
  get compressionGuidelineStatePath() {
2649
- return path.join(this.stateDir, "compression-guideline-state.json");
3574
+ return path3.join(this.stateDir, "compression-guideline-state.json");
2650
3575
  }
2651
3576
  get compressionGuidelineDraftStatePath() {
2652
- return path.join(this.stateDir, "compression-guideline-draft-state.json");
3577
+ return path3.join(this.stateDir, "compression-guideline-draft-state.json");
2653
3578
  }
2654
3579
  get behaviorSignalsPath() {
2655
- return path.join(this.stateDir, "behavior-signals.jsonl");
3580
+ return path3.join(this.stateDir, "behavior-signals.jsonl");
2656
3581
  }
2657
3582
  /**
2658
3583
  * Buffer surprise telemetry ledger (issue #563 PR 3).
@@ -2665,7 +3590,7 @@ var StorageManager = class _StorageManager {
2665
3590
  * governance sweeps can treat it uniformly.
2666
3591
  */
2667
3592
  get bufferSurpriseLedgerPath() {
2668
- return path.join(this.stateDir, "buffer-surprise-ledger.jsonl");
3593
+ return path3.join(this.stateDir, "buffer-surprise-ledger.jsonl");
2669
3594
  }
2670
3595
  /**
2671
3596
  * Entity alias table loaded from THIS store's config/aliases.json.
@@ -2675,9 +3600,11 @@ var StorageManager = class _StorageManager {
2675
3600
  * store loaded last rewrite every other store's canonical entity ids.
2676
3601
  */
2677
3602
  userAliases = {};
2678
- /** Normalize an entity name using this store's alias table. */
2679
3603
  normalizeEntityName(raw, type) {
2680
- return normalizeEntityName(raw, type, this.userAliases);
3604
+ return this.entityStore.normalizeEntityName(
3605
+ raw,
3606
+ type
3607
+ );
2681
3608
  }
2682
3609
  /**
2683
3610
  * Read-only view of this store's user alias table, for call sites that
@@ -2697,7 +3624,7 @@ var StorageManager = class _StorageManager {
2697
3624
  this.loadAliasesSync();
2698
3625
  }
2699
3626
  loadAliasesSync() {
2700
- const aliasPath = path.join(this.baseDir, "config", "aliases.json");
3627
+ const aliasPath = path3.join(this.baseDir, "config", "aliases.json");
2701
3628
  this.userAliases = {};
2702
3629
  try {
2703
3630
  const raw = readFileSync(aliasPath, "utf-8");
@@ -2722,9 +3649,9 @@ var StorageManager = class _StorageManager {
2722
3649
  }
2723
3650
  async ensureDirectories() {
2724
3651
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2725
- await mkdir(path.join(this.factsDir, today), { recursive: true });
2726
- await mkdir(path.join(this.proceduresDir, today), { recursive: true });
2727
- await mkdir(path.join(this.reasoningTracesDir, today), { recursive: true });
3652
+ await mkdir(path3.join(this.factsDir, today), { recursive: true });
3653
+ await mkdir(path3.join(this.proceduresDir, today), { recursive: true });
3654
+ await mkdir(path3.join(this.reasoningTracesDir, today), { recursive: true });
2728
3655
  await mkdir(this.correctionsDir, { recursive: true });
2729
3656
  await mkdir(this.entitiesDir, { recursive: true });
2730
3657
  await mkdir(this.stateDir, { recursive: true });
@@ -2734,7 +3661,7 @@ var StorageManager = class _StorageManager {
2734
3661
  await mkdir(this.identityIncidentsDir, { recursive: true });
2735
3662
  await mkdir(this.identityAuditsWeeklyDir, { recursive: true });
2736
3663
  await mkdir(this.identityAuditsMonthlyDir, { recursive: true });
2737
- await mkdir(path.join(this.baseDir, "config"), { recursive: true });
3664
+ await mkdir(path3.join(this.baseDir, "config"), { recursive: true });
2738
3665
  }
2739
3666
  /**
2740
3667
  * Resolve the on-disk write path for a memory of the given category, creating
@@ -2751,11 +3678,11 @@ var StorageManager = class _StorageManager {
2751
3678
  async resolveCategoryWritePath(category, id, today) {
2752
3679
  if (category === "correction") {
2753
3680
  await mkdir(this.correctionsDir, { recursive: true });
2754
- return path.join(this.correctionsDir, `${id}.md`);
3681
+ return path3.join(this.correctionsDir, `${id}.md`);
2755
3682
  }
2756
- const datedDir = path.join(getCategoryDir(this.baseDir, category), today);
3683
+ const datedDir = path3.join(getCategoryDir(this.baseDir, category), today);
2757
3684
  await mkdir(datedDir, { recursive: true });
2758
- return path.join(datedDir, `${id}.md`);
3685
+ return path3.join(datedDir, `${id}.md`);
2759
3686
  }
2760
3687
  async writeMemory(category, content, options = {}) {
2761
3688
  await this.ensureDirectories();
@@ -3005,7 +3932,7 @@ ${sanitized.text}
3005
3932
  await this.ensureDirectories();
3006
3933
  const now = /* @__PURE__ */ new Date();
3007
3934
  const day = now.toISOString().slice(0, 10);
3008
- const dir = path.join(this.artifactsDir, day);
3935
+ const dir = path3.join(this.artifactsDir, day);
3009
3936
  await mkdir(dir, { recursive: true });
3010
3937
  const id = `artifact-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
3011
3938
  const fm = {
@@ -3029,7 +3956,7 @@ ${sanitized.text}
3029
3956
  log.warn(`artifact content rejected for ${id}; violations=${sanitized.violations.join(", ")}`);
3030
3957
  return "";
3031
3958
  }
3032
- const filePath = path.join(dir, `${id}.md`);
3959
+ const filePath = path3.join(dir, `${id}.md`);
3033
3960
  await this.writeStorageSecureFile(filePath, `${serializeFrontmatter(fm)}
3034
3961
 
3035
3962
  ${sanitized.text}
@@ -3048,45 +3975,7 @@ ${sanitized.text}
3048
3975
  return id;
3049
3976
  }
3050
3977
  async readAllArtifactsCached() {
3051
- if (this.artifactIndexCache && Date.now() - this.artifactIndexCache.loadedAtMs <= _StorageManager.ARTIFACT_INDEX_CACHE_TTL_MS && this.artifactIndexCache.writeVersion === this.getArtifactWriteVersion()) {
3052
- return this.artifactIndexCache.memories;
3053
- }
3054
- const scanArtifacts = async () => {
3055
- const artifacts = [];
3056
- const readDir = async (dir) => {
3057
- try {
3058
- const entries = await readdir(dir, { withFileTypes: true });
3059
- for (const entry of entries) {
3060
- const fullPath = path.join(dir, entry.name);
3061
- if (entry.isDirectory()) {
3062
- await readDir(fullPath);
3063
- continue;
3064
- }
3065
- if (!entry.name.endsWith(".md")) continue;
3066
- const memory = await this.readMemoryByPath(fullPath);
3067
- if (!memory) continue;
3068
- artifacts.push(memory);
3069
- }
3070
- } catch {
3071
- }
3072
- };
3073
- await readDir(this.artifactsDir);
3074
- return artifacts;
3075
- };
3076
- const MAX_REBUILD_RETRIES = 2;
3077
- let latestArtifacts = [];
3078
- for (let attempt = 0; attempt <= MAX_REBUILD_RETRIES; attempt += 1) {
3079
- const versionBefore = this.getArtifactWriteVersion();
3080
- const artifacts = await scanArtifacts();
3081
- const versionAfter = this.getArtifactWriteVersion();
3082
- latestArtifacts = artifacts;
3083
- if (versionAfter === versionBefore) {
3084
- this.artifactIndexCache = { memories: artifacts, loadedAtMs: Date.now(), writeVersion: versionAfter };
3085
- return artifacts;
3086
- }
3087
- }
3088
- this.artifactIndexCache = null;
3089
- return latestArtifacts;
3978
+ return this.memoryReadStore.readAllArtifactsCached();
3090
3979
  }
3091
3980
  async searchArtifacts(query, maxResults) {
3092
3981
  const tokens = tokenizeArtifactSearchText(query);
@@ -3106,107 +3995,12 @@ ${sanitized.text}
3106
3995
  return hits.slice(0, maxResults).map((h) => h.memory);
3107
3996
  }
3108
3997
  async writeEntity(name, type, facts, options = {}) {
3109
- await this.ensureDirectories();
3110
- if (typeof name !== "string" || !name.trim() || typeof type !== "string" || !type.trim()) {
3111
- log.warn("writeEntity: invalid entity payload, skipping", {
3112
- nameType: typeof name,
3113
- typeType: typeof type
3114
- });
3115
- return "";
3116
- }
3117
- const safeFacts = Array.isArray(facts) ? [...new Set(
3118
- facts.filter((fact) => typeof fact === "string").map((fact) => fact.trim()).filter((fact) => fact.length > 0)
3119
- )] : [];
3120
- let normalized = this.normalizeEntityName(name, type);
3121
- const match = await this.findMatchingEntity(name, type);
3122
- if (match && match !== normalized) {
3123
- log.debug(`fuzzy match: "${normalized}" \u2192 existing "${match}"`);
3124
- normalized = match;
3125
- }
3126
- const filePath = path.join(this.entitiesDir, `${normalized}.md`);
3127
- let entity = {
3998
+ return this.entityStore.writeEntity(
3128
3999
  name,
3129
4000
  type,
3130
- created: "",
3131
- updated: (/* @__PURE__ */ new Date()).toISOString(),
3132
- facts: [],
3133
- summary: void 0,
3134
- synthesis: void 0,
3135
- synthesisUpdatedAt: void 0,
3136
- synthesisVersion: void 0,
3137
- synthesisStructuredFactCount: void 0,
3138
- synthesisStructuredFactDigest: void 0,
3139
- timeline: [],
3140
- relationships: [],
3141
- activity: [],
3142
- aliases: []
3143
- };
3144
- try {
3145
- const existing = await this.readStorageSecureFile(filePath);
3146
- entity = parseEntityFile(existing, this.entitySchemas);
3147
- } catch (err) {
3148
- if (err instanceof SecureStoreLockedError) throw err;
3149
- if (!isErrnoCode(err, "ENOENT")) throw err;
3150
- }
3151
- const timestamp = options.timestamp?.trim() || (/* @__PURE__ */ new Date()).toISOString();
3152
- const source = options.source?.trim() || void 0;
3153
- const sessionKey = options.sessionKey?.trim() || void 0;
3154
- const principal = options.principal?.trim() || void 0;
3155
- const structuredSectionMap = new Map(
3156
- (entity.structuredSections ?? []).map((section) => [section.key, {
3157
- ...section,
3158
- facts: [...section.facts]
3159
- }])
3160
- );
3161
- for (const section of options.structuredSections ?? []) {
3162
- const normalizedSection = normalizeEntityStructuredSection(type, section, this.entitySchemas);
3163
- const normalizedFacts = normalizeStructuredSectionFacts(section.facts);
3164
- if (normalizedFacts.length === 0) continue;
3165
- const existingSection = structuredSectionMap.get(normalizedSection.key);
3166
- if (!existingSection) {
3167
- structuredSectionMap.set(normalizedSection.key, {
3168
- key: normalizedSection.key,
3169
- title: normalizedSection.title,
3170
- facts: normalizedFacts
3171
- });
3172
- continue;
3173
- }
3174
- existingSection.facts = normalizeStructuredSectionFacts([...existingSection.facts, ...normalizedFacts]);
3175
- if (!existingSection.title.trim() && normalizedSection.title.trim()) {
3176
- existingSection.title = normalizedSection.title;
3177
- }
3178
- }
3179
- for (const fact of safeFacts) {
3180
- const nextEntry = {
3181
- timestamp,
3182
- text: fact,
3183
- ...source ? { source } : {},
3184
- ...sessionKey ? { sessionKey } : {},
3185
- ...principal ? { principal } : {}
3186
- };
3187
- const alreadyPresent = entity.timeline.some(
3188
- (entry) => entry.timestamp === nextEntry.timestamp && entry.text === nextEntry.text && entry.source === nextEntry.source && entry.sessionKey === nextEntry.sessionKey && entry.principal === nextEntry.principal
3189
- );
3190
- if (alreadyPresent) continue;
3191
- entity.timeline.push(nextEntry);
3192
- }
3193
- entity.structuredSections = sortStructuredSectionsBySchema(
3194
- type,
3195
- Array.from(structuredSectionMap.values()).filter((section) => section.facts.length > 0),
3196
- this.entitySchemas
4001
+ facts,
4002
+ options
3197
4003
  );
3198
- entity.facts = compileEntityFacts(entity.timeline, entity.structuredSections);
3199
- entity.summary = entity.synthesis || entity.summary;
3200
- entity.name = name;
3201
- entity.type = type;
3202
- entity.created = entity.created || timestamp;
3203
- entity.updated = (/* @__PURE__ */ new Date()).toISOString();
3204
- await this.snapshotBeforeWrite(filePath, "write");
3205
- await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
3206
- this.invalidateKnowledgeIndexCache();
3207
- this.bumpMemoryStatusVersion();
3208
- log.debug(`wrote entity ${normalized}`);
3209
- return normalized;
3210
4004
  }
3211
4005
  async readProfile() {
3212
4006
  try {
@@ -3364,7 +4158,7 @@ ${sanitized.text}
3364
4158
  * per-process in-memory cache safe across process boundaries.
3365
4159
  */
3366
4160
  invalidateColdMemoriesCache() {
3367
- const coldRoot = path.join(this.baseDir, "cold");
4161
+ const coldRoot = path3.join(this.baseDir, "cold");
3368
4162
  _StorageManager.coldMemoriesCache.delete(coldRoot);
3369
4163
  this.bumpColdWriteVersion();
3370
4164
  invalidateAllForDir(this.baseDir);
@@ -3387,45 +4181,7 @@ ${sanitized.text}
3387
4181
  return Math.max(1, Math.floor(batchSize));
3388
4182
  }
3389
4183
  async collectActiveMemoryPaths() {
3390
- const filePaths = [];
3391
- let memoryRootReal;
3392
- try {
3393
- memoryRootReal = await realpath(this.baseDir);
3394
- } catch {
3395
- return filePaths;
3396
- }
3397
- const collectPaths = async (dir) => {
3398
- let entries;
3399
- try {
3400
- const dirStat = await lstat(dir);
3401
- if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) return;
3402
- assertPathInsideRoot(memoryRootReal, await realpath(dir), dir);
3403
- entries = await readdir(dir, { withFileTypes: true });
3404
- } catch {
3405
- return;
3406
- }
3407
- const subdirs = [];
3408
- for (const entry of entries) {
3409
- if (entry.isSymbolicLink()) continue;
3410
- const fullPath = path.join(dir, entry.name);
3411
- if (entry.isDirectory()) {
3412
- subdirs.push(fullPath);
3413
- } else if (entry.name.endsWith(".md")) {
3414
- try {
3415
- assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
3416
- filePaths.push(fullPath);
3417
- } catch {
3418
- }
3419
- }
3420
- }
3421
- for (const subdir of subdirs) {
3422
- await collectPaths(subdir);
3423
- }
3424
- };
3425
- for (const dir of RECALL_FALLBACK_DIRS) {
3426
- await collectPaths(path.join(this.baseDir, dir));
3427
- }
3428
- return filePaths;
4184
+ return this.memoryReadStore.collectActiveMemoryPaths();
3429
4185
  }
3430
4186
  async readParsedMemoriesFromPaths(filePaths, batchSize) {
3431
4187
  if (filePaths.length === 0) return [];
@@ -3480,7 +4236,7 @@ ${sanitized.text}
3480
4236
  return updatedMs >= updatedAfterMs ? filePath : null;
3481
4237
  }
3482
4238
  try {
3483
- const fileStat = await stat(filePath);
4239
+ const fileStat = await stat2(filePath);
3484
4240
  return fileStat.mtimeMs >= updatedAfterMs ? filePath : null;
3485
4241
  } catch {
3486
4242
  return filePath;
@@ -3492,7 +4248,7 @@ ${sanitized.text}
3492
4248
  const correctionPaths = [];
3493
4249
  const factPaths = [];
3494
4250
  for (const filePath of filePaths) {
3495
- if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${path.sep}`)) {
4251
+ if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${path3.sep}`)) {
3496
4252
  correctionPaths.push(filePath);
3497
4253
  } else {
3498
4254
  factPaths.push(filePath);
@@ -3534,103 +4290,18 @@ ${sanitized.text}
3534
4290
  }
3535
4291
  return { memories, filePaths };
3536
4292
  }
3537
- async readMemoriesWindow(options = {}) {
3538
- const allPaths = await this.collectActiveMemoryPaths();
3539
- const sortedPaths = this.orderWindowPaths(allPaths);
3540
- const maxMemories = typeof options.maxMemories === "number" && Number.isFinite(options.maxMemories) ? Math.max(1, Math.floor(options.maxMemories)) : void 0;
3541
- const maxCandidatePaths = maxMemories === void 0 ? void 0 : maxMemories * 2;
3542
- const updatedAfterMs = options.updatedAfter?.getTime();
3543
- const normalizedBatchSize = this.normalizeMemoryReadBatchSize(options.batchSize);
3544
- const memories = [];
3545
- const selectedPaths = [];
3546
- for (let i = 0; i < sortedPaths.length; i += normalizedBatchSize) {
3547
- if (maxMemories !== void 0 && (memories.length >= maxMemories || maxCandidatePaths !== void 0 && selectedPaths.length >= maxCandidatePaths)) {
3548
- return { memories, filePaths: selectedPaths };
3549
- }
3550
- const batchPaths = sortedPaths.slice(i, i + normalizedBatchSize);
3551
- const candidateBatchPaths = updatedAfterMs === void 0 ? batchPaths : await this.filterWindowPathsByUpdatedAfter(batchPaths, updatedAfterMs);
3552
- const remainingSlots = maxMemories === void 0 ? void 0 : Math.max(0, maxMemories - memories.length);
3553
- const remainingInspectionBudget = maxCandidatePaths === void 0 ? void 0 : Math.max(0, maxCandidatePaths - selectedPaths.length);
3554
- const { memories: batchMemories, filePaths: parsedCandidatePaths } = remainingSlots === void 0 ? {
3555
- memories: await this.readParsedMemoriesFromPaths(candidateBatchPaths, normalizedBatchSize),
3556
- filePaths: candidateBatchPaths
3557
- } : await this.readWindowBoundedBatch(
3558
- candidateBatchPaths,
3559
- remainingSlots,
3560
- remainingInspectionBudget ?? remainingSlots,
3561
- normalizedBatchSize
3562
- );
3563
- selectedPaths.push(...parsedCandidatePaths);
3564
- for (const memory of batchMemories) {
3565
- memories.push(memory);
3566
- if (maxMemories !== void 0 && memories.length >= maxMemories) {
3567
- return { memories, filePaths: selectedPaths };
3568
- }
3569
- }
3570
- }
3571
- return { memories, filePaths: selectedPaths };
3572
- }
3573
- async _readAllMemoriesFromDisk() {
3574
- const filePaths = await this.collectActiveMemoryPaths();
3575
- return this.readParsedMemoriesFromPaths(filePaths, 50);
3576
- }
3577
- /**
3578
- * Read all memories from the cold tier by scanning the entire cold/ root
3579
- * tree. Previously this only scanned cold/facts/ and cold/corrections/, but
3580
- * structuredAttributes can appear on any MemoryCategory (preference, decision,
3581
- * entity, etc.). buildTierMemoryPath now routes each category to its own
3582
- * cold/<dir>/ subtree via the shared categoryDirName() chokepoint (issue
3583
- * #1546), so cold decisions/preferences/... live outside cold/facts/.
3584
- * Scanning the full coldRoot covers every category dir and guards against
3585
- * files placed in unexpected subdirectories during manual operations or future
3586
- * refactors.
3587
- *
3588
- * Broadened in PR #402 round-6 (Finding UTsP): scanning only facts/ and
3589
- * corrections/ was a narrower-than-necessary subset of the cold directory
3590
- * tree. Correctness trumps the minor performance difference — cold scans
3591
- * already happen at most once per supersession write.
3592
- *
3593
- * Used by applyTemporalSupersession so that memories already demoted to
3594
- * cold/ can still be marked superseded when a newer hot fact arrives.
3595
- *
3596
- * Cached with a TTL (Finding UOGi, PR #402 round-6): back-to-back
3597
- * structured-attribute writes in the same burst reuse the cached result
3598
- * instead of re-scanning the cold tree on every call. The cache is
3599
- * invalidated whenever a write calls invalidateAllMemoriesCache() (which
3600
- * covers any hot→cold demotion that changes cold-tier contents) and
3601
- * expires after COLD_SCAN_CACHE_TTL_MS as a safety net.
3602
- */
3603
- async readAllColdMemories() {
3604
- const coldRoot = this.resolveTierRootDir("cold");
3605
- const currentColdVersion = this.readColdWriteVersion();
3606
- const cached = _StorageManager.coldMemoriesCache.get(coldRoot);
3607
- if (cached && Date.now() - cached.loadedAt < _StorageManager.COLD_SCAN_CACHE_TTL_MS && cached.coldVersion === currentColdVersion) {
3608
- return cached.memories;
3609
- }
3610
- const filePaths = [];
3611
- const collectPaths = async (dir) => {
3612
- try {
3613
- const entries = await readdir(dir, { withFileTypes: true });
3614
- const subdirs = [];
3615
- for (const entry of entries) {
3616
- const fullPath = path.join(dir, entry.name);
3617
- if (entry.isDirectory()) {
3618
- subdirs.push(fullPath);
3619
- } else if (entry.name.endsWith(".md")) {
3620
- filePaths.push(fullPath);
3621
- }
3622
- }
3623
- for (const subdir of subdirs) {
3624
- await collectPaths(subdir);
3625
- }
3626
- } catch {
3627
- }
3628
- };
3629
- await collectPaths(coldRoot);
3630
- const memories = await this.readParsedMemoriesFromPaths(filePaths, 50);
3631
- _StorageManager.coldMemoriesCache.set(coldRoot, { memories, loadedAt: Date.now(), coldVersion: currentColdVersion });
3632
- return memories;
3633
- }
4293
+ async readMemoriesWindow(options = {}) {
4294
+ return this.memoryReadStore.readMemoriesWindow(
4295
+ options
4296
+ );
4297
+ }
4298
+ async _readAllMemoriesFromDisk() {
4299
+ const filePaths = await this.collectActiveMemoryPaths();
4300
+ return this.readParsedMemoriesFromPaths(filePaths, 50);
4301
+ }
4302
+ async readAllColdMemories() {
4303
+ return this.memoryReadStore.readAllColdMemories();
4304
+ }
3634
4305
  /**
3635
4306
  * Read archived memory markdown files under archive/.
3636
4307
  * Used by long-term recall fallback when hot recall has no hits.
@@ -3640,9 +4311,9 @@ ${sanitized.text}
3640
4311
  const root = this.archiveDir;
3641
4312
  const readDir = async (dir) => {
3642
4313
  try {
3643
- const entries = await readdir(dir, { withFileTypes: true });
4314
+ const entries = await readdir3(dir, { withFileTypes: true });
3644
4315
  for (const entry of entries) {
3645
- const fullPath = path.join(dir, entry.name);
4316
+ const fullPath = path3.join(dir, entry.name);
3646
4317
  if (entry.isDirectory()) {
3647
4318
  await readDir(fullPath);
3648
4319
  } else if (entry.name.endsWith(".md")) {
@@ -3671,51 +4342,13 @@ ${sanitized.text}
3671
4342
  await readDir(root);
3672
4343
  return memories;
3673
4344
  }
3674
- /** Read a single memory file by its absolute path. Returns null if unreadable. */
3675
4345
  async readMemoryByPath(filePath) {
3676
- try {
3677
- const raw = await readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
3678
- const parsed = parseFrontmatter(raw);
3679
- if (parsed) {
3680
- return {
3681
- path: filePath,
3682
- frontmatter: normalizeFrontmatterForPath(
3683
- parsed.frontmatter,
3684
- toMemoryPathRel(this.baseDir, filePath),
3685
- parsed.content
3686
- ),
3687
- content: parsed.content
3688
- };
3689
- }
3690
- const normalizedPath = filePath.split(path.sep).join("/");
3691
- if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
3692
- const entity = parseEntityFile(raw, this.entitySchemas);
3693
- if (!entity.name) return null;
3694
- const nameWithoutExt = path.basename(filePath, ".md");
3695
- const fileMtime = entity.updated || await stat(filePath).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
3696
- return {
3697
- path: filePath,
3698
- frontmatter: {
3699
- id: nameWithoutExt,
3700
- category: "entity",
3701
- created: fileMtime,
3702
- updated: fileMtime,
3703
- source: "entity_extraction",
3704
- confidence: 0.9,
3705
- confidenceTier: confidenceTier(0.9),
3706
- tags: entity.type ? [entity.type] : []
3707
- },
3708
- content: raw
3709
- };
3710
- }
3711
- return null;
3712
- } catch (err) {
3713
- if (err instanceof SecureStoreLockedError) throw err;
3714
- return null;
3715
- }
4346
+ return this.memoryReadStore.readMemoryByPath(
4347
+ filePath
4348
+ );
3716
4349
  }
3717
4350
  resolveTierRootDir(tier) {
3718
- return tier === "cold" ? path.join(this.baseDir, "cold") : this.baseDir;
4351
+ return tier === "cold" ? path3.join(this.baseDir, "cold") : this.baseDir;
3719
4352
  }
3720
4353
  resolveMemoryDateDir(memory) {
3721
4354
  const preferred = memory.frontmatter.created || memory.frontmatter.updated;
@@ -3730,13 +4363,13 @@ ${sanitized.text}
3730
4363
  buildTierMemoryPath(memory, tier) {
3731
4364
  const root = this.resolveTierRootDir(tier);
3732
4365
  if (this.isArtifactMemory(memory)) {
3733
- return path.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
4366
+ return path3.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
3734
4367
  }
3735
4368
  if (memory.frontmatter.category === "correction") {
3736
- return path.join(root, "corrections", `${memory.frontmatter.id}.md`);
4369
+ return path3.join(root, "corrections", `${memory.frontmatter.id}.md`);
3737
4370
  }
3738
4371
  const dir = categoryDirName(memory.frontmatter.category);
3739
- return path.join(root, dir, this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
4372
+ return path3.join(root, dir, this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
3740
4373
  }
3741
4374
  async writeMemoryFileAtomic(targetPath, memory) {
3742
4375
  const fileContent = `${serializeFrontmatter(memory.frontmatter)}
@@ -3749,11 +4382,11 @@ ${memory.content}
3749
4382
  }
3750
4383
  async moveMemoryToPath(memory, targetPath) {
3751
4384
  await this.writeMemoryFileAtomic(targetPath, memory);
3752
- const sourcePath = path.resolve(memory.path);
3753
- const destPath = path.resolve(targetPath);
4385
+ const sourcePath = path3.resolve(memory.path);
4386
+ const destPath = path3.resolve(targetPath);
3754
4387
  if (sourcePath !== destPath) {
3755
4388
  try {
3756
- await unlink(memory.path);
4389
+ await unlink2(memory.path);
3757
4390
  } catch (err) {
3758
4391
  const message = err instanceof Error ? err.message : String(err);
3759
4392
  if (!message.includes("ENOENT")) {
@@ -3765,15 +4398,15 @@ ${memory.content}
3765
4398
  }
3766
4399
  async migrateMemoryToTier(memory, targetTier) {
3767
4400
  const targetPath = this.buildTierMemoryPath(memory, targetTier);
3768
- const sourcePath = path.resolve(memory.path);
3769
- const destPath = path.resolve(targetPath);
4401
+ const sourcePath = path3.resolve(memory.path);
4402
+ const destPath = path3.resolve(targetPath);
3770
4403
  if (sourcePath === destPath) {
3771
4404
  return { changed: false, targetPath };
3772
4405
  }
3773
4406
  const existing = await this.readMemoryByPath(targetPath);
3774
4407
  if (existing?.frontmatter.id === memory.frontmatter.id) {
3775
4408
  try {
3776
- await unlink(memory.path);
4409
+ await unlink2(memory.path);
3777
4410
  } catch (err) {
3778
4411
  const message = err instanceof Error ? err.message : String(err);
3779
4412
  if (!message.includes("ENOENT")) {
@@ -3792,7 +4425,7 @@ ${memory.content}
3792
4425
  return { changed: true, targetPath };
3793
4426
  }
3794
4427
  get archiveDir() {
3795
- return path.join(this.baseDir, "archive");
4428
+ return path3.join(this.baseDir, "archive");
3796
4429
  }
3797
4430
  /**
3798
4431
  * Archive a memory by moving it from facts/ to archive/YYYY-MM-DD/.
@@ -3803,7 +4436,7 @@ ${memory.content}
3803
4436
  try {
3804
4437
  const now = lifecycle?.at ?? /* @__PURE__ */ new Date();
3805
4438
  const today = now.toISOString().slice(0, 10);
3806
- const destDir = path.join(this.archiveDir, today);
4439
+ const destDir = path3.join(this.archiveDir, today);
3807
4440
  await mkdir(destDir, { recursive: true });
3808
4441
  const updatedFm = {
3809
4442
  ...memory.frontmatter,
@@ -3815,9 +4448,9 @@ ${memory.content}
3815
4448
 
3816
4449
  ${memory.content}
3817
4450
  `;
3818
- const destPath = path.join(destDir, path.basename(memory.path));
4451
+ const destPath = path3.join(destDir, path3.basename(memory.path));
3819
4452
  await this.writeStorageSecureFile(destPath, fileContent);
3820
- await unlink(memory.path);
4453
+ await unlink2(memory.path);
3821
4454
  this.invalidateAllMemoriesCache();
3822
4455
  await this.appendGeneratedMemoryLifecycleEventFailOpen(
3823
4456
  "storage.archiveMemory",
@@ -3844,33 +4477,19 @@ ${memory.content}
3844
4477
  }
3845
4478
  async readEntities() {
3846
4479
  try {
3847
- const entries = await readdir(this.entitiesDir);
4480
+ const entries = await readdir3(this.entitiesDir);
3848
4481
  return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", ""));
3849
4482
  } catch {
3850
4483
  return [];
3851
4484
  }
3852
4485
  }
3853
4486
  async readEntity(name) {
3854
- const filePath = this.resolveEntityFilePath(name);
3855
- if (filePath === null) {
3856
- return "";
3857
- }
3858
- try {
3859
- return await this.readStorageSecureFile(filePath);
3860
- } catch (err) {
3861
- if (err instanceof SecureStoreLockedError) throw err;
3862
- if (!isErrnoCode(err, "ENOENT")) throw err;
3863
- return "";
3864
- }
4487
+ return this.entityStore.readEntity(
4488
+ name
4489
+ );
3865
4490
  }
3866
- /** Return sorted list of entity filenames (without .md extension) */
3867
4491
  async listEntityNames() {
3868
- try {
3869
- const entries = await readdir(this.entitiesDir);
3870
- return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", "")).sort();
3871
- } catch {
3872
- return [];
3873
- }
4492
+ return this.entityStore.listEntityNames();
3874
4493
  }
3875
4494
  /**
3876
4495
  * Find an existing entity that fuzzy-matches the proposed name.
@@ -3912,7 +4531,7 @@ ${memory.content}
3912
4531
  const memory = memories.find((m) => m.frontmatter.id === id);
3913
4532
  if (!memory) return false;
3914
4533
  try {
3915
- await unlink(memory.path);
4534
+ await unlink2(memory.path);
3916
4535
  this.invalidateAllMemoriesCache();
3917
4536
  this.bumpMemoryStatusVersion();
3918
4537
  log.debug(`invalidated memory ${id}`);
@@ -3977,7 +4596,7 @@ ${memory.content}
3977
4596
  `;
3978
4597
  await this.writeStorageSecureFile(memory.path, fileContent);
3979
4598
  this.invalidateAllMemoriesCache();
3980
- if (memory.path.includes(`${path.sep}cold${path.sep}`)) {
4599
+ if (memory.path.includes(`${path3.sep}cold${path3.sep}`)) {
3981
4600
  this.invalidateColdMemoriesCache();
3982
4601
  }
3983
4602
  try {
@@ -4035,7 +4654,7 @@ ${memory.content}
4035
4654
  const expiresAt = new Date(m.frontmatter.expiresAt).getTime();
4036
4655
  if (expiresAt < now) {
4037
4656
  try {
4038
- await unlink(m.path);
4657
+ await unlink2(m.path);
4039
4658
  deleted.push(m);
4040
4659
  log.debug(`cleaned expired memory ${m.frontmatter.id} (TTL expired)`);
4041
4660
  } catch {
@@ -4049,7 +4668,7 @@ ${memory.content}
4049
4668
  return deleted;
4050
4669
  }
4051
4670
  async loadBuffer() {
4052
- const bufferPath = path.join(this.stateDir, "buffer.json");
4671
+ const bufferPath = path3.join(this.stateDir, "buffer.json");
4053
4672
  try {
4054
4673
  const raw = await this.readStorageSecureFile(bufferPath);
4055
4674
  return JSON.parse(raw);
@@ -4061,11 +4680,11 @@ ${memory.content}
4061
4680
  }
4062
4681
  async saveBuffer(state) {
4063
4682
  await this.ensureDirectories();
4064
- const bufferPath = path.join(this.stateDir, "buffer.json");
4683
+ const bufferPath = path3.join(this.stateDir, "buffer.json");
4065
4684
  await this.writeStorageSecureFile(bufferPath, JSON.stringify(state, null, 2));
4066
4685
  }
4067
4686
  async loadMeta() {
4068
- const metaPath = path.join(this.stateDir, "meta.json");
4687
+ const metaPath = path3.join(this.stateDir, "meta.json");
4069
4688
  try {
4070
4689
  const raw = await this.readStorageSecureFile(metaPath);
4071
4690
  const parsed = JSON.parse(raw);
@@ -4099,7 +4718,7 @@ ${memory.content}
4099
4718
  }
4100
4719
  async saveMeta(state) {
4101
4720
  await this.ensureDirectories();
4102
- const metaPath = path.join(this.stateDir, "meta.json");
4721
+ const metaPath = path3.join(this.stateDir, "meta.json");
4103
4722
  await this.writeStorageSecureFile(metaPath, JSON.stringify(state, null, 2));
4104
4723
  }
4105
4724
  async appendMemoryActionEvents(events) {
@@ -4132,99 +4751,15 @@ ${memory.content}
4132
4751
  await this.appendStorageSecureFile(this.memoryLifecycleLedgerPath, payload);
4133
4752
  return events.length;
4134
4753
  }
4135
- /**
4136
- * Append a batch of `BUFFER_SURPRISE` telemetry events (issue #563 PR 3).
4137
- *
4138
- * Each event records a single buffer flush decision driven by the
4139
- * surprise gate. The ledger is consumed by
4140
- * `reportBufferSurpriseDistribution` (Doctor report) and by downstream
4141
- * benchmark analysis. This method is fire-and-forget by contract:
4142
- * callers log but do not fail the hot path if the append throws.
4143
- */
4144
4754
  async appendBufferSurpriseEvents(events) {
4145
- if (events.length === 0) return 0;
4146
- await this.ensureDirectories();
4147
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4148
- const payload = events.map((event) => {
4149
- const normalized = {
4150
- ...event,
4151
- event: "BUFFER_SURPRISE",
4152
- timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
4153
- };
4154
- return `${JSON.stringify(normalized)}
4155
- `;
4156
- }).join("");
4157
- await this.appendStorageSecureFile(this.bufferSurpriseLedgerPath, payload);
4158
- return events.length;
4755
+ return this.memoryReadStore.appendBufferSurpriseEvents(
4756
+ events
4757
+ );
4159
4758
  }
4160
- /**
4161
- * Read the buffer-surprise ledger, most recent rows last.
4162
- *
4163
- * `limit` bounds the number of **valid rows** returned (not the
4164
- * number of raw lines parsed). We parse every row, discard malformed
4165
- * ones, then take the tail — so a partial/truncated trailing line
4166
- * (the common failure mode after an interrupted append) cannot hide
4167
- * otherwise-valid recent data above it.
4168
- *
4169
- * Non-positive / non-integer / non-finite limits return `[]` rather
4170
- * than the entire file, matching the other ledger readers in this
4171
- * class and protecting against `slice(-0.5)` → `slice(-0)` silently
4172
- * devolving into an unbounded parse.
4173
- *
4174
- * # Performance note
4175
- *
4176
- * For very large ledgers (issue #563 follow-up), a tail-first reader
4177
- * would avoid parsing the full file when only a recent window is
4178
- * needed. We keep the full-scan implementation here because:
4179
- *
4180
- * - the ledger is opt-in (flag off by default), so early deployments
4181
- * accumulate rows slowly;
4182
- * - telemetry rows are small (~200 bytes), so even 100k rows parse
4183
- * in well under a second;
4184
- * - the governance archive/cleanup flow can trim the ledger when
4185
- * size becomes a concern, reusing the existing maintenance hooks.
4186
- *
4187
- * Swap to a chunked tail-reader if production logs show this is a
4188
- * hot path — leaving that work for a follow-up keeps this PR scoped
4189
- * to correctness, not optimization.
4190
- */
4191
4759
  async readBufferSurpriseEvents(options = {}) {
4192
- let raw;
4193
- try {
4194
- raw = await this.readStorageSecureFile(this.bufferSurpriseLedgerPath);
4195
- } catch (err) {
4196
- if (err instanceof SecureStoreLockedError) throw err;
4197
- const code = err.code;
4198
- if (code === "ENOENT") return [];
4199
- throw err;
4200
- }
4201
- let effectiveLimit = null;
4202
- if (options.limit !== void 0) {
4203
- if (typeof options.limit !== "number" || !Number.isFinite(options.limit) || options.limit <= 0) {
4204
- return [];
4205
- }
4206
- const floored = Math.floor(options.limit);
4207
- if (floored <= 0) return [];
4208
- effectiveLimit = floored;
4209
- }
4210
- const lines = raw.split("\n");
4211
- const events = [];
4212
- for (const line of lines) {
4213
- const trimmed = line.trim();
4214
- if (trimmed.length === 0) continue;
4215
- try {
4216
- const parsed = JSON.parse(trimmed);
4217
- if (isValidBufferSurpriseEvent(parsed)) {
4218
- events.push(parsed);
4219
- }
4220
- } catch {
4221
- }
4222
- }
4223
- events.sort(
4224
- (left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp)
4760
+ return this.memoryReadStore.readBufferSurpriseEvents(
4761
+ options
4225
4762
  );
4226
- if (effectiveLimit === null) return events;
4227
- return events.slice(-effectiveLimit);
4228
4763
  }
4229
4764
  async appendBehaviorSignals(events) {
4230
4765
  if (events.length === 0) return 0;
@@ -4269,7 +4804,7 @@ ${memory.content}
4269
4804
  async appendReextractJobs(events) {
4270
4805
  if (events.length === 0) return 0;
4271
4806
  await this.ensureDirectories();
4272
- const filePath = path.join(this.stateDir, "reextract-jobs.jsonl");
4807
+ const filePath = path3.join(this.stateDir, "reextract-jobs.jsonl");
4273
4808
  const lines = events.map((event) => JSON.stringify(event)).join("\n") + "\n";
4274
4809
  try {
4275
4810
  await this.appendStorageSecureFile(filePath, lines);
@@ -4282,7 +4817,7 @@ ${memory.content}
4282
4817
  }
4283
4818
  async readReextractJobs(limit = 200) {
4284
4819
  const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(1e3, Math.floor(limit))) : 200;
4285
- const filePath = path.join(this.stateDir, "reextract-jobs.jsonl");
4820
+ const filePath = path3.join(this.stateDir, "reextract-jobs.jsonl");
4286
4821
  try {
4287
4822
  const raw = await this.readStorageSecureFile(filePath);
4288
4823
  const lines = raw.split("\n").filter((line) => line.trim().length > 0);
@@ -4463,59 +4998,15 @@ ${memory.content}
4463
4998
  activationState: "active"
4464
4999
  });
4465
5000
  await Promise.all([
4466
- unlink(this.compressionGuidelineDraftPath).catch(() => void 0),
4467
- unlink(this.compressionGuidelineDraftStatePath).catch(() => void 0)
5001
+ unlink2(this.compressionGuidelineDraftPath).catch(() => void 0),
5002
+ unlink2(this.compressionGuidelineDraftStatePath).catch(() => void 0)
4468
5003
  ]);
4469
5004
  return true;
4470
5005
  }
4471
5006
  async readCompressionGuidelineStateFile(filePath) {
4472
- const isFiniteNonNegativeInteger = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
4473
- const isValidActionSummary = (value) => {
4474
- if (!value || typeof value !== "object") return false;
4475
- const summary = value;
4476
- 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);
4477
- };
4478
- const isValidRuleUpdate = (value) => {
4479
- if (!value || typeof value !== "object") return false;
4480
- const rule = value;
4481
- 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");
4482
- };
4483
- try {
4484
- const raw = await this.readStorageSecureFile(filePath);
4485
- const parsed = JSON.parse(raw);
4486
- const sourceWindow = parsed?.sourceWindow;
4487
- const eventCounts = parsed?.eventCounts;
4488
- const activationState = parsed?.activationState === "draft" || parsed?.activationState === "active" ? parsed.activationState : void 0;
4489
- const contentHash = typeof parsed?.contentHash === "string" && parsed.contentHash.length > 0 ? parsed.contentHash : void 0;
4490
- const actionSummaries = Array.isArray(parsed?.actionSummaries) ? parsed.actionSummaries.filter(isValidActionSummary) : void 0;
4491
- const ruleUpdates = Array.isArray(parsed?.ruleUpdates) ? parsed.ruleUpdates.filter(isValidRuleUpdate) : void 0;
4492
- 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)) {
4493
- return null;
4494
- }
4495
- return {
4496
- version: parsed.version,
4497
- updatedAt: parsed.updatedAt,
4498
- sourceWindow: {
4499
- from: sourceWindow.from,
4500
- to: sourceWindow.to
4501
- },
4502
- eventCounts: {
4503
- total: eventCounts.total,
4504
- applied: eventCounts.applied,
4505
- skipped: eventCounts.skipped,
4506
- failed: eventCounts.failed
4507
- },
4508
- guidelineVersion: parsed.guidelineVersion,
4509
- ...contentHash ? { contentHash } : {},
4510
- ...activationState ? { activationState } : {},
4511
- ...actionSummaries ? { actionSummaries } : {},
4512
- ...ruleUpdates ? { ruleUpdates } : {}
4513
- };
4514
- } catch (err) {
4515
- if (err instanceof SecureStoreLockedError) throw err;
4516
- if (!isErrnoCode(err, "ENOENT")) throw err;
4517
- return null;
4518
- }
5007
+ return this.memoryReadStore.readCompressionGuidelineStateFile(
5008
+ filePath
5009
+ );
4519
5010
  }
4520
5011
  async writeIdentityAnchor(content) {
4521
5012
  await this.ensureDirectories();
@@ -4536,7 +5027,7 @@ ${memory.content}
4536
5027
  const date = nowIso.slice(0, 10);
4537
5028
  const id = this.generateId("incident");
4538
5029
  const incident = createContinuityIncidentRecord(id, input, nowIso);
4539
- const filePath = path.join(this.identityIncidentsDir, `${date}-${id}.md`);
5030
+ const filePath = path3.join(this.identityIncidentsDir, `${date}-${id}.md`);
4540
5031
  await this.writeStorageSecureFile(filePath, serializeContinuityIncident(incident));
4541
5032
  return { ...incident, filePath };
4542
5033
  }
@@ -4549,7 +5040,7 @@ ${memory.content}
4549
5040
  const incidents = [];
4550
5041
  for (const file of candidates) {
4551
5042
  if (incidents.length >= cappedLimit) break;
4552
- const filePath = path.join(this.identityIncidentsDir, file);
5043
+ const filePath = path3.join(this.identityIncidentsDir, file);
4553
5044
  try {
4554
5045
  const raw = await this.readStorageSecureFile(filePath);
4555
5046
  const parsed = parseContinuityIncident(raw);
@@ -4580,7 +5071,7 @@ ${memory.content}
4580
5071
  await this.ensureDirectories();
4581
5072
  const safeKey = this.sanitizeIdentityAuditKey(key);
4582
5073
  const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
4583
- const filePath = path.join(dir, `${safeKey}.md`);
5074
+ const filePath = path3.join(dir, `${safeKey}.md`);
4584
5075
  await this.writeStorageSecureFile(filePath, content);
4585
5076
  return filePath;
4586
5077
  }
@@ -4588,7 +5079,7 @@ ${memory.content}
4588
5079
  try {
4589
5080
  const safeKey = this.sanitizeIdentityAuditKey(key);
4590
5081
  const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
4591
- return await this.readStorageSecureFile(path.join(dir, `${safeKey}.md`));
5082
+ return await this.readStorageSecureFile(path3.join(dir, `${safeKey}.md`));
4592
5083
  } catch (err) {
4593
5084
  if (err instanceof Error && err.message === "Invalid identity audit key") return null;
4594
5085
  if (!isErrnoCode(err, "ENOENT")) throw err;
@@ -4635,7 +5126,7 @@ ${memory.content}
4635
5126
  return `${prefix}-${ts}-${rand}`;
4636
5127
  }
4637
5128
  async readContinuityIncidentFileNames() {
4638
- const files = await readdir(this.identityIncidentsDir);
5129
+ const files = await readdir3(this.identityIncidentsDir);
4639
5130
  return files.filter((file) => file.endsWith(".md")).sort().reverse();
4640
5131
  }
4641
5132
  async readContinuityIncidentFile(filePath) {
@@ -4652,12 +5143,12 @@ ${memory.content}
4652
5143
  const fileNames = await this.readContinuityIncidentFileNames();
4653
5144
  const directMatch = fileNames.find((name) => name.endsWith(`-${id}.md`));
4654
5145
  if (directMatch) {
4655
- const directPath = path.join(this.identityIncidentsDir, directMatch);
5146
+ const directPath = path3.join(this.identityIncidentsDir, directMatch);
4656
5147
  const parsed = await this.readContinuityIncidentFile(directPath);
4657
5148
  if (parsed?.id === id) return directPath;
4658
5149
  }
4659
5150
  for (const fileName of fileNames) {
4660
- const filePath = path.join(this.identityIncidentsDir, fileName);
5151
+ const filePath = path3.join(this.identityIncidentsDir, fileName);
4661
5152
  const parsed = await this.readContinuityIncidentFile(filePath);
4662
5153
  if (parsed?.id === id) return filePath;
4663
5154
  }
@@ -4687,43 +5178,16 @@ ${question}
4687
5178
 
4688
5179
  **Context:** ${context}
4689
5180
  `;
4690
- const filePath = path.join(this.questionsDir, `${id}.md`);
5181
+ const filePath = path3.join(this.questionsDir, `${id}.md`);
4691
5182
  await this.writeStorageSecureFile(filePath, content);
4692
5183
  log.debug(`wrote question ${id} to ${filePath}`);
4693
5184
  this.invalidateQuestionsCache();
4694
5185
  return id;
4695
5186
  }
4696
5187
  async readQuestions(opts) {
4697
- const cacheKey = this.questionsDir;
4698
- const cached = _StorageManager.questionsCache.get(cacheKey);
4699
- if (cached && Date.now() - cached.loadedAt < _StorageManager.QUESTIONS_CACHE_TTL_MS) {
4700
- try {
4701
- const dirStat = await stat(this.questionsDir);
4702
- if (dirStat.mtimeMs <= cached.loadedAt) {
4703
- const all = cached.questions;
4704
- return opts?.unresolvedOnly ? all.filter((q) => !q.resolved) : all;
4705
- }
4706
- } catch {
4707
- }
4708
- }
4709
- try {
4710
- const files = await readdir(this.questionsDir);
4711
- const questions = [];
4712
- for (const file of files) {
4713
- if (!file.endsWith(".md")) continue;
4714
- const filePath = path.join(this.questionsDir, file);
4715
- const raw = await readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
4716
- const parsed = this.parseQuestionFile(raw, filePath);
4717
- if (parsed) {
4718
- questions.push(parsed);
4719
- }
4720
- }
4721
- const sorted = questions.sort((a, b) => b.priority - a.priority);
4722
- _StorageManager.questionsCache.set(cacheKey, { questions: sorted, loadedAt: Date.now() });
4723
- return opts?.unresolvedOnly ? sorted.filter((q) => !q.resolved) : sorted;
4724
- } catch {
4725
- return [];
4726
- }
5188
+ return this.memoryReadStore.readQuestions(
5189
+ opts
5190
+ );
4727
5191
  }
4728
5192
  /** Invalidate the questions cache (call after writing a question). */
4729
5193
  invalidateQuestionsCache() {
@@ -4734,7 +5198,7 @@ ${question}
4734
5198
  if (!match) return null;
4735
5199
  const frontmatterStr = match[1];
4736
5200
  const body = match[2].trim();
4737
- const id = this.extractFrontmatterValue(frontmatterStr, "id") ?? path.basename(filePath, ".md");
5201
+ const id = this.extractFrontmatterValue(frontmatterStr, "id") ?? path3.basename(filePath, ".md");
4738
5202
  const created = this.extractFrontmatterValue(frontmatterStr, "created") ?? "";
4739
5203
  const priority = parseFloat(
4740
5204
  this.extractFrontmatterValue(frontmatterStr, "priority") ?? "0.5"
@@ -4796,11 +5260,11 @@ ${question}
4796
5260
  } catch {
4797
5261
  }
4798
5262
  const hygiene = opts?.hygiene;
4799
- const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(path.basename(identityPath));
5263
+ const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(path3.basename(identityPath));
4800
5264
  if (rotateEnabled) {
4801
5265
  const maxBytes = hygiene.rotateMaxBytes;
4802
5266
  if (existing.length > maxBytes) {
4803
- const archiveDir = path.join(workspaceDir, hygiene.archiveDir);
5267
+ const archiveDir = path3.join(workspaceDir, hygiene.archiveDir);
4804
5268
  const { newContent } = await rotateMarkdownFileToArchive({
4805
5269
  filePath: identityPath,
4806
5270
  archiveDir,
@@ -4894,7 +5358,7 @@ ${reflection}
4894
5358
  * Deduplicates by target+label.
4895
5359
  */
4896
5360
  async addEntityRelationship(name, rel) {
4897
- const filePath = path.join(this.entitiesDir, `${name}.md`);
5361
+ const filePath = path3.join(this.entitiesDir, `${name}.md`);
4898
5362
  let entity;
4899
5363
  try {
4900
5364
  const content = await this.readStorageSecureFile(filePath);
@@ -4919,7 +5383,7 @@ ${reflection}
4919
5383
  * Prepends to the beginning, prunes oldest entries beyond maxEntries.
4920
5384
  */
4921
5385
  async addEntityActivity(name, entry, maxEntries) {
4922
- const filePath = path.join(this.entitiesDir, `${name}.md`);
5386
+ const filePath = path3.join(this.entitiesDir, `${name}.md`);
4923
5387
  let entity;
4924
5388
  try {
4925
5389
  const content = await this.readStorageSecureFile(filePath);
@@ -4942,7 +5406,7 @@ ${reflection}
4942
5406
  * Add an alias to an entity file. Deduplicates.
4943
5407
  */
4944
5408
  async addEntityAlias(name, alias) {
4945
- const filePath = path.join(this.entitiesDir, `${name}.md`);
5409
+ const filePath = path3.join(this.entitiesDir, `${name}.md`);
4946
5410
  let entity;
4947
5411
  try {
4948
5412
  const content = await this.readStorageSecureFile(filePath);
@@ -4959,40 +5423,12 @@ ${reflection}
4959
5423
  await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
4960
5424
  this.invalidateKnowledgeIndexCache();
4961
5425
  }
4962
- /**
4963
- * Set or rewrite the synthesis layer of an entity file.
4964
- */
4965
5426
  async updateEntitySynthesis(name, synthesis, options = {}) {
4966
- const filePath = path.join(this.entitiesDir, `${name}.md`);
4967
- let entity;
4968
- try {
4969
- const content = await this.readStorageSecureFile(filePath);
4970
- entity = parseEntityFile(content, this.entitySchemas);
4971
- } catch (err) {
4972
- if (err instanceof SecureStoreLockedError) throw err;
4973
- if (!isErrnoCode(err, "ENOENT")) throw err;
4974
- log.debug(`updateEntitySynthesis: entity file ${name}.md not found`);
4975
- return;
4976
- }
4977
- const updatedAt = options.updatedAt?.trim() || entity.synthesisUpdatedAt?.trim() || void 0;
4978
- const entityUpdatedAt = options.entityUpdatedAt?.trim() || updatedAt || entity.updated || (/* @__PURE__ */ new Date()).toISOString();
4979
- const synthesisTimelineCount = Number.isInteger(options.synthesisTimelineCount) && (options.synthesisTimelineCount ?? 0) >= 0 ? options.synthesisTimelineCount : void 0;
4980
- const synthesisStructuredFactCount = Number.isInteger(options.synthesisStructuredFactCount) && (options.synthesisStructuredFactCount ?? 0) >= 0 ? options.synthesisStructuredFactCount : countEntityStructuredFacts(entity);
4981
- const synthesisStructuredFactDigest = options.synthesisStructuredFactDigest?.trim() || fingerprintEntityStructuredFacts(entity);
4982
- entity.synthesis = synthesis.trim();
4983
- entity.summary = entity.synthesis;
4984
- entity.synthesisUpdatedAt = updatedAt;
4985
- entity.synthesisTimelineCount = synthesisTimelineCount;
4986
- entity.synthesisStructuredFactCount = synthesisStructuredFactCount;
4987
- entity.synthesisStructuredFactDigest = synthesisStructuredFactDigest;
4988
- entity.synthesisVersion = Math.max(0, entity.synthesisVersion ?? 0) + (options.incrementVersion === false ? 0 : 1);
4989
- entity.updated = entityUpdatedAt;
4990
- await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
4991
- await this.removeEntitySynthesisQueueEntries([
4992
- .../* @__PURE__ */ new Set([name, this.normalizeEntityName(entity.name, entity.type)])
4993
- ]);
4994
- this.invalidateKnowledgeIndexCache();
4995
- this.bumpMemoryStatusVersion();
5427
+ return this.entityStore.updateEntitySynthesis(
5428
+ name,
5429
+ synthesis,
5430
+ options
5431
+ );
4996
5432
  }
4997
5433
  /**
4998
5434
  * Backward-compatible alias for legacy callers/tests.
@@ -5001,7 +5437,7 @@ ${reflection}
5001
5437
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5002
5438
  let synthesisTimelineCount;
5003
5439
  try {
5004
- const filePath = path.join(this.entitiesDir, `${name}.md`);
5440
+ const filePath = path3.join(this.entitiesDir, `${name}.md`);
5005
5441
  const content = await this.readStorageSecureFile(filePath);
5006
5442
  synthesisTimelineCount = parseEntityFile(content, this.entitySchemas).timeline.length;
5007
5443
  } catch (err) {
@@ -5084,7 +5520,7 @@ ${reflection}
5084
5520
  if (!raw) continue;
5085
5521
  const serialized = serializeEntityFile(parseEntityFile(raw, this.entitySchemas), this.entitySchemas);
5086
5522
  if (raw.trimEnd() === serialized.trimEnd()) continue;
5087
- await this.writeStorageSecureFile(path.join(this.entitiesDir, `${entityName}.md`), serialized);
5523
+ await this.writeStorageSecureFile(path3.join(this.entitiesDir, `${entityName}.md`), serialized);
5088
5524
  migrated += 1;
5089
5525
  }
5090
5526
  if (migrated > 0) {
@@ -5099,45 +5535,8 @@ ${reflection}
5099
5535
  // ---------------------------------------------------------------------------
5100
5536
  // Scoring + Knowledge Index (Knowledge Graph v7.0)
5101
5537
  // ---------------------------------------------------------------------------
5102
- /**
5103
- * Read all entity files and return lightweight EntityFile objects.
5104
- * Parsing is fast (~50-100ms for ~1,800 files) since entity files are small.
5105
- */
5106
5538
  async readAllEntityFiles() {
5107
- const currentVersion = this.getMemoryStatusVersion();
5108
- const schemaCacheKey = buildEntitySchemaCacheKey(this.entitySchemas);
5109
- const cacheKey = `${this.getEntityCacheSecureStoreKey()}\0${schemaCacheKey}`;
5110
- const cached = getCachedEntities(this.baseDir, currentVersion, cacheKey);
5111
- if (cached) return cached;
5112
- try {
5113
- const entries = await readdir(this.entitiesDir);
5114
- const mdFiles = entries.filter((e) => e.endsWith(".md"));
5115
- if (mdFiles.length === 0) return [];
5116
- const BATCH_SIZE = 100;
5117
- const entities = [];
5118
- for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {
5119
- const batch = mdFiles.slice(i, i + BATCH_SIZE);
5120
- const results = await Promise.all(
5121
- batch.map(async (entry) => {
5122
- try {
5123
- return await this.readStorageSecureFile(path.join(this.entitiesDir, entry));
5124
- } catch (err) {
5125
- if (err instanceof SecureStoreLockedError) throw err;
5126
- if (!isErrnoCode(err, "ENOENT")) throw err;
5127
- return null;
5128
- }
5129
- })
5130
- );
5131
- for (const content of results) {
5132
- if (content !== null) entities.push(parseEntityFile(content, this.entitySchemas));
5133
- }
5134
- }
5135
- setCachedEntities(this.baseDir, entities, currentVersion, cacheKey);
5136
- return entities;
5137
- } catch (err) {
5138
- if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
5139
- return [];
5140
- }
5539
+ return this.entityStore.readAllEntityFiles();
5141
5540
  }
5142
5541
  /**
5143
5542
  * Score an entity based on recency, frequency, activity, type priority,
@@ -5163,53 +5562,11 @@ ${reflection}
5163
5562
  const relDensity = Math.min(entity.relationships.length / 8, 1);
5164
5563
  return recency * 0.4 + frequency * 0.25 + activityScore * 0.15 + typePriority * 0.1 + relDensity * 0.1;
5165
5564
  }
5166
- /**
5167
- * Build the Knowledge Index: a compact markdown table of top-scored entities.
5168
- * Respects maxEntities and maxChars limits from config.
5169
- */
5170
5565
  async buildKnowledgeIndex(config, overrides) {
5171
- const useDefaultLimits = overrides?.maxEntities === void 0 && overrides?.maxChars === void 0;
5172
- if (useDefaultLimits && this.knowledgeIndexCache && Date.now() - this.knowledgeIndexCache.builtAt < _StorageManager.KNOWLEDGE_INDEX_CACHE_TTL_MS) {
5173
- return { result: this.knowledgeIndexCache.result, cached: true };
5174
- }
5175
- const entities = await this.readAllEntityFiles();
5176
- if (entities.length === 0) {
5177
- if (useDefaultLimits) this.knowledgeIndexCache = { result: "", builtAt: Date.now() };
5178
- return { result: "", cached: false };
5179
- }
5180
- const now = /* @__PURE__ */ new Date();
5181
- const scored = entities.map((e) => ({
5182
- name: e.name,
5183
- type: e.type,
5184
- score: _StorageManager.scoreEntity(e, now),
5185
- factCount: e.facts.length,
5186
- summary: e.synthesis ?? e.summary,
5187
- topRelationships: e.relationships.slice(0, 3).map((r) => r.target)
5188
- }));
5189
- scored.sort((a, b) => b.score - a.score);
5190
- const maxEntities = typeof overrides?.maxEntities === "number" ? Math.max(0, Math.floor(overrides.maxEntities)) : config.knowledgeIndexMaxEntities;
5191
- const topN = scored.slice(0, maxEntities);
5192
- if (topN.length === 0) {
5193
- if (useDefaultLimits) this.knowledgeIndexCache = { result: "", builtAt: Date.now() };
5194
- return { result: "", cached: false };
5195
- }
5196
- const header = "## Knowledge Index\n\n| Entity | Type | Summary | Connected to |\n|--------|------|---------|-------------|";
5197
- const rows = [];
5198
- let totalChars = header.length;
5199
- const maxChars = typeof overrides?.maxChars === "number" ? Math.max(0, Math.floor(overrides.maxChars)) : config.knowledgeIndexMaxChars;
5200
- for (const entity of topN) {
5201
- const summary = entity.summary || `${entity.factCount} facts`;
5202
- const connected = entity.topRelationships.length > 0 ? entity.topRelationships.join(", ") : "\u2014";
5203
- const row = `| ${entity.name} | ${entity.type} | ${summary} | ${connected} |`;
5204
- if (totalChars + row.length + 1 > maxChars) break;
5205
- rows.push(row);
5206
- totalChars += row.length + 1;
5207
- }
5208
- const result = rows.length === 0 ? "" : `${header}
5209
- ${rows.join("\n")}
5210
- `;
5211
- if (useDefaultLimits) this.knowledgeIndexCache = { result, builtAt: Date.now() };
5212
- return { result, cached: false };
5566
+ return this.entityStore.buildKnowledgeIndex(
5567
+ config,
5568
+ overrides
5569
+ );
5213
5570
  }
5214
5571
  /** Invalidate the Knowledge Index cache (call after entity mutations). */
5215
5572
  invalidateKnowledgeIndexCache() {
@@ -5220,201 +5577,8 @@ ${rows.join("\n")}
5220
5577
  // ---------------------------------------------------------------------------
5221
5578
  /** Max lines for profile.md before LLM consolidation triggers */
5222
5579
  static PROFILE_MAX_LINES = 300;
5223
- /**
5224
- * Merge fragmented entity files that resolve to the same canonical name.
5225
- * Preserves relationships, activity, aliases, and summary from all fragments.
5226
- * Returns count of files merged.
5227
- */
5228
5580
  async mergeFragmentedEntities() {
5229
- let merged = 0;
5230
- try {
5231
- const entries = await readdir(this.entitiesDir);
5232
- const mdFiles = entries.filter((e) => e.endsWith(".md"));
5233
- const groups = /* @__PURE__ */ new Map();
5234
- for (const file of mdFiles) {
5235
- const baseName = file.replace(".md", "");
5236
- const dashIdx = baseName.indexOf("-");
5237
- if (dashIdx === -1) continue;
5238
- const type = baseName.slice(0, dashIdx);
5239
- const restOfName = baseName.slice(dashIdx + 1);
5240
- const canonical = this.normalizeEntityName(restOfName, type);
5241
- if (!groups.has(canonical)) groups.set(canonical, []);
5242
- groups.get(canonical).push(file);
5243
- }
5244
- for (const [canonical, files] of groups) {
5245
- if (files.length <= 1) continue;
5246
- const mergedEntity = {
5247
- name: "",
5248
- type: "other",
5249
- created: "",
5250
- updated: "",
5251
- extraFrontmatterLines: [],
5252
- preSectionLines: [],
5253
- facts: [],
5254
- summary: void 0,
5255
- synthesis: void 0,
5256
- synthesisUpdatedAt: void 0,
5257
- synthesisTimelineCount: void 0,
5258
- synthesisStructuredFactCount: void 0,
5259
- synthesisStructuredFactDigest: void 0,
5260
- synthesisVersion: void 0,
5261
- timeline: [],
5262
- relationships: [],
5263
- activity: [],
5264
- aliases: [],
5265
- structuredSections: [],
5266
- extraSections: []
5267
- };
5268
- for (const file of files) {
5269
- const filePath = path.join(this.entitiesDir, file);
5270
- try {
5271
- const content = await this.readStorageSecureFile(filePath);
5272
- const parsed = parseEntityFile(content, this.entitySchemas);
5273
- if (!mergedEntity.type || mergedEntity.type === "other") {
5274
- mergedEntity.type = parsed.type;
5275
- }
5276
- if (!mergedEntity.updated || compareEntityTimestamps(parsed.updated, mergedEntity.updated) > 0) {
5277
- mergedEntity.updated = parsed.updated;
5278
- }
5279
- const parsedCreated = parsed.created || parsed.updated;
5280
- const mergedCreated = mergedEntity.created?.trim() || "";
5281
- const parsedCreatedMs = parsedCreated ? Date.parse(parsedCreated) : Number.NaN;
5282
- const mergedCreatedMs = mergedCreated ? Date.parse(mergedCreated) : Number.NaN;
5283
- const parsedCreatedIsValid = Number.isFinite(parsedCreatedMs);
5284
- const mergedCreatedIsValid = Number.isFinite(mergedCreatedMs);
5285
- if (parsedCreated && (!mergedCreated || parsedCreatedIsValid && !mergedCreatedIsValid || parsedCreatedIsValid && mergedCreatedIsValid && parsedCreatedMs < mergedCreatedMs || !parsedCreatedIsValid && !mergedCreatedIsValid && compareEntityTimestamps(parsedCreated, mergedCreated) < 0)) {
5286
- mergedEntity.created = parsedCreated;
5287
- }
5288
- if (parsed.name.length > mergedEntity.name.length) {
5289
- mergedEntity.name = parsed.name;
5290
- }
5291
- const parsedSynthesisUpdatedAt = parsed.synthesisUpdatedAt?.trim() || void 0;
5292
- const mergedSynthesisUpdatedAt = mergedEntity.synthesisUpdatedAt?.trim() || void 0;
5293
- if (parsed.synthesis && (!mergedEntity.synthesis || !mergedSynthesisUpdatedAt && Boolean(parsedSynthesisUpdatedAt) || Boolean(mergedSynthesisUpdatedAt) && Boolean(parsedSynthesisUpdatedAt) && compareEntityTimestamps(parsedSynthesisUpdatedAt, mergedSynthesisUpdatedAt) > 0)) {
5294
- mergedEntity.synthesis = parsed.synthesis;
5295
- mergedEntity.summary = parsed.synthesis;
5296
- mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
5297
- mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
5298
- mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
5299
- mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
5300
- mergedEntity.synthesisVersion = parsed.synthesisVersion;
5301
- } else if (!mergedEntity.summary && parsed.summary) {
5302
- mergedEntity.summary = parsed.summary;
5303
- mergedEntity.synthesis = parsed.summary;
5304
- mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
5305
- mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
5306
- mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
5307
- mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
5308
- mergedEntity.synthesisVersion = parsed.synthesisVersion;
5309
- }
5310
- mergedEntity.timeline.push(...parsed.timeline);
5311
- mergedEntity.relationships.push(...parsed.relationships);
5312
- mergedEntity.activity.push(...parsed.activity);
5313
- mergedEntity.aliases.push(...parsed.aliases);
5314
- const mergedStructuredSectionMap = new Map(
5315
- (mergedEntity.structuredSections ?? []).map((section) => [section.key, {
5316
- ...section,
5317
- facts: [...section.facts]
5318
- }])
5319
- );
5320
- for (const section of parsed.structuredSections ?? []) {
5321
- const existingSection = mergedStructuredSectionMap.get(section.key);
5322
- if (!existingSection) {
5323
- mergedStructuredSectionMap.set(section.key, {
5324
- key: section.key,
5325
- title: section.title,
5326
- facts: [...new Set(section.facts.map((fact) => fact.trim()).filter((fact) => fact.length > 0))]
5327
- });
5328
- continue;
5329
- }
5330
- const mergedFacts = new Set(existingSection.facts.map((fact) => fact.trim()));
5331
- for (const fact of section.facts) {
5332
- const trimmed = fact.trim();
5333
- if (!trimmed) continue;
5334
- mergedFacts.add(trimmed);
5335
- }
5336
- existingSection.facts = Array.from(mergedFacts);
5337
- if (!existingSection.title.trim() && section.title.trim()) {
5338
- existingSection.title = section.title;
5339
- }
5340
- }
5341
- mergedEntity.structuredSections = Array.from(mergedStructuredSectionMap.values());
5342
- mergedEntity.extraFrontmatterLines.push(...parsed.extraFrontmatterLines ?? []);
5343
- mergedEntity.preSectionLines.push(...parsed.preSectionLines ?? []);
5344
- mergedEntity.extraSections.push(...(parsed.extraSections ?? []).map((section) => ({
5345
- title: section.title,
5346
- lines: [...section.lines]
5347
- })));
5348
- } catch (err) {
5349
- if (err instanceof SecureStoreLockedError) throw err;
5350
- if (!isErrnoCode(err, "ENOENT")) throw err;
5351
- }
5352
- }
5353
- const timelineKeys = /* @__PURE__ */ new Set();
5354
- mergedEntity.timeline = mergedEntity.timeline.filter((entry) => {
5355
- const key = JSON.stringify([
5356
- entry.timestamp,
5357
- entry.source ?? "",
5358
- entry.sessionKey ?? "",
5359
- entry.principal ?? "",
5360
- entry.text
5361
- ]);
5362
- if (timelineKeys.has(key)) return false;
5363
- timelineKeys.add(key);
5364
- return true;
5365
- });
5366
- const relKeys = /* @__PURE__ */ new Set();
5367
- mergedEntity.relationships = mergedEntity.relationships.filter((r) => {
5368
- const key = `${r.target}::${r.label}`;
5369
- if (relKeys.has(key)) return false;
5370
- relKeys.add(key);
5371
- return true;
5372
- });
5373
- const actKeys = /* @__PURE__ */ new Set();
5374
- mergedEntity.activity = mergedEntity.activity.filter((a) => {
5375
- const key = `${a.date}::${a.note}`;
5376
- if (actKeys.has(key)) return false;
5377
- actKeys.add(key);
5378
- return true;
5379
- }).sort((a, b) => b.date.localeCompare(a.date));
5380
- mergedEntity.aliases = [...new Set(mergedEntity.aliases)];
5381
- mergedEntity.structuredSections = sortStructuredSectionsBySchema(
5382
- mergedEntity.type,
5383
- mergedEntity.structuredSections ?? [],
5384
- this.entitySchemas
5385
- );
5386
- mergedEntity.facts = compileEntityFacts(mergedEntity.timeline, mergedEntity.structuredSections);
5387
- const extraSectionKeys = /* @__PURE__ */ new Set();
5388
- mergedEntity.extraSections = (mergedEntity.extraSections ?? []).filter((section) => {
5389
- const key = `${section.title}::${section.lines.join("\n")}`;
5390
- if (extraSectionKeys.has(key)) return false;
5391
- extraSectionKeys.add(key);
5392
- return true;
5393
- });
5394
- if (!mergedEntity.name) {
5395
- const dashIdx = canonical.indexOf("-");
5396
- mergedEntity.name = dashIdx !== -1 ? canonical.slice(dashIdx + 1) : canonical;
5397
- }
5398
- mergedEntity.created = mergedEntity.created || mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
5399
- mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
5400
- const canonicalPath = path.join(this.entitiesDir, `${canonical}.md`);
5401
- await this.writeStorageSecureFile(canonicalPath, serializeEntityFile(mergedEntity, this.entitySchemas));
5402
- for (const file of files) {
5403
- const filePath = path.join(this.entitiesDir, file);
5404
- if (filePath !== canonicalPath) {
5405
- try {
5406
- await unlink(filePath);
5407
- merged++;
5408
- log.debug(`merged entity ${file} \u2192 ${canonical}.md`);
5409
- } catch {
5410
- }
5411
- }
5412
- }
5413
- }
5414
- } catch (err) {
5415
- if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
5416
- }
5417
- return merged;
5581
+ return this.entityStore.mergeFragmentedEntities();
5418
5582
  }
5419
5583
  async cleanExpiredCommitments(decayDays) {
5420
5584
  const memories = await this.readAllMemories();
@@ -5429,7 +5593,7 @@ ${rows.join("\n")}
5429
5593
  const updatedAt = new Date(m.frontmatter.updated).getTime();
5430
5594
  if (updatedAt < cutoff) {
5431
5595
  try {
5432
- await unlink(m.path);
5596
+ await unlink2(m.path);
5433
5597
  deleted.push(m);
5434
5598
  log.debug(`cleaned expired commitment ${m.frontmatter.id}`);
5435
5599
  } catch {
@@ -5499,7 +5663,7 @@ ${memory.content}
5499
5663
  const filePaths = await this.collectActiveMemoryPaths();
5500
5664
  const foundIds = /* @__PURE__ */ new Set();
5501
5665
  for (const filePath of filePaths) {
5502
- const basename = path.basename(filePath, ".md");
5666
+ const basename = path3.basename(filePath, ".md");
5503
5667
  if (wantedIds.has(basename)) {
5504
5668
  foundIds.add(basename);
5505
5669
  if (foundIds.size === wantedIds.size) break;
@@ -5689,14 +5853,14 @@ Reason: ${reason}`, {
5689
5853
  // Memory Summarization (Phase 4A)
5690
5854
  // ---------------------------------------------------------------------------
5691
5855
  get summariesDir() {
5692
- return path.join(this.baseDir, "summaries");
5856
+ return path3.join(this.baseDir, "summaries");
5693
5857
  }
5694
5858
  /**
5695
5859
  * Write a memory summary.
5696
5860
  */
5697
5861
  async writeSummary(summary) {
5698
5862
  await mkdir(this.summariesDir, { recursive: true });
5699
- const filePath = path.join(this.summariesDir, `${summary.id}.json`);
5863
+ const filePath = path3.join(this.summariesDir, `${summary.id}.json`);
5700
5864
  await this.writeStorageSecureFile(filePath, JSON.stringify(summary, null, 2));
5701
5865
  log.debug(`wrote summary ${summary.id}`);
5702
5866
  }
@@ -5705,11 +5869,11 @@ Reason: ${reason}`, {
5705
5869
  */
5706
5870
  async readSummaries() {
5707
5871
  try {
5708
- const files = await readdir(this.summariesDir);
5872
+ const files = await readdir3(this.summariesDir);
5709
5873
  const summaries = [];
5710
5874
  for (const file of files) {
5711
5875
  if (!file.endsWith(".json")) continue;
5712
- const filePath = path.join(this.summariesDir, file);
5876
+ const filePath = path3.join(this.summariesDir, file);
5713
5877
  const raw = await this.readStorageSecureFile(filePath);
5714
5878
  summaries.push(JSON.parse(raw));
5715
5879
  }
@@ -5770,7 +5934,7 @@ ${memory.content}
5770
5934
  * Save topic scores to meta.json.
5771
5935
  */
5772
5936
  async saveTopics(topics) {
5773
- const metaPath = path.join(this.stateDir, "topics.json");
5937
+ const metaPath = path3.join(this.stateDir, "topics.json");
5774
5938
  await mkdir(this.stateDir, { recursive: true });
5775
5939
  await this.writeStorageSecureFile(metaPath, JSON.stringify({ topics, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
5776
5940
  log.debug(`saved ${topics.length} topic scores`);
@@ -5779,7 +5943,7 @@ ${memory.content}
5779
5943
  * Load topic scores from meta.json.
5780
5944
  */
5781
5945
  async loadTopics() {
5782
- const metaPath = path.join(this.stateDir, "topics.json");
5946
+ const metaPath = path3.join(this.stateDir, "topics.json");
5783
5947
  try {
5784
5948
  const raw = await this.readStorageSecureFile(metaPath);
5785
5949
  return JSON.parse(raw);
@@ -5857,14 +6021,23 @@ ${memory.content}
5857
6021
  };
5858
6022
 
5859
6023
  export {
6024
+ selfDeps,
6025
+ parseFrontmatterStringValue,
6026
+ parseFrontmatter,
6027
+ normalizeFrontmatterForPath,
5860
6028
  normalizeEntityName,
5861
6029
  ContentHashIndex,
5862
6030
  normalizeAttributePairs,
6031
+ normalizeStructuredSectionFacts,
6032
+ compileEntityFacts,
5863
6033
  compareEntityTimestamps,
6034
+ countEntityStructuredFacts,
5864
6035
  fingerprintEntityStructuredFacts,
5865
6036
  isEntitySynthesisStale,
5866
6037
  parseEntityFile,
5867
6038
  serializeEntityFile,
5868
- StorageManager
6039
+ buildEntitySchemaCacheKey,
6040
+ isValidBufferSurpriseEvent,
6041
+ StorageManager3 as StorageManager
5869
6042
  };
5870
- //# sourceMappingURL=chunk-THZ4WR5L.js.map
6043
+ //# sourceMappingURL=chunk-7APVFQ34.js.map