@remnic/core 1.0.2 → 1.0.3

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 (347) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/dist/access-cli.d.ts +13 -3
  4. package/dist/access-cli.js +90 -75
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-http.d.ts +10 -3
  7. package/dist/access-http.js +25 -18
  8. package/dist/access-mcp.d.ts +30 -3
  9. package/dist/access-mcp.js +16 -1
  10. package/dist/access-schema.d.ts +12 -12
  11. package/dist/access-schema.js +1 -1
  12. package/dist/access-service.d.ts +65 -4
  13. package/dist/access-service.js +21 -15
  14. package/dist/active-memory-bridge.d.ts +66 -0
  15. package/dist/active-memory-bridge.js +11 -0
  16. package/dist/active-recall.d.ts +96 -0
  17. package/dist/active-recall.js +308 -0
  18. package/dist/active-recall.js.map +1 -0
  19. package/dist/behavior-learner.js +1 -1
  20. package/dist/bootstrap.d.ts +6 -3
  21. package/dist/bootstrap.js +2 -2
  22. package/dist/boxes.js +2 -2
  23. package/dist/briefing.d.ts +169 -0
  24. package/dist/briefing.js +52 -0
  25. package/dist/briefing.js.map +1 -0
  26. package/dist/buffer.d.ts +19 -5
  27. package/dist/buffer.js +2 -2
  28. package/dist/calibration.js +6 -6
  29. package/dist/causal-behavior.js +5 -5
  30. package/dist/causal-chain.js +3 -3
  31. package/dist/causal-consolidation.d.ts +22 -2
  32. package/dist/causal-consolidation.js +36 -9
  33. package/dist/causal-consolidation.js.map +1 -1
  34. package/dist/causal-retrieval.js +6 -6
  35. package/dist/causal-trajectory-graph.js +1 -1
  36. package/dist/causal-trajectory.d.ts +14 -1
  37. package/dist/causal-trajectory.js +5 -1
  38. package/dist/{chunk-KWBU5S5U.js → chunk-2ODBA7MQ.js} +9 -3
  39. package/dist/chunk-2ODBA7MQ.js.map +1 -0
  40. package/dist/{chunk-6UJQNRIO.js → chunk-2VFW5K5U.js} +93 -36
  41. package/dist/chunk-2VFW5K5U.js.map +1 -0
  42. package/dist/chunk-3PG3H5TD.js +7 -0
  43. package/dist/chunk-3PG3H5TD.js.map +1 -0
  44. package/dist/{chunk-NTTLPF7F.js → chunk-3QFQGRHO.js} +5 -5
  45. package/dist/chunk-4DJQYKMN.js +187 -0
  46. package/dist/chunk-4DJQYKMN.js.map +1 -0
  47. package/dist/chunk-4KAN3GZ3.js +225 -0
  48. package/dist/chunk-4KAN3GZ3.js.map +1 -0
  49. package/dist/{chunk-ORZMT74A.js → chunk-4NRAJUDS.js} +11 -1
  50. package/dist/chunk-4NRAJUDS.js.map +1 -0
  51. package/dist/{chunk-B7LOFDVE.js → chunk-4WMCPJWX.js} +8 -3
  52. package/dist/chunk-4WMCPJWX.js.map +1 -0
  53. package/dist/{chunk-G3AG3KZN.js → chunk-5IZL4DCV.js} +2 -2
  54. package/dist/{chunk-BRK4ODMI.js → chunk-5NPGSAVB.js} +2 -2
  55. package/dist/chunk-6MKAMLQL.js +16 -0
  56. package/dist/chunk-6MKAMLQL.js.map +1 -0
  57. package/dist/{chunk-ESSMF2FR.js → chunk-6PFRXT4K.js} +15 -6
  58. package/dist/chunk-6PFRXT4K.js.map +1 -0
  59. package/dist/chunk-6ZH4TU6I.js +245 -0
  60. package/dist/chunk-6ZH4TU6I.js.map +1 -0
  61. package/dist/{chunk-V4YC4LUK.js → chunk-74JR4N5J.js} +175 -63
  62. package/dist/chunk-74JR4N5J.js.map +1 -0
  63. package/dist/{chunk-L5RPWGFK.js → chunk-7DHTMOND.js} +2 -2
  64. package/dist/{chunk-TVVVQQAK.js → chunk-7PA4OZEU.js} +53 -11
  65. package/dist/chunk-7PA4OZEU.js.map +1 -0
  66. package/dist/{chunk-Q6FETXJA.js → chunk-7SEAZFFB.js} +2 -2
  67. package/dist/chunk-ALXMCZEU.js +332 -0
  68. package/dist/chunk-ALXMCZEU.js.map +1 -0
  69. package/dist/{chunk-QANCTXQF.js → chunk-AYPYCLR7.js} +3 -3
  70. package/dist/{chunk-WWIQTB2Y.js → chunk-BKQJBXXX.js} +9 -2
  71. package/dist/chunk-BKQJBXXX.js.map +1 -0
  72. package/dist/{chunk-LP47L3ZX.js → chunk-BTY5RRRF.js} +7 -7
  73. package/dist/{chunk-SCHEKPYH.js → chunk-C2EFFULQ.js} +1 -1
  74. package/dist/{chunk-GJR6D6KC.js → chunk-D654IBA6.js} +2 -2
  75. package/dist/{chunk-UV2FO7J4.js → chunk-E6K4NIEU.js} +2 -2
  76. package/dist/{chunk-T4WRIV2C.js → chunk-EABGC2TL.js} +2 -2
  77. package/dist/chunk-ECKDIK5F.js +813 -0
  78. package/dist/chunk-ECKDIK5F.js.map +1 -0
  79. package/dist/chunk-EJI5XIBB.js +232 -0
  80. package/dist/chunk-EJI5XIBB.js.map +1 -0
  81. package/dist/{chunk-ONRU4L2N.js → chunk-FEMOX5AD.js} +2 -2
  82. package/dist/{chunk-IFFFR3MR.js → chunk-FSFEQI74.js} +3 -3
  83. package/dist/chunk-G4SK7DSQ.js +121 -0
  84. package/dist/chunk-G4SK7DSQ.js.map +1 -0
  85. package/dist/{chunk-UIYZ5T3I.js → chunk-GJQPH5G3.js} +8 -8
  86. package/dist/{chunk-2PO5ZRKV.js → chunk-GZCUW5IC.js} +16 -3
  87. package/dist/chunk-GZCUW5IC.js.map +1 -0
  88. package/dist/{chunk-IZME7KW2.js → chunk-HITJFT7E.js} +24 -10
  89. package/dist/{chunk-IZME7KW2.js.map → chunk-HITJFT7E.js.map} +1 -1
  90. package/dist/chunk-IQT3XTKW.js +121 -0
  91. package/dist/chunk-IQT3XTKW.js.map +1 -0
  92. package/dist/{chunk-BDFZXRSO.js → chunk-J4IYOZZ5.js} +15 -2
  93. package/dist/chunk-J4IYOZZ5.js.map +1 -0
  94. package/dist/{chunk-ZKYI7UVO.js → chunk-JR4ZC3G4.js} +2 -2
  95. package/dist/{chunk-UCYSTFZR.js → chunk-JRNQ3RNA.js} +2 -2
  96. package/dist/{chunk-UYSKNO6E.js → chunk-JROGC36Y.js} +15 -4
  97. package/dist/chunk-JROGC36Y.js.map +1 -0
  98. package/dist/{chunk-GPGBSNKM.js → chunk-K4FLSOR5.js} +2 -2
  99. package/dist/{chunk-M5ZBBBJI.js → chunk-KEG4GNGI.js} +2 -2
  100. package/dist/chunk-KVE7R4CG.js +320 -0
  101. package/dist/chunk-KVE7R4CG.js.map +1 -0
  102. package/dist/{chunk-L7WO3MZ4.js → chunk-KWP7T3DP.js} +2 -2
  103. package/dist/chunk-LAYN4LDC.js +267 -0
  104. package/dist/chunk-LAYN4LDC.js.map +1 -0
  105. package/dist/{chunk-PGK3VUHN.js → chunk-MTLYEMJB.js} +3 -2
  106. package/dist/chunk-MTLYEMJB.js.map +1 -0
  107. package/dist/{chunk-J47FNDR7.js → chunk-MYQWXITD.js} +7 -7
  108. package/dist/{chunk-YNI4S5WT.js → chunk-N53K2EXC.js} +2 -2
  109. package/dist/{chunk-763GUIOU.js → chunk-NBNN5GOB.js} +2 -2
  110. package/dist/{chunk-CXWFUJR2.js → chunk-NSB3WSYS.js} +125 -6
  111. package/dist/chunk-NSB3WSYS.js.map +1 -0
  112. package/dist/{chunk-KL4CP4SB.js → chunk-O5ETUNBT.js} +17 -5
  113. package/dist/chunk-O5ETUNBT.js.map +1 -0
  114. package/dist/{chunk-OOSWAUYB.js → chunk-ODWDQNRE.js} +2 -2
  115. package/dist/{chunk-ISY75RLM.js → chunk-OJFGVJS6.js} +288 -7
  116. package/dist/chunk-OJFGVJS6.js.map +1 -0
  117. package/dist/{chunk-HLBYLYRD.js → chunk-PAORGQRI.js} +70 -13
  118. package/dist/chunk-PAORGQRI.js.map +1 -0
  119. package/dist/{chunk-ZJLY4QSU.js → chunk-PMB3WGDL.js} +69 -6
  120. package/dist/chunk-PMB3WGDL.js.map +1 -0
  121. package/dist/{chunk-J3BT33K7.js → chunk-POBPGDWI.js} +5 -5
  122. package/dist/{chunk-QWUUMMIK.js → chunk-POMSFKTB.js} +1351 -76
  123. package/dist/chunk-POMSFKTB.js.map +1 -0
  124. package/dist/{chunk-OTAVQCSF.js → chunk-PYXS46O7.js} +2 -2
  125. package/dist/chunk-QDW3E4RD.js +108 -0
  126. package/dist/chunk-QDW3E4RD.js.map +1 -0
  127. package/dist/{chunk-YNCQ7E4M.js → chunk-QDYXG4CS.js} +4 -3
  128. package/dist/chunk-QDYXG4CS.js.map +1 -0
  129. package/dist/{chunk-XUHI52HK.js → chunk-QKAH5B6E.js} +4 -4
  130. package/dist/{chunk-HLXVTBF3.js → chunk-QNJMBKFK.js} +3 -2
  131. package/dist/chunk-QNJMBKFK.js.map +1 -0
  132. package/dist/chunk-RCICHSHL.js +789 -0
  133. package/dist/chunk-RCICHSHL.js.map +1 -0
  134. package/dist/{chunk-HG2NKWR2.js → chunk-S4LX5EBI.js} +2 -2
  135. package/dist/{chunk-4A24LIM2.js → chunk-S75M5ZRK.js} +2 -2
  136. package/dist/{chunk-QCCCQT3O.js → chunk-TBBDFYXW.js} +2 -2
  137. package/dist/chunk-TBBDFYXW.js.map +1 -0
  138. package/dist/{chunk-U4PV25RD.js → chunk-U2IQTSBY.js} +1 -1
  139. package/dist/chunk-U2IQTSBY.js.map +1 -0
  140. package/dist/chunk-U66YHYC7.js +31 -0
  141. package/dist/chunk-U66YHYC7.js.map +1 -0
  142. package/dist/{chunk-MWGVGUIS.js → chunk-UEYA6UC7.js} +36 -4
  143. package/dist/chunk-UEYA6UC7.js.map +1 -0
  144. package/dist/{chunk-MDDAA2AO.js → chunk-UPMD5XND.js} +2 -2
  145. package/dist/{chunk-M5KEYE5E.js → chunk-URB2WSKZ.js} +2 -2
  146. package/dist/chunk-UVJFDP7P.js +202 -0
  147. package/dist/chunk-UVJFDP7P.js.map +1 -0
  148. package/dist/{chunk-QY2BHY5O.js → chunk-V7XCAHIB.js} +265 -25
  149. package/dist/chunk-V7XCAHIB.js.map +1 -0
  150. package/dist/chunk-W6SL7OFG.js +180 -0
  151. package/dist/chunk-W6SL7OFG.js.map +1 -0
  152. package/dist/{chunk-QDOSNLB4.js → chunk-X4WESCKA.js} +17 -15
  153. package/dist/chunk-X4WESCKA.js.map +1 -0
  154. package/dist/{chunk-OTFNI3OO.js → chunk-XMGSSBFX.js} +1738 -383
  155. package/dist/chunk-XMGSSBFX.js.map +1 -0
  156. package/dist/chunk-YDBIWGNI.js +298 -0
  157. package/dist/chunk-YDBIWGNI.js.map +1 -0
  158. package/dist/chunk-YFYL2SIJ.js +7857 -0
  159. package/dist/chunk-YFYL2SIJ.js.map +1 -0
  160. package/dist/chunking.js +1 -1
  161. package/dist/citations.d.ts +67 -0
  162. package/dist/citations.js +13 -0
  163. package/dist/citations.js.map +1 -0
  164. package/dist/cli-DwIBnp2g.d.ts +1240 -0
  165. package/dist/cli.d.ts +31 -1147
  166. package/dist/cli.js +149 -7092
  167. package/dist/cli.js.map +1 -1
  168. package/dist/codex-materialize-CQlLTzke.d.ts +139 -0
  169. package/dist/codex-thread-key.d.ts +3 -0
  170. package/dist/codex-thread-key.js +7 -0
  171. package/dist/codex-thread-key.js.map +1 -0
  172. package/dist/config.js +3 -2
  173. package/dist/connectors/codex/instructions.md +160 -0
  174. package/dist/connectors/codex/resources/namespace-cheatsheet.md +48 -0
  175. package/dist/day-summary.d.ts +7 -2
  176. package/dist/day-summary.js +5 -2
  177. package/dist/embedding-fallback.d.ts +96 -2
  178. package/dist/embedding-fallback.js +6 -4
  179. package/dist/{engine-2A6J4XEX.js → engine-X7X3AAG3.js} +10 -7
  180. package/dist/engine-X7X3AAG3.js.map +1 -0
  181. package/dist/entity-retrieval.d.ts +3 -2
  182. package/dist/entity-retrieval.js +10 -7
  183. package/dist/entity-schema.d.ts +11 -0
  184. package/dist/entity-schema.js +19 -0
  185. package/dist/entity-schema.js.map +1 -0
  186. package/dist/explicit-capture.d.ts +6 -3
  187. package/dist/explicit-capture.js +2 -2
  188. package/dist/extraction-judge.d.ts +66 -0
  189. package/dist/extraction-judge.js +18 -0
  190. package/dist/extraction-judge.js.map +1 -0
  191. package/dist/extraction.d.ts +1 -0
  192. package/dist/extraction.js +12 -10
  193. package/dist/fallback-llm.js +4 -4
  194. package/dist/graph.js +1 -1
  195. package/dist/importance.d.ts +11 -1
  196. package/dist/importance.js +3 -1
  197. package/dist/index.d.ts +1140 -8
  198. package/dist/index.js +3350 -333
  199. package/dist/index.js.map +1 -1
  200. package/dist/intent.d.ts +2 -1
  201. package/dist/intent.js +3 -1
  202. package/dist/lifecycle.js +1 -1
  203. package/dist/local-llm.js +2 -2
  204. package/dist/logger.d.ts +1 -1
  205. package/dist/logger.js +1 -1
  206. package/dist/memory-cache.d.ts +2 -2
  207. package/dist/memory-cache.js +1 -1
  208. package/dist/{memory-projection-store-NxMkbocT.d.ts → memory-projection-store-DeSXPh1j.d.ts} +1 -1
  209. package/dist/memory-projection-store.d.ts +1 -1
  210. package/dist/model-registry.js +2 -2
  211. package/dist/models-json.js +2 -2
  212. package/dist/native-knowledge.js +2 -2
  213. package/dist/negative.js +2 -2
  214. package/dist/operator-toolkit.js +20 -16
  215. package/dist/{orchestrator-zTa-Qo-1.d.ts → orchestrator-B9kwlCep.d.ts} +252 -7
  216. package/dist/orchestrator.d.ts +6 -3
  217. package/dist/orchestrator.js +70 -58
  218. package/dist/page-versioning.d.ts +77 -0
  219. package/dist/page-versioning.js +15 -0
  220. package/dist/page-versioning.js.map +1 -0
  221. package/dist/plugin-id.d.ts +37 -0
  222. package/dist/plugin-id.js +11 -0
  223. package/dist/plugin-id.js.map +1 -0
  224. package/dist/policy-runtime.js +2 -2
  225. package/dist/profiling.js +2 -2
  226. package/dist/qmd.d.ts +5 -2
  227. package/dist/qmd.js +3 -3
  228. package/dist/recall-audit.d.ts +20 -0
  229. package/dist/recall-audit.js +50 -0
  230. package/dist/recall-audit.js.map +1 -0
  231. package/dist/recall-mmr.d.ts +152 -0
  232. package/dist/recall-mmr.js +17 -0
  233. package/dist/recall-mmr.js.map +1 -0
  234. package/dist/recall-qos.js +2 -2
  235. package/dist/recall-state.js +2 -2
  236. package/dist/relevance.js +2 -2
  237. package/dist/resolve-provider-secret.js +2 -2
  238. package/dist/resume-bundles.js +5 -4
  239. package/dist/retrieval-agents.js +2 -2
  240. package/dist/retrieval.js +2 -2
  241. package/dist/schemas.d.ts +398 -40
  242. package/dist/schemas.js +3 -1
  243. package/dist/sdk-compat.d.ts +2 -0
  244. package/dist/sdk-compat.js +6 -3
  245. package/dist/sdk-compat.js.map +1 -1
  246. package/dist/semantic-chunking.d.ts +87 -0
  247. package/dist/semantic-chunking.js +20 -0
  248. package/dist/semantic-chunking.js.map +1 -0
  249. package/dist/semantic-consolidation-DrvSYRdB.d.ts +119 -0
  250. package/dist/semantic-consolidation.d.ts +4 -42
  251. package/dist/semantic-consolidation.js +23 -2
  252. package/dist/semantic-rule-promotion.js +9 -6
  253. package/dist/semantic-rule-verifier.js +10 -7
  254. package/dist/session-observer-state.js +2 -2
  255. package/dist/session-toggles.d.ts +22 -0
  256. package/dist/session-toggles.js +116 -0
  257. package/dist/session-toggles.js.map +1 -0
  258. package/dist/skills-registry.d.ts +47 -0
  259. package/dist/skills-registry.js +48 -0
  260. package/dist/skills-registry.js.map +1 -0
  261. package/dist/source-attribution.d.ts +169 -0
  262. package/dist/source-attribution.js +27 -0
  263. package/dist/source-attribution.js.map +1 -0
  264. package/dist/storage.d.ts +171 -10
  265. package/dist/storage.js +16 -5
  266. package/dist/summarizer.js +7 -7
  267. package/dist/temporal-supersession.d.ts +127 -0
  268. package/dist/temporal-supersession.js +20 -0
  269. package/dist/temporal-supersession.js.map +1 -0
  270. package/dist/threading.js +2 -2
  271. package/dist/tier-migration.d.ts +2 -1
  272. package/dist/tier-routing.js +2 -2
  273. package/dist/tokens.d.ts +21 -1
  274. package/dist/tokens.js +5 -1
  275. package/dist/transcript.js +2 -2
  276. package/dist/types.d.ts +497 -3
  277. package/dist/types.js +1 -1
  278. package/dist/utility-learner.js +2 -2
  279. package/dist/utility-runtime.js +3 -3
  280. package/dist/verified-recall.js +11 -8
  281. package/dist/whitespace.d.ts +4 -0
  282. package/dist/whitespace.js +9 -0
  283. package/dist/whitespace.js.map +1 -0
  284. package/package.json +14 -8
  285. package/dist/chunk-2CJCWDMR.js +0 -87
  286. package/dist/chunk-2CJCWDMR.js.map +0 -1
  287. package/dist/chunk-2PO5ZRKV.js.map +0 -1
  288. package/dist/chunk-6UJQNRIO.js.map +0 -1
  289. package/dist/chunk-B7LOFDVE.js.map +0 -1
  290. package/dist/chunk-BDFZXRSO.js.map +0 -1
  291. package/dist/chunk-CXWFUJR2.js.map +0 -1
  292. package/dist/chunk-DORBM6OB.js +0 -81
  293. package/dist/chunk-DORBM6OB.js.map +0 -1
  294. package/dist/chunk-ESSMF2FR.js.map +0 -1
  295. package/dist/chunk-HLBYLYRD.js.map +0 -1
  296. package/dist/chunk-HLXVTBF3.js.map +0 -1
  297. package/dist/chunk-ISY75RLM.js.map +0 -1
  298. package/dist/chunk-KL4CP4SB.js.map +0 -1
  299. package/dist/chunk-KWBU5S5U.js.map +0 -1
  300. package/dist/chunk-MWGVGUIS.js.map +0 -1
  301. package/dist/chunk-ORZMT74A.js.map +0 -1
  302. package/dist/chunk-OTFNI3OO.js.map +0 -1
  303. package/dist/chunk-PGK3VUHN.js.map +0 -1
  304. package/dist/chunk-QCCCQT3O.js.map +0 -1
  305. package/dist/chunk-QDOSNLB4.js.map +0 -1
  306. package/dist/chunk-QPKFPHOO.js +0 -178
  307. package/dist/chunk-QPKFPHOO.js.map +0 -1
  308. package/dist/chunk-QWUUMMIK.js.map +0 -1
  309. package/dist/chunk-QY2BHY5O.js.map +0 -1
  310. package/dist/chunk-TVVVQQAK.js.map +0 -1
  311. package/dist/chunk-U4PV25RD.js.map +0 -1
  312. package/dist/chunk-UYSKNO6E.js.map +0 -1
  313. package/dist/chunk-V4YC4LUK.js.map +0 -1
  314. package/dist/chunk-WWIQTB2Y.js.map +0 -1
  315. package/dist/chunk-YNCQ7E4M.js.map +0 -1
  316. package/dist/chunk-ZJLY4QSU.js.map +0 -1
  317. /package/dist/{engine-2A6J4XEX.js.map → active-memory-bridge.js.map} +0 -0
  318. /package/dist/{chunk-NTTLPF7F.js.map → chunk-3QFQGRHO.js.map} +0 -0
  319. /package/dist/{chunk-G3AG3KZN.js.map → chunk-5IZL4DCV.js.map} +0 -0
  320. /package/dist/{chunk-BRK4ODMI.js.map → chunk-5NPGSAVB.js.map} +0 -0
  321. /package/dist/{chunk-L5RPWGFK.js.map → chunk-7DHTMOND.js.map} +0 -0
  322. /package/dist/{chunk-Q6FETXJA.js.map → chunk-7SEAZFFB.js.map} +0 -0
  323. /package/dist/{chunk-QANCTXQF.js.map → chunk-AYPYCLR7.js.map} +0 -0
  324. /package/dist/{chunk-LP47L3ZX.js.map → chunk-BTY5RRRF.js.map} +0 -0
  325. /package/dist/{chunk-SCHEKPYH.js.map → chunk-C2EFFULQ.js.map} +0 -0
  326. /package/dist/{chunk-GJR6D6KC.js.map → chunk-D654IBA6.js.map} +0 -0
  327. /package/dist/{chunk-UV2FO7J4.js.map → chunk-E6K4NIEU.js.map} +0 -0
  328. /package/dist/{chunk-T4WRIV2C.js.map → chunk-EABGC2TL.js.map} +0 -0
  329. /package/dist/{chunk-ONRU4L2N.js.map → chunk-FEMOX5AD.js.map} +0 -0
  330. /package/dist/{chunk-IFFFR3MR.js.map → chunk-FSFEQI74.js.map} +0 -0
  331. /package/dist/{chunk-UIYZ5T3I.js.map → chunk-GJQPH5G3.js.map} +0 -0
  332. /package/dist/{chunk-ZKYI7UVO.js.map → chunk-JR4ZC3G4.js.map} +0 -0
  333. /package/dist/{chunk-UCYSTFZR.js.map → chunk-JRNQ3RNA.js.map} +0 -0
  334. /package/dist/{chunk-GPGBSNKM.js.map → chunk-K4FLSOR5.js.map} +0 -0
  335. /package/dist/{chunk-M5ZBBBJI.js.map → chunk-KEG4GNGI.js.map} +0 -0
  336. /package/dist/{chunk-L7WO3MZ4.js.map → chunk-KWP7T3DP.js.map} +0 -0
  337. /package/dist/{chunk-J47FNDR7.js.map → chunk-MYQWXITD.js.map} +0 -0
  338. /package/dist/{chunk-YNI4S5WT.js.map → chunk-N53K2EXC.js.map} +0 -0
  339. /package/dist/{chunk-763GUIOU.js.map → chunk-NBNN5GOB.js.map} +0 -0
  340. /package/dist/{chunk-OOSWAUYB.js.map → chunk-ODWDQNRE.js.map} +0 -0
  341. /package/dist/{chunk-J3BT33K7.js.map → chunk-POBPGDWI.js.map} +0 -0
  342. /package/dist/{chunk-OTAVQCSF.js.map → chunk-PYXS46O7.js.map} +0 -0
  343. /package/dist/{chunk-XUHI52HK.js.map → chunk-QKAH5B6E.js.map} +0 -0
  344. /package/dist/{chunk-HG2NKWR2.js.map → chunk-S4LX5EBI.js.map} +0 -0
  345. /package/dist/{chunk-4A24LIM2.js.map → chunk-S75M5ZRK.js.map} +0 -0
  346. /package/dist/{chunk-MDDAA2AO.js.map → chunk-UPMD5XND.js.map} +0 -0
  347. /package/dist/{chunk-M5KEYE5E.js.map → chunk-URB2WSKZ.js.map} +0 -0
@@ -1,11 +1,19 @@
1
1
  import {
2
2
  SPECULATIVE_TTL_DAYS,
3
3
  confidenceTier
4
- } from "./chunk-U4PV25RD.js";
4
+ } from "./chunk-U2IQTSBY.js";
5
+ import {
6
+ DEFAULT_CITATION_FORMAT,
7
+ hasCitation,
8
+ stripCitationForTemplate
9
+ } from "./chunk-4KAN3GZ3.js";
10
+ import {
11
+ createVersion
12
+ } from "./chunk-6ZH4TU6I.js";
5
13
  import {
6
14
  getCachedEntities,
7
15
  setCachedEntities
8
- } from "./chunk-ESSMF2FR.js";
16
+ } from "./chunk-6PFRXT4K.js";
9
17
  import {
10
18
  inferMemoryStatus,
11
19
  isArchivedMemoryPath,
@@ -22,9 +30,6 @@ import {
22
30
  readProjectedMemoryState,
23
31
  readProjectedMemoryTimeline
24
32
  } from "./chunk-BOUYNNYD.js";
25
- import {
26
- rotateMarkdownFileToArchive
27
- } from "./chunk-DM2T26WE.js";
28
33
  import {
29
34
  closeContinuityIncidentRecord,
30
35
  createContinuityIncidentRecord,
@@ -34,12 +39,20 @@ import {
34
39
  serializeContinuityIncident,
35
40
  upsertContinuityLoopInMarkdown
36
41
  } from "./chunk-QSVPYQPG.js";
42
+ import {
43
+ rotateMarkdownFileToArchive
44
+ } from "./chunk-DM2T26WE.js";
37
45
  import {
38
46
  sanitizeMemoryContent
39
47
  } from "./chunk-M62O4P4T.js";
48
+ import {
49
+ matchEntitySchemaSection,
50
+ normalizeEntityStructuredSection,
51
+ sortStructuredSectionsBySchema
52
+ } from "./chunk-4DJQYKMN.js";
40
53
  import {
41
54
  log
42
- } from "./chunk-KWBU5S5U.js";
55
+ } from "./chunk-2ODBA7MQ.js";
43
56
 
44
57
  // src/storage.ts
45
58
  import { access, readdir, readFile, stat, writeFile, mkdir, unlink, rename, appendFile } from "fs/promises";
@@ -146,6 +159,7 @@ function serializeFrontmatter(fm) {
146
159
  if (fm.structuredAttributes && Object.keys(fm.structuredAttributes).length > 0) {
147
160
  lines.push(`structuredAttributes: ${JSON.stringify(fm.structuredAttributes)}`);
148
161
  }
162
+ if (fm.contentHash) lines.push(`contentHash: ${fm.contentHash}`);
149
163
  lines.push("---");
150
164
  return lines.join("\n");
151
165
  }
@@ -280,7 +294,9 @@ function parseFrontmatter(raw) {
280
294
  // v8.0 Phase 2B: HiMem episode/note classification
281
295
  memoryKind: fm.memoryKind || void 0,
282
296
  // Structured attributes (JSON on a single line)
283
- structuredAttributes: parseStructuredAttributes(fm.structuredAttributes)
297
+ structuredAttributes: parseStructuredAttributes(fm.structuredAttributes),
298
+ // Raw-content dedup hash (format-agnostic archive/consolidation cleanup)
299
+ contentHash: fm.contentHash || void 0
284
300
  },
285
301
  content
286
302
  };
@@ -303,14 +319,47 @@ function parseFrontmatter(raw) {
303
319
  }
304
320
  return result;
305
321
  }
306
- function normalizeFrontmatterForPath(frontmatter, pathRel) {
307
- if (isArchivedMemoryPath(pathRel) && (!frontmatter.status || frontmatter.status === "active")) {
322
+ function inferEntityTypeFromContent(content) {
323
+ const typeMatch = content.match(/^\*\*Type:\*\*\s*([^\n]+)/m)?.[1]?.trim().toLowerCase();
324
+ return typeMatch || void 0;
325
+ }
326
+ var KNOWN_ENTITY_FILENAME_PREFIXES = /* @__PURE__ */ new Set([
327
+ "company",
328
+ "other",
329
+ "person",
330
+ "place",
331
+ "project",
332
+ "tool",
333
+ "topic"
334
+ ]);
335
+ function inferEntityTypeFromFilename(pathRel) {
336
+ const basename = path.basename(pathRel, ".md").toLowerCase();
337
+ const separator = basename.indexOf("-");
338
+ if (separator <= 0) return void 0;
339
+ const candidate = basename.slice(0, separator);
340
+ return KNOWN_ENTITY_FILENAME_PREFIXES.has(candidate) ? candidate : void 0;
341
+ }
342
+ function normalizeFrontmatterForPath(frontmatter, pathRel, content = "") {
343
+ const normalizedPath = pathRel.split(path.sep).join("/");
344
+ let normalizedFrontmatter = frontmatter;
345
+ if (normalizedPath === "entities" || normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/")) {
346
+ const basename = path.basename(pathRel, ".md");
347
+ const inferredType = inferEntityTypeFromContent(content) || inferEntityTypeFromFilename(pathRel) || "entity";
348
+ const existingTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
349
+ normalizedFrontmatter = {
350
+ ...normalizedFrontmatter,
351
+ id: typeof normalizedFrontmatter.id === "string" && normalizedFrontmatter.id.trim().length > 0 ? normalizedFrontmatter.id : basename,
352
+ category: "entity",
353
+ tags: existingTags.includes(inferredType) ? existingTags : [...existingTags, inferredType]
354
+ };
355
+ }
356
+ if (isArchivedMemoryPath(pathRel) && (!normalizedFrontmatter.status || normalizedFrontmatter.status === "active")) {
308
357
  return {
309
- ...frontmatter,
358
+ ...normalizedFrontmatter,
310
359
  status: "archived"
311
360
  };
312
361
  }
313
- return frontmatter;
362
+ return normalizedFrontmatter;
314
363
  }
315
364
  function inferCurrentStateStatus(frontmatter, pathRel, fallbackStatus) {
316
365
  return inferMemoryStatus(frontmatter, pathRel, fallbackStatus);
@@ -406,6 +455,33 @@ var ContentHashIndex = class _ContentHashIndex {
406
455
  this.dirty = true;
407
456
  }
408
457
  }
458
+ /**
459
+ * Remove a pre-computed SHA-256 hash directly from the index without
460
+ * re-hashing. Use this when the caller already holds the stored hash
461
+ * (e.g. `memory.frontmatter.contentHash`) to avoid the double-hash bug
462
+ * where `remove(hash)` would compute `hash(hash)` and never match the
463
+ * entry.
464
+ */
465
+ removeByHash(hash) {
466
+ if (this.hashes.delete(hash)) {
467
+ this.dirty = true;
468
+ }
469
+ }
470
+ /**
471
+ * Add a pre-computed SHA-256 hash directly to the index without re-hashing.
472
+ * Use this when the caller already holds the stored hash
473
+ * (e.g. `memory.frontmatter.contentHash`) so that the index records the raw
474
+ * content hash rather than re-hashing the citation-annotated body.
475
+ *
476
+ * @internal Only called from `StorageManager.ensureFactHashIndexAuthoritative`.
477
+ * Not part of the public API — prefer `add(content)` for external callers.
478
+ */
479
+ addByHash(hash) {
480
+ if (!this.hashes.has(hash)) {
481
+ this.hashes.add(hash);
482
+ this.dirty = true;
483
+ }
484
+ }
409
485
  /** Normalize content and compute SHA-256 hash. */
410
486
  static normalizeContent(content) {
411
487
  return content.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
@@ -416,36 +492,598 @@ var ContentHashIndex = class _ContentHashIndex {
416
492
  return createHash("sha256").update(normalized).digest("hex");
417
493
  }
418
494
  };
419
- function parseEntityFile(content) {
420
- const lines = content.split("\n");
495
+ function normalizeAttributePairs(pairs) {
496
+ return Object.entries(pairs).map(([k, v]) => [k.trim().toLowerCase(), v.trim()]).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}: ${v}`).join("; ");
497
+ }
498
+ function parseEntityFrontmatter(raw) {
499
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
500
+ if (!match) {
501
+ return { frontmatter: {}, body: raw };
502
+ }
503
+ const values = {};
504
+ const extraLines = [];
505
+ const recognizedKeys = /* @__PURE__ */ new Set([
506
+ "created",
507
+ "updated",
508
+ "synthesis_updated_at",
509
+ "synthesis_timeline_count",
510
+ "synthesis_structured_fact_count",
511
+ "synthesis_structured_fact_digest",
512
+ "synthesis_version"
513
+ ]);
514
+ for (const line of match[1].split(/\r?\n/)) {
515
+ if (/^\s/.test(line)) {
516
+ extraLines.push(line);
517
+ continue;
518
+ }
519
+ const colonIdx = line.indexOf(":");
520
+ if (colonIdx === -1) {
521
+ extraLines.push(line);
522
+ continue;
523
+ }
524
+ const key = line.slice(0, colonIdx).trim();
525
+ if (!recognizedKeys.has(key)) {
526
+ extraLines.push(line);
527
+ continue;
528
+ }
529
+ const value = parseManagedFrontmatterValue(line.slice(colonIdx + 1));
530
+ values[key] = value;
531
+ }
532
+ const synthesisTimelineCount = Number.parseInt(values.synthesis_timeline_count ?? "", 10);
533
+ const synthesisStructuredFactCount = Number.parseInt(values.synthesis_structured_fact_count ?? "", 10);
534
+ const synthesisVersion = Number.parseInt(values.synthesis_version ?? "", 10);
535
+ return {
536
+ frontmatter: {
537
+ created: values.created || void 0,
538
+ updated: values.updated || void 0,
539
+ synthesisUpdatedAt: values.synthesis_updated_at || void 0,
540
+ synthesisTimelineCount: Number.isFinite(synthesisTimelineCount) ? synthesisTimelineCount : void 0,
541
+ synthesisStructuredFactCount: Number.isFinite(synthesisStructuredFactCount) ? synthesisStructuredFactCount : void 0,
542
+ synthesisStructuredFactDigest: values.synthesis_structured_fact_digest || void 0,
543
+ synthesisVersion: Number.isFinite(synthesisVersion) ? synthesisVersion : void 0,
544
+ extraLines
545
+ },
546
+ body: match[2]
547
+ };
548
+ }
549
+ function parseManagedFrontmatterValue(rawValue) {
550
+ const trimmed = rawValue.trim();
551
+ if (!trimmed) return "";
552
+ const openingQuote = trimmed[0];
553
+ if (openingQuote === '"' || openingQuote === "'") {
554
+ let escaped = false;
555
+ for (let index = 1; index < trimmed.length; index += 1) {
556
+ const char = trimmed[index];
557
+ if (openingQuote === '"' && !escaped && char === "\\") {
558
+ escaped = true;
559
+ continue;
560
+ }
561
+ if (!escaped && char === openingQuote) {
562
+ return trimmed.slice(1, index);
563
+ }
564
+ escaped = false;
565
+ }
566
+ return trimmed.slice(1).replace(new RegExp(`${openingQuote}$`), "");
567
+ }
568
+ for (let index = 0; index < trimmed.length; index += 1) {
569
+ if (trimmed[index] === "#" && (index === 0 || /\s/.test(trimmed[index - 1] ?? ""))) {
570
+ return trimmed.slice(0, index).trimEnd();
571
+ }
572
+ }
573
+ return trimmed;
574
+ }
575
+ function readEntitySectionText(lines, sectionNames, options = {}) {
576
+ const normalizedSections = new Set(sectionNames.map((name) => name.toLowerCase()));
577
+ let section = "";
578
+ const sectionLines = [];
579
+ for (const line of lines) {
580
+ if (line.startsWith("## ")) {
581
+ const nextSection = line.slice(3).trim().toLowerCase();
582
+ if (section && !normalizedSections.has(nextSection)) break;
583
+ section = normalizedSections.has(nextSection) ? nextSection : "";
584
+ continue;
585
+ }
586
+ if (!section) continue;
587
+ const trimmed = line.trim();
588
+ if (!trimmed) {
589
+ if (options.preserveBullets === true && sectionLines.length > 0 && sectionLines[sectionLines.length - 1] !== "") {
590
+ sectionLines.push("");
591
+ }
592
+ continue;
593
+ }
594
+ if (options.skipTimelineBullets === true && trimmed.startsWith("- ") && isEntitySynthesisTimelinePromotionBullet(trimmed.slice(2))) {
595
+ continue;
596
+ }
597
+ if (trimmed.startsWith("- ") && options.preserveBullets !== true) continue;
598
+ sectionLines.push(options.preserveBullets === true ? line.trimEnd() : trimmed);
599
+ }
600
+ while (sectionLines[sectionLines.length - 1] === "") {
601
+ sectionLines.pop();
602
+ }
603
+ if (sectionLines.length === 0) return void 0;
604
+ return sectionLines.join(options.preserveBullets === true ? "\n" : " ");
605
+ }
606
+ function parseEntityTimelineBullet(bullet, fallbackTimestamp) {
607
+ const trimmed = bullet.trim();
608
+ if (!trimmed) return null;
609
+ let rest = trimmed;
610
+ const entry = {
611
+ timestamp: trimmed.startsWith("[") ? "" : fallbackTimestamp,
612
+ text: ""
613
+ };
614
+ const consumedMetadataSegments = [];
615
+ let literalSingleSourceSegment;
616
+ if (!trimmed.startsWith("[")) {
617
+ entry.text = trimmed;
618
+ return entry.text ? entry : null;
619
+ }
620
+ const firstEnd = trimmed.indexOf("]");
621
+ if (firstEnd === -1) {
622
+ entry.text = trimmed;
623
+ return entry.text ? entry : null;
624
+ }
625
+ const firstToken = trimmed.slice(1, firstEnd).trim();
626
+ const parsedTimestamp = Date.parse(firstToken);
627
+ if (Number.isFinite(parsedTimestamp)) {
628
+ entry.timestamp = firstToken || fallbackTimestamp;
629
+ rest = trimmed.slice(firstEnd + 1).trimStart();
630
+ }
631
+ while (rest.startsWith("[")) {
632
+ const end = findEntityTimelineTokenEnd(rest);
633
+ if (end === -1) break;
634
+ const rawSegment = rest.slice(0, end + 1);
635
+ const token = rest.slice(1, end).trim();
636
+ const equalsIdx = token.indexOf("=");
637
+ if (equalsIdx === -1) {
638
+ if (rest === trimmed) {
639
+ entry.text = trimmed;
640
+ return entry.text ? entry : null;
641
+ }
642
+ break;
643
+ }
644
+ const key = token.slice(0, equalsIdx).trim().toLowerCase();
645
+ const value = unescapeEntityTimelineMetadataValue(token.slice(equalsIdx + 1).trim());
646
+ if (!value) break;
647
+ const nextRest = rest.slice(end + 1).trimStart();
648
+ switch (key) {
649
+ case "source_meta":
650
+ entry.source = value;
651
+ break;
652
+ case "source":
653
+ if (consumedMetadataSegments.length === 0 && !nextRest.startsWith("[") && nextRest.length > 0 && !isManagedEntityTimelineSource(value)) {
654
+ literalSingleSourceSegment = rawSegment;
655
+ rest = nextRest;
656
+ break;
657
+ }
658
+ entry.source = value;
659
+ break;
660
+ case "session":
661
+ case "sessionkey":
662
+ entry.sessionKey = value;
663
+ break;
664
+ case "principal":
665
+ entry.principal = value;
666
+ break;
667
+ default:
668
+ entry.text = rest.trim();
669
+ return entry.text ? entry : null;
670
+ }
671
+ if (literalSingleSourceSegment) break;
672
+ consumedMetadataSegments.push(rawSegment);
673
+ rest = nextRest;
674
+ }
675
+ if (literalSingleSourceSegment) {
676
+ return {
677
+ timestamp: entry.timestamp,
678
+ text: `${literalSingleSourceSegment} ${rest}`.trim()
679
+ };
680
+ }
681
+ entry.text = rest.trim();
682
+ if (!entry.text) return null;
683
+ return entry;
684
+ }
685
+ function isEntitySynthesisTimelinePromotionBullet(bullet) {
686
+ const trimmed = bullet.trim();
687
+ if (!trimmed.startsWith("[")) return false;
688
+ const firstEnd = findEntityTimelineTokenEnd(trimmed);
689
+ if (firstEnd === -1) return false;
690
+ const firstToken = trimmed.slice(1, firstEnd).trim();
691
+ return looksLikeEntityTimelineTimestamp(firstToken);
692
+ }
693
+ function looksLikeEntityTimelineTimestamp(token) {
694
+ if (!/^\d{4}-\d{2}-\d{2}(?:[T\s].*)?$/.test(token)) return false;
695
+ return Number.isFinite(Date.parse(token));
696
+ }
697
+ function isManagedEntityTimelineSource(source) {
698
+ switch (source.trim().toLowerCase()) {
699
+ case "artifact":
700
+ case "chunking":
701
+ case "cli-migrate":
702
+ case "compounding-promotion":
703
+ case "consolidation":
704
+ case "contradiction-detection":
705
+ case "entity_extraction":
706
+ case "explicit":
707
+ case "explicit-inline":
708
+ case "explicit-inline-review":
709
+ case "explicit-review":
710
+ case "extraction":
711
+ case "extraction-shared-promotion":
712
+ case "manual":
713
+ case "migration":
714
+ case "migration-rechunk":
715
+ case "proactive":
716
+ case "replay":
717
+ case "semantic-consolidation":
718
+ case "unknown":
719
+ return true;
720
+ default:
721
+ return false;
722
+ }
723
+ }
724
+ function findEntityTimelineTokenEnd(input) {
725
+ let escaped = false;
726
+ for (let index = 0; index < input.length; index += 1) {
727
+ const char = input[index];
728
+ if (escaped) {
729
+ escaped = false;
730
+ continue;
731
+ }
732
+ if (char === "\\") {
733
+ escaped = true;
734
+ continue;
735
+ }
736
+ if (char === "]") return index;
737
+ }
738
+ return -1;
739
+ }
740
+ function escapeEntityTimelineMetadataValue(value) {
741
+ let escaped = "";
742
+ for (const char of value) {
743
+ switch (char) {
744
+ case "\\":
745
+ escaped += "\\\\";
746
+ break;
747
+ case "]":
748
+ escaped += "\\]";
749
+ break;
750
+ case "\n":
751
+ escaped += "\\n";
752
+ break;
753
+ case "\r":
754
+ escaped += "\\r";
755
+ break;
756
+ case " ":
757
+ escaped += "\\t";
758
+ break;
759
+ default: {
760
+ const codePoint = char.codePointAt(0) ?? 0;
761
+ if (codePoint < 32) {
762
+ escaped += `\\u${codePoint.toString(16).padStart(4, "0")}`;
763
+ } else {
764
+ escaped += char;
765
+ }
766
+ }
767
+ }
768
+ }
769
+ return escaped;
770
+ }
771
+ function unescapeEntityTimelineMetadataValue(value) {
772
+ if (!value.includes("\\")) return value;
773
+ let result = "";
774
+ for (let index = 0; index < value.length; index += 1) {
775
+ const char = value[index];
776
+ if (char !== "\\") {
777
+ result += char;
778
+ continue;
779
+ }
780
+ const next = value[index + 1];
781
+ if (!next) {
782
+ result += "\\";
783
+ break;
784
+ }
785
+ switch (next) {
786
+ case "n":
787
+ result += "\n";
788
+ index += 1;
789
+ break;
790
+ case "r":
791
+ result += "\r";
792
+ index += 1;
793
+ break;
794
+ case "t":
795
+ result += " ";
796
+ index += 1;
797
+ break;
798
+ case "u": {
799
+ const hex = value.slice(index + 2, index + 6);
800
+ if (/^[0-9a-fA-F]{4}$/.test(hex)) {
801
+ result += String.fromCharCode(parseInt(hex, 16));
802
+ index += 5;
803
+ break;
804
+ }
805
+ result += "u";
806
+ index += 1;
807
+ break;
808
+ }
809
+ default:
810
+ result += next;
811
+ index += 1;
812
+ break;
813
+ }
814
+ }
815
+ return result;
816
+ }
817
+ function serializeEntityTimelineEntry(entry) {
818
+ const tokens = [];
819
+ if (entry.timestamp.trim().length > 0) {
820
+ tokens.push(`[${entry.timestamp}]`);
821
+ }
822
+ if (entry.source) {
823
+ const sourceKey = isManagedEntityTimelineSource(entry.source) ? "source" : "source_meta";
824
+ tokens.push(`[${sourceKey}=${escapeEntityTimelineMetadataValue(entry.source)}]`);
825
+ }
826
+ if (entry.sessionKey) {
827
+ tokens.push(`[session=${escapeEntityTimelineMetadataValue(entry.sessionKey)}]`);
828
+ }
829
+ if (entry.principal) {
830
+ tokens.push(`[principal=${escapeEntityTimelineMetadataValue(entry.principal)}]`);
831
+ }
832
+ const serializedMetadata = tokens.length > 0 ? `${tokens.join(" ")} ` : "";
833
+ return `- ${serializedMetadata}${entry.text}`.trimEnd();
834
+ }
835
+ function dedupeEntityTimelineFacts(timeline) {
836
+ return [...new Set(
837
+ timeline.map((entry) => entry.text.trim()).filter((entry) => entry.length > 0)
838
+ )];
839
+ }
840
+ function normalizeEntitySectionFact(value) {
841
+ return value.replace(/\s+/g, " ").trim();
842
+ }
843
+ function normalizeStructuredSectionFacts(facts) {
844
+ return [...new Set(
845
+ facts.map((fact) => normalizeEntitySectionFact(fact)).filter((fact) => fact.length > 0)
846
+ )];
847
+ }
848
+ function collectStructuredSectionFacts(structuredSections) {
849
+ const facts = [];
850
+ for (const section of structuredSections) {
851
+ for (const fact of section.facts) {
852
+ const normalized = normalizeEntitySectionFact(fact);
853
+ if (!normalized) continue;
854
+ facts.push(normalized);
855
+ }
856
+ }
857
+ return [...new Set(facts)];
858
+ }
859
+ function compileEntityFacts(timeline, structuredSections) {
860
+ const facts = [];
861
+ const seen = /* @__PURE__ */ new Set();
862
+ for (const fact of dedupeEntityTimelineFacts(timeline)) {
863
+ if (seen.has(fact)) continue;
864
+ seen.add(fact);
865
+ facts.push(fact);
866
+ }
867
+ for (const fact of collectStructuredSectionFacts(structuredSections)) {
868
+ if (seen.has(fact)) continue;
869
+ seen.add(fact);
870
+ facts.push(fact);
871
+ }
872
+ return facts;
873
+ }
874
+ function parseEntityStructuredSectionFacts(lines) {
875
+ const facts = [];
876
+ let currentBlock = [];
877
+ const flushCurrentBlock = () => {
878
+ const normalized = normalizeEntitySectionFact(currentBlock.join(" "));
879
+ if (normalized.length > 0) facts.push(normalized);
880
+ currentBlock = [];
881
+ };
882
+ for (const rawLine of lines) {
883
+ const line = rawLine.trim();
884
+ if (!line) {
885
+ flushCurrentBlock();
886
+ continue;
887
+ }
888
+ if (line.startsWith("- ")) {
889
+ flushCurrentBlock();
890
+ currentBlock = [line.slice(2).trim()];
891
+ continue;
892
+ }
893
+ currentBlock.push(line);
894
+ }
895
+ flushCurrentBlock();
896
+ return [...new Set(facts)];
897
+ }
898
+ function looksLikeStructuredSectionFactList(lines) {
899
+ const firstNonBlank = lines.find((line) => line.trim().length > 0)?.trim() ?? "";
900
+ return firstNonBlank.startsWith("- ");
901
+ }
902
+ function partitionEntityStructuredSections(entityType, extraSections, entitySchemas) {
903
+ const structuredSections = [];
904
+ const remainingExtraSections = [];
905
+ const structuredSectionIndex = /* @__PURE__ */ new Map();
906
+ for (const section of extraSections) {
907
+ const matchedSection = matchEntitySchemaSection(entityType, section.title, entitySchemas);
908
+ if (!matchedSection && !looksLikeStructuredSectionFactList(section.lines)) {
909
+ remainingExtraSections.push(section);
910
+ continue;
911
+ }
912
+ const facts = parseEntityStructuredSectionFacts(section.lines);
913
+ if (!matchedSection && facts.length === 0) {
914
+ remainingExtraSections.push(section);
915
+ continue;
916
+ }
917
+ const normalizedSection = matchedSection ? { key: matchedSection.key, title: matchedSection.title } : normalizeEntityStructuredSection(
918
+ entityType,
919
+ { key: section.title, title: section.title },
920
+ entitySchemas
921
+ );
922
+ if (facts.length === 0) {
923
+ remainingExtraSections.push(section);
924
+ continue;
925
+ }
926
+ const existing = structuredSectionIndex.get(normalizedSection.key);
927
+ if (existing) {
928
+ existing.facts = normalizeStructuredSectionFacts([...existing.facts, ...facts]);
929
+ continue;
930
+ }
931
+ const structuredSection = {
932
+ key: normalizedSection.key,
933
+ title: normalizedSection.title,
934
+ facts: normalizeStructuredSectionFacts(facts)
935
+ };
936
+ structuredSections.push(structuredSection);
937
+ structuredSectionIndex.set(normalizedSection.key, structuredSection);
938
+ }
939
+ return {
940
+ structuredSections,
941
+ remainingExtraSections
942
+ };
943
+ }
944
+ function latestEntityTimelineTimestamp(entity) {
945
+ let latestRaw;
946
+ for (const entry of entity.timeline) {
947
+ const timestamp = entry.timestamp.trim();
948
+ if (!timestamp) continue;
949
+ if (!latestRaw || compareEntityTimestamps(timestamp, latestRaw) > 0) {
950
+ latestRaw = timestamp;
951
+ }
952
+ }
953
+ return latestRaw;
954
+ }
955
+ function compareEntityTimestamps(left, right) {
956
+ const leftValue = left?.trim() ?? "";
957
+ const rightValue = right?.trim() ?? "";
958
+ if (!leftValue && !rightValue) return 0;
959
+ if (!leftValue) return -1;
960
+ if (!rightValue) return 1;
961
+ const leftMs = Date.parse(leftValue);
962
+ const rightMs = Date.parse(rightValue);
963
+ const leftParsed = Number.isFinite(leftMs);
964
+ const rightParsed = Number.isFinite(rightMs);
965
+ if (leftParsed && rightParsed) {
966
+ if (leftMs === rightMs) return 0;
967
+ return leftMs > rightMs ? 1 : -1;
968
+ }
969
+ if (leftParsed) return 1;
970
+ if (rightParsed) return -1;
971
+ return leftValue.localeCompare(rightValue);
972
+ }
973
+ function countEntityStructuredFacts(entity) {
974
+ return (entity.structuredSections ?? []).reduce((count, section) => count + section.facts.length, 0);
975
+ }
976
+ function fingerprintEntityStructuredFacts(entity) {
977
+ const normalizedSections = (entity.structuredSections ?? []).map((section) => ({
978
+ key: section.key.trim().toLowerCase(),
979
+ title: section.title.replace(/\s+/g, " ").trim(),
980
+ facts: normalizeStructuredSectionFacts(section.facts).slice().sort((left, right) => left.localeCompare(right))
981
+ })).filter((section) => section.facts.length > 0).sort((left, right) => left.key.localeCompare(right.key) || left.title.localeCompare(right.title) || left.facts.join("\n").localeCompare(right.facts.join("\n")));
982
+ if (normalizedSections.length === 0) return void 0;
983
+ return createHash("sha256").update(JSON.stringify(normalizedSections)).digest("hex");
984
+ }
985
+ function isEntitySynthesisStale(entity) {
986
+ const structuredFactCount = countEntityStructuredFacts(entity);
987
+ const structuredFactDigest = fingerprintEntityStructuredFacts(entity);
988
+ const storedStructuredFactDigest = entity.synthesisStructuredFactDigest?.trim() || void 0;
989
+ if (entity.timeline.length === 0 && structuredFactCount === 0) return false;
990
+ if (!entity.synthesis?.trim()) return true;
991
+ if (entity.synthesisTimelineCount === void 0) return true;
992
+ if (structuredFactCount > 0 && entity.synthesisStructuredFactCount === void 0) return true;
993
+ if (structuredFactCount > 0 && !storedStructuredFactDigest) return true;
994
+ const latestTimelineTimestamp = latestEntityTimelineTimestamp(entity);
995
+ if (!latestTimelineTimestamp) {
996
+ return entity.timeline.length > entity.synthesisTimelineCount || structuredFactCount > (entity.synthesisStructuredFactCount ?? 0) || structuredFactDigest !== storedStructuredFactDigest;
997
+ }
998
+ if (!entity.synthesisUpdatedAt?.trim()) return true;
999
+ const timelineFreshness = compareEntityTimestamps(latestTimelineTimestamp, entity.synthesisUpdatedAt);
1000
+ if (timelineFreshness > 0) return true;
1001
+ return entity.timeline.length > entity.synthesisTimelineCount || structuredFactCount > (entity.synthesisStructuredFactCount ?? 0) || structuredFactDigest !== storedStructuredFactDigest;
1002
+ }
1003
+ function parseEntityFile(content, entitySchemas) {
1004
+ const { frontmatter, body } = parseEntityFrontmatter(content);
1005
+ const lines = body.split("\n");
1006
+ const recognizedSections = /* @__PURE__ */ new Set([
1007
+ "facts",
1008
+ "timeline",
1009
+ "summary",
1010
+ "synthesis",
1011
+ "connected to",
1012
+ "activity",
1013
+ "aliases"
1014
+ ]);
421
1015
  let name = "";
422
1016
  let type = "other";
1017
+ let created = frontmatter.created ?? "";
423
1018
  let updated = "";
424
- let summary;
425
- const facts = [];
1019
+ const legacyFacts = [];
426
1020
  const relationships = [];
427
1021
  const activity = [];
428
1022
  const aliases = [];
1023
+ const timeline = [];
1024
+ const extraSections = [];
429
1025
  const headingLine = lines.find((l) => l.startsWith("# "));
430
1026
  if (headingLine) name = headingLine.slice(2).trim();
431
1027
  const typeLine = lines.find((l) => l.startsWith("**Type:**"));
432
1028
  if (typeLine) type = typeLine.replace("**Type:**", "").trim();
433
1029
  const updatedLine = lines.find((l) => l.startsWith("**Updated:**"));
434
1030
  if (updatedLine) updated = updatedLine.replace("**Updated:**", "").trim();
1031
+ if (!updated) updated = frontmatter.updated ?? frontmatter.created ?? "";
1032
+ if (!created) created = updated;
1033
+ const headingLineIndex = lines.findIndex((l) => l.startsWith("# "));
1034
+ const firstSectionIndex = lines.findIndex((l) => l.startsWith("## "));
1035
+ const preSectionStartIndex = headingLineIndex > -1 ? headingLineIndex + 1 : 0;
1036
+ const preSectionCandidates = firstSectionIndex > -1 ? lines.slice(preSectionStartIndex, firstSectionIndex) : lines.slice(preSectionStartIndex);
1037
+ const preSectionLines = preSectionCandidates.filter(
1038
+ (line) => !line.startsWith("**Type:**") && !line.startsWith("**Updated:**")
1039
+ );
1040
+ const normalizedPreSectionLines = [...preSectionLines];
1041
+ while (normalizedPreSectionLines[0] === "") {
1042
+ normalizedPreSectionLines.shift();
1043
+ }
1044
+ const preservedPreSectionLines = normalizedPreSectionLines.some((line) => line.trim().length > 0) ? normalizedPreSectionLines : [];
1045
+ const fallbackTimestamp = updated || created || "";
435
1046
  let section = "";
1047
+ let currentExtraSection = null;
436
1048
  for (const line of lines) {
437
1049
  if (line.startsWith("## ")) {
438
- section = line.slice(3).trim().toLowerCase();
1050
+ const heading = line.slice(3).trim();
1051
+ section = heading.toLowerCase();
1052
+ if (recognizedSections.has(section)) {
1053
+ currentExtraSection = null;
1054
+ } else {
1055
+ currentExtraSection = { title: heading, lines: [] };
1056
+ extraSections.push(currentExtraSection);
1057
+ }
439
1058
  continue;
440
1059
  }
1060
+ if (currentExtraSection) {
1061
+ currentExtraSection.lines.push(line);
1062
+ }
441
1063
  if (!line.startsWith("- ")) continue;
442
1064
  const bullet = line.slice(2).trim();
443
1065
  if (!bullet) continue;
444
1066
  switch (section) {
445
1067
  case "facts":
446
- facts.push(bullet);
1068
+ legacyFacts.push(bullet);
447
1069
  break;
1070
+ case "timeline": {
1071
+ const parsed = parseEntityTimelineBullet(
1072
+ bullet,
1073
+ fallbackTimestamp
1074
+ );
1075
+ if (parsed) timeline.push(parsed);
1076
+ break;
1077
+ }
448
1078
  case "summary":
1079
+ case "synthesis":
1080
+ if (isEntitySynthesisTimelinePromotionBullet(bullet)) {
1081
+ const parsed = parseEntityTimelineBullet(
1082
+ bullet,
1083
+ fallbackTimestamp
1084
+ );
1085
+ if (parsed) timeline.push(parsed);
1086
+ }
449
1087
  break;
450
1088
  case "connected to": {
451
1089
  const relMatch = bullet.match(/^\[\[([^\]]+)\]\]\s*[—–-]\s*(.+)$/);
@@ -466,34 +1104,128 @@ function parseEntityFile(content) {
466
1104
  break;
467
1105
  }
468
1106
  }
469
- const summaryIdx = lines.findIndex((l) => l.startsWith("## Summary"));
470
- if (summaryIdx !== -1) {
471
- const summaryLines = [];
472
- for (let i = summaryIdx + 1; i < lines.length; i++) {
473
- if (lines[i].startsWith("## ")) break;
474
- const trimmed = lines[i].trim();
475
- if (trimmed) summaryLines.push(trimmed);
1107
+ const legacyFactTimelineEntries = legacyFacts.map((fact) => ({
1108
+ timestamp: fallbackTimestamp,
1109
+ text: fact,
1110
+ source: "migration"
1111
+ }));
1112
+ if (legacyFactTimelineEntries.length > 0) {
1113
+ const existingTimelineFacts = new Set(
1114
+ timeline.map((entry) => entry.text.trim()).filter((entry) => entry.length > 0)
1115
+ );
1116
+ for (const fact of legacyFactTimelineEntries) {
1117
+ const normalizedFact = fact.text.trim();
1118
+ if (!normalizedFact || existingTimelineFacts.has(normalizedFact)) continue;
1119
+ timeline.push(fact);
1120
+ existingTimelineFacts.add(normalizedFact);
476
1121
  }
477
- if (summaryLines.length > 0) summary = summaryLines.join(" ");
478
1122
  }
479
- return { name, type, updated, facts, summary, relationships, activity, aliases };
1123
+ const synthesis = readEntitySectionText(lines, ["Synthesis"], { preserveBullets: true, skipTimelineBullets: true }) ?? readEntitySectionText(lines, ["Summary"], { preserveBullets: true, skipTimelineBullets: true });
1124
+ const synthesisUpdatedAt = frontmatter.synthesisUpdatedAt || void 0;
1125
+ const synthesisTimelineCount = frontmatter.synthesisTimelineCount;
1126
+ const synthesisStructuredFactCount = frontmatter.synthesisStructuredFactCount;
1127
+ const synthesisStructuredFactDigest = frontmatter.synthesisStructuredFactDigest;
1128
+ const { structuredSections, remainingExtraSections } = partitionEntityStructuredSections(
1129
+ type,
1130
+ extraSections,
1131
+ entitySchemas
1132
+ );
1133
+ const facts = compileEntityFacts(timeline, structuredSections);
1134
+ return {
1135
+ name,
1136
+ type,
1137
+ created,
1138
+ updated,
1139
+ extraFrontmatterLines: frontmatter.extraLines ?? [],
1140
+ preSectionLines: preservedPreSectionLines,
1141
+ facts,
1142
+ summary: synthesis,
1143
+ synthesis,
1144
+ synthesisUpdatedAt,
1145
+ synthesisTimelineCount,
1146
+ synthesisStructuredFactCount,
1147
+ synthesisStructuredFactDigest,
1148
+ synthesisVersion: frontmatter.synthesisVersion,
1149
+ timeline,
1150
+ structuredSections,
1151
+ relationships,
1152
+ activity,
1153
+ aliases,
1154
+ extraSections: remainingExtraSections
1155
+ };
480
1156
  }
481
- function serializeEntityFile(entity) {
1157
+ function serializeEntityFile(entity, entitySchemas) {
1158
+ const synthesis = entity.synthesis || entity.summary || "";
1159
+ const created = entity.created?.trim() || entity.updated || (/* @__PURE__ */ new Date()).toISOString();
1160
+ const updated = entity.updated || created;
1161
+ const timeline = entity.timeline;
1162
+ const structuredSections = sortStructuredSectionsBySchema(
1163
+ entity.type,
1164
+ (entity.structuredSections ?? []).map((section) => ({
1165
+ ...section,
1166
+ facts: normalizeStructuredSectionFacts(section.facts)
1167
+ })).filter((section) => section.facts.length > 0),
1168
+ entitySchemas
1169
+ );
1170
+ const sectionFacts = new Set(collectStructuredSectionFacts(structuredSections));
1171
+ const legacyFacts = timeline.length === 0 ? [...new Set(
1172
+ entity.facts.map((fact) => normalizeEntitySectionFact(fact)).filter((fact) => fact.length > 0 && !sectionFacts.has(fact))
1173
+ )] : [];
1174
+ const synthesisUpdatedAt = entity.synthesisUpdatedAt?.trim() || "";
1175
+ const synthesisTimelineCount = entity.synthesisTimelineCount;
1176
+ const synthesisStructuredFactCount = entity.synthesisStructuredFactCount;
1177
+ const synthesisStructuredFactDigest = entity.synthesisStructuredFactDigest?.trim() || "";
1178
+ const synthesisVersion = entity.synthesisVersion ?? (synthesis ? 1 : 0);
482
1179
  const lines = [
1180
+ "---",
1181
+ `created: ${created}`,
1182
+ `updated: ${updated}`,
1183
+ `synthesis_updated_at: "${synthesisUpdatedAt}"`,
1184
+ ...synthesisTimelineCount === void 0 ? [] : [`synthesis_timeline_count: ${synthesisTimelineCount}`],
1185
+ ...synthesisStructuredFactCount === void 0 ? [] : [`synthesis_structured_fact_count: ${synthesisStructuredFactCount}`],
1186
+ ...synthesisStructuredFactDigest ? [`synthesis_structured_fact_digest: "${synthesisStructuredFactDigest}"`] : [],
1187
+ `synthesis_version: ${synthesisVersion}`,
1188
+ ...entity.extraFrontmatterLines ?? [],
1189
+ "---",
1190
+ "",
483
1191
  `# ${entity.name}`,
484
1192
  "",
485
1193
  `**Type:** ${entity.type}`,
486
- `**Updated:** ${entity.updated || (/* @__PURE__ */ new Date()).toISOString()}`,
1194
+ `**Updated:** ${updated}`,
487
1195
  ""
488
1196
  ];
489
- if (entity.summary) {
490
- lines.push("## Summary", "", entity.summary, "");
1197
+ if ((entity.preSectionLines ?? []).length > 0) {
1198
+ lines.push(...entity.preSectionLines ?? []);
1199
+ if (entity.preSectionLines?.[entity.preSectionLines.length - 1] !== "") {
1200
+ lines.push("");
1201
+ }
491
1202
  }
492
- lines.push("## Facts", "");
493
- for (const f of entity.facts) {
494
- lines.push(`- ${f}`);
1203
+ lines.push("## Synthesis", "");
1204
+ if (synthesis) {
1205
+ lines.push(synthesis);
495
1206
  }
496
1207
  lines.push("");
1208
+ if (timeline.length > 0 || legacyFacts.length === 0) {
1209
+ lines.push("## Timeline", "");
1210
+ for (const entry of timeline) {
1211
+ lines.push(serializeEntityTimelineEntry(entry));
1212
+ }
1213
+ lines.push("");
1214
+ }
1215
+ if (legacyFacts.length > 0) {
1216
+ lines.push("## Facts", "");
1217
+ for (const fact of legacyFacts) {
1218
+ lines.push(`- ${fact}`);
1219
+ }
1220
+ lines.push("");
1221
+ }
1222
+ for (const section of structuredSections) {
1223
+ lines.push(`## ${section.title}`, "");
1224
+ for (const fact of section.facts) {
1225
+ lines.push(`- ${fact}`);
1226
+ }
1227
+ lines.push("");
1228
+ }
497
1229
  if (entity.relationships.length > 0) {
498
1230
  lines.push("## Connected to", "");
499
1231
  for (const rel of entity.relationships) {
@@ -515,13 +1247,37 @@ function serializeEntityFile(entity) {
515
1247
  }
516
1248
  lines.push("");
517
1249
  }
1250
+ for (const section of entity.extraSections ?? []) {
1251
+ lines.push(`## ${section.title}`);
1252
+ lines.push(...section.lines);
1253
+ if (section.lines.length > 0 && section.lines[section.lines.length - 1] !== "") {
1254
+ lines.push("");
1255
+ }
1256
+ }
518
1257
  return lines.join("\n");
519
1258
  }
1259
+ function buildEntitySchemaCacheKey(entitySchemas) {
1260
+ if (!entitySchemas) return "";
1261
+ const normalized = Object.entries(entitySchemas).sort(([left], [right]) => left.localeCompare(right)).map(([entityType, schema]) => [
1262
+ entityType,
1263
+ {
1264
+ sections: schema.sections.map((section) => ({
1265
+ key: section.key,
1266
+ title: section.title,
1267
+ description: section.description,
1268
+ aliases: section.aliases ? [...section.aliases] : void 0
1269
+ }))
1270
+ }
1271
+ ]);
1272
+ return JSON.stringify(normalized);
1273
+ }
520
1274
  var StorageManager = class _StorageManager {
521
- constructor(baseDir) {
1275
+ constructor(baseDir, entitySchemas) {
522
1276
  this.baseDir = baseDir;
1277
+ this.entitySchemas = entitySchemas;
523
1278
  }
524
1279
  baseDir;
1280
+ entitySchemas;
525
1281
  knowledgeIndexCache = null;
526
1282
  static KNOWLEDGE_INDEX_CACHE_TTL_MS = 6e5;
527
1283
  // 10 minutes (entity mutations invalidate)
@@ -530,6 +1286,9 @@ var StorageManager = class _StorageManager {
530
1286
  // 1 minute
531
1287
  static artifactWriteVersionByDir = /* @__PURE__ */ new Map();
532
1288
  static memoryStatusVersionByDir = /* @__PURE__ */ new Map();
1289
+ // In-process fallback for the cold-write sentinel (used when the disk file
1290
+ // is not accessible). The canonical source of truth is state/cold-write.log.
1291
+ static coldWriteVersionByDir = /* @__PURE__ */ new Map();
533
1292
  // Module-level cache for readAllMemories() keyed by base directory.
534
1293
  // Shared across all StorageManager instances to avoid duplicate I/O when
535
1294
  // multiple concurrent callers (e.g. verifiedRecall + verifiedRules) read the
@@ -541,6 +1300,29 @@ var StorageManager = class _StorageManager {
541
1300
  // refresh. This eliminates the 13-60 s cold-scan penalty that would otherwise
542
1301
  // block recall requests every 5 minutes on large memory collections (80k+ files).
543
1302
  static allMemoriesInFlight = /* @__PURE__ */ new Map();
1303
+ // Cache for readAllColdMemories() — keyed by cold root directory path.
1304
+ // Prevents an uncached full-tree directory scan on every structured-attribute
1305
+ // write (Finding UOGi, PR #402 round-6). The cache is only invalidated when
1306
+ // cold-tier content actually changes (via invalidateColdMemoriesCache), NOT
1307
+ // on every hot-tier write. It also expires after COLD_SCAN_CACHE_TTL_MS as
1308
+ // a safety net.
1309
+ //
1310
+ // Finding UvUy (PR #402 round-11): cache entries now carry a `coldVersion`
1311
+ // sentinel that is bumped (via a file-size counter in state/cold-write.log)
1312
+ // on every write that modifies cold-tier content. Before serving a cached
1313
+ // result, readAllColdMemories() reads the sentinel from disk and compares.
1314
+ // If they differ the entry is dropped and the cold tree is re-scanned. This
1315
+ // makes the cache correct across process boundaries (gateway + CLI): a second
1316
+ // process that writes a new cold memory bumps the sentinel on disk, so the
1317
+ // first process's next readAllColdMemories() sees the change within one call
1318
+ // (rather than waiting up to 30s for TTL expiry).
1319
+ //
1320
+ // After Finding UTsP broadened readAllColdMemories to scan the entire cold/
1321
+ // subtree (not just facts/+corrections/), amortizing this I/O across
1322
+ // back-to-back writes in the same burst is even more important.
1323
+ static COLD_SCAN_CACHE_TTL_MS = 3e4;
1324
+ // 30 seconds
1325
+ static coldMemoriesCache = /* @__PURE__ */ new Map();
544
1326
  // Cache for readQuestions() — avoids serially re-reading tens of thousands of
545
1327
  // question files on every recall. 60-second TTL is intentionally short so that
546
1328
  // newly written questions surface quickly.
@@ -551,6 +1333,26 @@ var StorageManager = class _StorageManager {
551
1333
  factHashIndexLoadPromise = null;
552
1334
  factHashIndexAuthoritative = null;
553
1335
  factHashIndexAuthoritativePromise = null;
1336
+ /** Optional: set by the orchestrator after construction to enable template-aware citation stripping during legacy hash rebuild. */
1337
+ citationTemplate = DEFAULT_CITATION_FORMAT;
1338
+ /** Page-versioning configuration. Set by the orchestrator after construction. */
1339
+ _versioningConfig = null;
1340
+ /** Set the page-versioning configuration. When `enabled` is false (default), all versioning calls are no-ops. */
1341
+ setVersioningConfig(config) {
1342
+ this._versioningConfig = config;
1343
+ }
1344
+ /**
1345
+ * Snapshot the current content of a page before overwriting.
1346
+ * No-op when versioning is disabled or the file does not yet exist.
1347
+ */
1348
+ async snapshotBeforeWrite(filePath, trigger) {
1349
+ if (!this._versioningConfig || !this._versioningConfig.enabled) return;
1350
+ try {
1351
+ const existing = await readFile(filePath, "utf-8");
1352
+ await createVersion(filePath, existing, trigger, this._versioningConfig, log, void 0, this.baseDir);
1353
+ } catch {
1354
+ }
1355
+ }
554
1356
  /** The root directory of this storage instance. */
555
1357
  get dir() {
556
1358
  return this.baseDir;
@@ -562,7 +1364,7 @@ var StorageManager = class _StorageManager {
562
1364
  return path.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
563
1365
  }
564
1366
  versionFilePath(kind) {
565
- const fileName = kind === "memory-status" ? ".memory-status-version.log" : ".artifact-write-version.log";
1367
+ const fileName = kind === "memory-status" ? ".memory-status-version.log" : kind === "artifact-write" ? ".artifact-write-version.log" : ".cold-write-version.log";
566
1368
  return path.join(this.stateDir, fileName);
567
1369
  }
568
1370
  bumpSharedVersion(kind, fallbackMap) {
@@ -605,12 +1407,18 @@ var StorageManager = class _StorageManager {
605
1407
  get correctionsDir() {
606
1408
  return path.join(this.baseDir, "corrections");
607
1409
  }
1410
+ get proceduresDir() {
1411
+ return path.join(this.baseDir, "procedures");
1412
+ }
608
1413
  get entitiesDir() {
609
1414
  return path.join(this.baseDir, "entities");
610
1415
  }
611
1416
  get stateDir() {
612
1417
  return path.join(this.baseDir, "state");
613
1418
  }
1419
+ get entitySynthesisQueuePath() {
1420
+ return path.join(this.stateDir, "entity-synthesis-queue.json");
1421
+ }
614
1422
  get factHashIndexReadyPath() {
615
1423
  return path.join(this.stateDir, "fact-hashes.ready");
616
1424
  }
@@ -647,10 +1455,35 @@ var StorageManager = class _StorageManager {
647
1455
  }
648
1456
  const factHashIndex = await this.getFactHashIndex();
649
1457
  const existing = await this.readAllMemories();
1458
+ let legacyRecovered = 0;
650
1459
  for (const memory of existing) {
651
1460
  if (memory.frontmatter.category !== "fact") continue;
652
1461
  if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") continue;
653
- factHashIndex.add(memory.content);
1462
+ if (memory.frontmatter.contentHash) {
1463
+ factHashIndex.addByHash(memory.frontmatter.contentHash);
1464
+ continue;
1465
+ }
1466
+ const content = memory.content;
1467
+ const stripped = stripCitationForTemplate(content, this.citationTemplate);
1468
+ if (stripped !== content) {
1469
+ factHashIndex.addByHash(
1470
+ ContentHashIndex.computeHash(sanitizeMemoryContent(stripped).text)
1471
+ );
1472
+ continue;
1473
+ }
1474
+ if (!hasCitation(content)) {
1475
+ factHashIndex.addByHash(
1476
+ ContentHashIndex.computeHash(sanitizeMemoryContent(content).text)
1477
+ );
1478
+ continue;
1479
+ }
1480
+ legacyRecovered++;
1481
+ continue;
1482
+ }
1483
+ if (legacyRecovered > 0) {
1484
+ log.info(
1485
+ `ensureFactHashIndexAuthoritative: skipped ${legacyRecovered} legacy fact(s) with no contentHash in frontmatter`
1486
+ );
654
1487
  }
655
1488
  await factHashIndex.save();
656
1489
  await mkdir(path.dirname(this.factHashIndexReadyPath), { recursive: true });
@@ -733,6 +1566,7 @@ var StorageManager = class _StorageManager {
733
1566
  async ensureDirectories() {
734
1567
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
735
1568
  await mkdir(path.join(this.factsDir, today), { recursive: true });
1569
+ await mkdir(path.join(this.proceduresDir, today), { recursive: true });
736
1570
  await mkdir(this.correctionsDir, { recursive: true });
737
1571
  await mkdir(this.entitiesDir, { recursive: true });
738
1572
  await mkdir(this.stateDir, { recursive: true });
@@ -782,16 +1616,22 @@ var StorageManager = class _StorageManager {
782
1616
  memoryKind: options.memoryKind,
783
1617
  structuredAttributes: options.structuredAttributes
784
1618
  };
1619
+ if (options.status !== void 0) {
1620
+ fm.status = options.status;
1621
+ }
785
1622
  let enrichedContent = content;
786
1623
  if (options.structuredAttributes && Object.keys(options.structuredAttributes).length > 0) {
787
- const attrLines = Object.entries(options.structuredAttributes).map(([k, v]) => `${k}: ${v}`).join("; ");
788
1624
  enrichedContent = `${content}
789
- [Attributes: ${attrLines}]`;
1625
+ [Attributes: ${normalizeAttributePairs(options.structuredAttributes)}]`;
790
1626
  }
791
1627
  const sanitized = sanitizeMemoryContent(enrichedContent);
792
1628
  if (!sanitized.clean) {
793
1629
  log.warn(`memory content sanitized for ${id}; violations=${sanitized.violations.join(", ")}`);
794
1630
  }
1631
+ if (category === "fact") {
1632
+ const hashSource = options.contentHashSource !== void 0 && options.contentHashSource.length > 0 ? sanitizeMemoryContent(options.contentHashSource).text : sanitized.text;
1633
+ fm.contentHash = ContentHashIndex.computeHash(hashSource);
1634
+ }
795
1635
  const fileContent = `${serializeFrontmatter(fm)}
796
1636
 
797
1637
  ${sanitized.text}
@@ -799,9 +1639,13 @@ ${sanitized.text}
799
1639
  let filePath;
800
1640
  if (category === "correction") {
801
1641
  filePath = path.join(this.correctionsDir, `${id}.md`);
1642
+ } else if (category === "procedure") {
1643
+ await mkdir(path.join(this.proceduresDir, today), { recursive: true });
1644
+ filePath = path.join(this.proceduresDir, today, `${id}.md`);
802
1645
  } else {
803
1646
  filePath = path.join(this.factsDir, today, `${id}.md`);
804
1647
  }
1648
+ await this.snapshotBeforeWrite(filePath, "write");
805
1649
  await writeFile(filePath, fileContent, "utf-8");
806
1650
  this.invalidateAllMemoriesCache();
807
1651
  await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeMemory", {
@@ -818,7 +1662,12 @@ ${sanitized.text}
818
1662
  if (category === "fact") {
819
1663
  try {
820
1664
  const factHashIndex = await this.getFactHashIndex();
821
- factHashIndex.add(sanitized.text);
1665
+ if (options.contentHashSource !== void 0 && options.contentHashSource.length > 0) {
1666
+ const hashSourceSanitized = sanitizeMemoryContent(options.contentHashSource);
1667
+ factHashIndex.add(hashSourceSanitized.text);
1668
+ } else {
1669
+ factHashIndex.add(sanitized.text);
1670
+ }
822
1671
  await factHashIndex.save();
823
1672
  } catch (err) {
824
1673
  log.warn(`storage.writeMemory completed but failed to update fact hash index: ${err}`);
@@ -941,7 +1790,7 @@ ${sanitized.text}
941
1790
  hits.sort((a, b) => b.score - a.score);
942
1791
  return hits.slice(0, maxResults).map((h) => h.memory);
943
1792
  }
944
- async writeEntity(name, type, facts) {
1793
+ async writeEntity(name, type, facts, options = {}) {
945
1794
  await this.ensureDirectories();
946
1795
  if (typeof name !== "string" || !name.trim() || typeof type !== "string" || !type.trim()) {
947
1796
  log.warn("writeEntity: invalid entity payload, skipping", {
@@ -950,7 +1799,9 @@ ${sanitized.text}
950
1799
  });
951
1800
  return "";
952
1801
  }
953
- const safeFacts = Array.isArray(facts) ? facts.filter((f) => typeof f === "string") : [];
1802
+ const safeFacts = Array.isArray(facts) ? [...new Set(
1803
+ facts.filter((fact) => typeof fact === "string").map((fact) => fact.trim()).filter((fact) => fact.length > 0)
1804
+ )] : [];
954
1805
  let normalized = normalizeEntityName(name, type);
955
1806
  const match = await this.findMatchingEntity(name, type);
956
1807
  if (match && match !== normalized) {
@@ -961,23 +1812,80 @@ ${sanitized.text}
961
1812
  let entity = {
962
1813
  name,
963
1814
  type,
1815
+ created: "",
964
1816
  updated: (/* @__PURE__ */ new Date()).toISOString(),
965
1817
  facts: [],
966
1818
  summary: void 0,
1819
+ synthesis: void 0,
1820
+ synthesisUpdatedAt: void 0,
1821
+ synthesisVersion: void 0,
1822
+ synthesisStructuredFactCount: void 0,
1823
+ synthesisStructuredFactDigest: void 0,
1824
+ timeline: [],
967
1825
  relationships: [],
968
1826
  activity: [],
969
1827
  aliases: []
970
1828
  };
971
1829
  try {
972
1830
  const existing = await readFile(filePath, "utf-8");
973
- entity = parseEntityFile(existing);
1831
+ entity = parseEntityFile(existing, this.entitySchemas);
974
1832
  } catch {
975
1833
  }
976
- entity.facts = [.../* @__PURE__ */ new Set([...entity.facts, ...safeFacts])];
1834
+ const timestamp = options.timestamp?.trim() || (/* @__PURE__ */ new Date()).toISOString();
1835
+ const source = options.source?.trim() || void 0;
1836
+ const sessionKey = options.sessionKey?.trim() || void 0;
1837
+ const principal = options.principal?.trim() || void 0;
1838
+ const structuredSectionMap = new Map(
1839
+ (entity.structuredSections ?? []).map((section) => [section.key, {
1840
+ ...section,
1841
+ facts: [...section.facts]
1842
+ }])
1843
+ );
1844
+ for (const section of options.structuredSections ?? []) {
1845
+ const normalizedSection = normalizeEntityStructuredSection(type, section, this.entitySchemas);
1846
+ const normalizedFacts = normalizeStructuredSectionFacts(section.facts);
1847
+ if (normalizedFacts.length === 0) continue;
1848
+ const existingSection = structuredSectionMap.get(normalizedSection.key);
1849
+ if (!existingSection) {
1850
+ structuredSectionMap.set(normalizedSection.key, {
1851
+ key: normalizedSection.key,
1852
+ title: normalizedSection.title,
1853
+ facts: normalizedFacts
1854
+ });
1855
+ continue;
1856
+ }
1857
+ existingSection.facts = normalizeStructuredSectionFacts([...existingSection.facts, ...normalizedFacts]);
1858
+ if (!existingSection.title.trim() && normalizedSection.title.trim()) {
1859
+ existingSection.title = normalizedSection.title;
1860
+ }
1861
+ }
1862
+ for (const fact of safeFacts) {
1863
+ const nextEntry = {
1864
+ timestamp,
1865
+ text: fact,
1866
+ ...source ? { source } : {},
1867
+ ...sessionKey ? { sessionKey } : {},
1868
+ ...principal ? { principal } : {}
1869
+ };
1870
+ const alreadyPresent = entity.timeline.some(
1871
+ (entry) => entry.timestamp === nextEntry.timestamp && entry.text === nextEntry.text && entry.source === nextEntry.source && entry.sessionKey === nextEntry.sessionKey && entry.principal === nextEntry.principal
1872
+ );
1873
+ if (alreadyPresent) continue;
1874
+ entity.timeline.push(nextEntry);
1875
+ }
1876
+ entity.structuredSections = sortStructuredSectionsBySchema(
1877
+ type,
1878
+ Array.from(structuredSectionMap.values()).filter((section) => section.facts.length > 0),
1879
+ this.entitySchemas
1880
+ );
1881
+ entity.facts = compileEntityFacts(entity.timeline, entity.structuredSections);
1882
+ entity.summary = entity.synthesis || entity.summary;
977
1883
  entity.name = name;
978
1884
  entity.type = type;
1885
+ entity.created = entity.created || timestamp;
979
1886
  entity.updated = (/* @__PURE__ */ new Date()).toISOString();
980
- await writeFile(filePath, serializeEntityFile(entity), "utf-8");
1887
+ await this.snapshotBeforeWrite(filePath, "write");
1888
+ await writeFile(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
981
1889
  this.invalidateKnowledgeIndexCache();
982
1890
  this.bumpMemoryStatusVersion();
983
1891
  log.debug(`wrote entity ${normalized}`);
@@ -992,6 +1900,7 @@ ${sanitized.text}
992
1900
  }
993
1901
  async writeProfile(content) {
994
1902
  await this.ensureDirectories();
1903
+ await this.snapshotBeforeWrite(this.profilePath, "consolidation");
995
1904
  await writeFile(this.profilePath, content, "utf-8");
996
1905
  log.debug("updated profile.md");
997
1906
  }
@@ -1080,12 +1989,51 @@ ${sanitized.text}
1080
1989
  static clearAllStaticCaches() {
1081
1990
  _StorageManager.allMemoriesInFlight.clear();
1082
1991
  _StorageManager.questionsCache.clear();
1992
+ _StorageManager.coldMemoriesCache.clear();
1083
1993
  }
1084
1994
  /** Cancel any in-flight concurrent read so the next readAllMemories()
1085
- * starts a fresh disk scan and sees the just-written data. */
1995
+ * starts a fresh disk scan and sees the just-written data.
1996
+ *
1997
+ * Finding UvBq (PR #402 round-11): this method intentionally does NOT
1998
+ * invalidate the cold-scan cache. Ordinary hot-tier writes (writeMemory)
1999
+ * do not change cold-tier content, so evicting the cold cache on every hot
2000
+ * write was defeating the burst-dedup optimisation — the cold cache was
2001
+ * cleared before applyTemporalSupersession ran, causing a full cold-tree
2002
+ * disk scan on every write in a burst. Cold cache invalidation is handled
2003
+ * exclusively by invalidateColdMemoriesCache(), which is called only when
2004
+ * cold content actually changes (hot→cold demotions, writeMemoryFileAtomic
2005
+ * inside cold/, archiveMemory, etc.). */
1086
2006
  invalidateAllMemoriesCache() {
1087
2007
  _StorageManager.allMemoriesInFlight.delete(this.baseDir);
1088
2008
  }
2009
+ /**
2010
+ * Invalidate the cold-scan cache for this storage root and bump the
2011
+ * on-disk cold-version sentinel so that other processes (gateway, CLI) see
2012
+ * the change immediately on their next readAllColdMemories() call.
2013
+ *
2014
+ * Must be called whenever a memory is written INTO the cold tier — hot→cold
2015
+ * demotion, atomic writes inside cold/, archiving a cold memory, etc.
2016
+ * NOT called on ordinary hot-tier writes (those don't change cold contents).
2017
+ *
2018
+ * Finding UvUy (PR #402 round-11): bumping the sentinel here makes the
2019
+ * per-process in-memory cache safe across process boundaries.
2020
+ */
2021
+ invalidateColdMemoriesCache() {
2022
+ const coldRoot = path.join(this.baseDir, "cold");
2023
+ _StorageManager.coldMemoriesCache.delete(coldRoot);
2024
+ this.bumpColdWriteVersion();
2025
+ }
2026
+ /** Return the current cold-write version counter for this storage root.
2027
+ * Reads the on-disk sentinel (state/cold-write.log) so it reflects writes
2028
+ * made by other processes. */
2029
+ readColdWriteVersion() {
2030
+ return this.readSharedVersion("cold-write", _StorageManager.coldWriteVersionByDir);
2031
+ }
2032
+ /** Bump the on-disk cold-write version sentinel and update the in-process
2033
+ * fallback map. Called by invalidateColdMemoriesCache(). */
2034
+ bumpColdWriteVersion() {
2035
+ this.bumpSharedVersion("cold-write", _StorageManager.coldWriteVersionByDir);
2036
+ }
1089
2037
  normalizeMemoryReadBatchSize(batchSize) {
1090
2038
  if (typeof batchSize !== "number" || !Number.isFinite(batchSize)) {
1091
2039
  return 50;
@@ -1113,6 +2061,7 @@ ${sanitized.text}
1113
2061
  }
1114
2062
  };
1115
2063
  await collectPaths(this.factsDir);
2064
+ await collectPaths(this.proceduresDir);
1116
2065
  await collectPaths(this.correctionsDir);
1117
2066
  return filePaths;
1118
2067
  }
@@ -1132,7 +2081,8 @@ ${sanitized.text}
1132
2081
  path: fullPath,
1133
2082
  frontmatter: normalizeFrontmatterForPath(
1134
2083
  parsed.frontmatter,
1135
- toMemoryPathRel(this.baseDir, fullPath)
2084
+ toMemoryPathRel(this.baseDir, fullPath),
2085
+ parsed.content
1136
2086
  ),
1137
2087
  content: parsed.content
1138
2088
  };
@@ -1261,6 +2211,62 @@ ${sanitized.text}
1261
2211
  const filePaths = await this.collectActiveMemoryPaths();
1262
2212
  return this.readParsedMemoriesFromPaths(filePaths, 50);
1263
2213
  }
2214
+ /**
2215
+ * Read all memories from the cold tier by scanning the entire cold/ root
2216
+ * tree. Previously this only scanned cold/facts/ and cold/corrections/, but
2217
+ * structuredAttributes can appear on any MemoryCategory (preference, decision,
2218
+ * entity, etc.). Although buildTierMemoryPath currently routes all
2219
+ * non-correction, non-artifact memories to cold/facts/, scanning the full
2220
+ * coldRoot ensures correctness if that routing ever changes and guards against
2221
+ * files placed in unexpected subdirectories during manual operations or future
2222
+ * refactors.
2223
+ *
2224
+ * Broadened in PR #402 round-6 (Finding UTsP): scanning only facts/ and
2225
+ * corrections/ was a narrower-than-necessary subset of the cold directory
2226
+ * tree. Correctness trumps the minor performance difference — cold scans
2227
+ * already happen at most once per supersession write.
2228
+ *
2229
+ * Used by applyTemporalSupersession so that memories already demoted to
2230
+ * cold/ can still be marked superseded when a newer hot fact arrives.
2231
+ *
2232
+ * Cached with a TTL (Finding UOGi, PR #402 round-6): back-to-back
2233
+ * structured-attribute writes in the same burst reuse the cached result
2234
+ * instead of re-scanning the cold tree on every call. The cache is
2235
+ * invalidated whenever a write calls invalidateAllMemoriesCache() (which
2236
+ * covers any hot→cold demotion that changes cold-tier contents) and
2237
+ * expires after COLD_SCAN_CACHE_TTL_MS as a safety net.
2238
+ */
2239
+ async readAllColdMemories() {
2240
+ const coldRoot = this.resolveTierRootDir("cold");
2241
+ const currentColdVersion = this.readColdWriteVersion();
2242
+ const cached = _StorageManager.coldMemoriesCache.get(coldRoot);
2243
+ if (cached && Date.now() - cached.loadedAt < _StorageManager.COLD_SCAN_CACHE_TTL_MS && cached.coldVersion === currentColdVersion) {
2244
+ return cached.memories;
2245
+ }
2246
+ const filePaths = [];
2247
+ const collectPaths = async (dir) => {
2248
+ try {
2249
+ const entries = await readdir(dir, { withFileTypes: true });
2250
+ const subdirs = [];
2251
+ for (const entry of entries) {
2252
+ const fullPath = path.join(dir, entry.name);
2253
+ if (entry.isDirectory()) {
2254
+ subdirs.push(fullPath);
2255
+ } else if (entry.name.endsWith(".md")) {
2256
+ filePaths.push(fullPath);
2257
+ }
2258
+ }
2259
+ for (const subdir of subdirs) {
2260
+ await collectPaths(subdir);
2261
+ }
2262
+ } catch {
2263
+ }
2264
+ };
2265
+ await collectPaths(coldRoot);
2266
+ const memories = await this.readParsedMemoriesFromPaths(filePaths, 50);
2267
+ _StorageManager.coldMemoriesCache.set(coldRoot, { memories, loadedAt: Date.now(), coldVersion: currentColdVersion });
2268
+ return memories;
2269
+ }
1264
2270
  /**
1265
2271
  * Read archived memory markdown files under archive/.
1266
2272
  * Used by long-term recall fallback when hot recall has no hits.
@@ -1284,7 +2290,8 @@ ${sanitized.text}
1284
2290
  path: fullPath,
1285
2291
  frontmatter: normalizeFrontmatterForPath(
1286
2292
  parsed.frontmatter,
1287
- toMemoryPathRel(this.baseDir, fullPath)
2293
+ toMemoryPathRel(this.baseDir, fullPath),
2294
+ parsed.content
1288
2295
  ),
1289
2296
  content: parsed.content
1290
2297
  });
@@ -1309,14 +2316,15 @@ ${sanitized.text}
1309
2316
  path: filePath,
1310
2317
  frontmatter: normalizeFrontmatterForPath(
1311
2318
  parsed.frontmatter,
1312
- toMemoryPathRel(this.baseDir, filePath)
2319
+ toMemoryPathRel(this.baseDir, filePath),
2320
+ parsed.content
1313
2321
  ),
1314
2322
  content: parsed.content
1315
2323
  };
1316
2324
  }
1317
2325
  const normalizedPath = filePath.split(path.sep).join("/");
1318
2326
  if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
1319
- const entity = parseEntityFile(raw);
2327
+ const entity = parseEntityFile(raw, this.entitySchemas);
1320
2328
  if (!entity.name) return null;
1321
2329
  const nameWithoutExt = path.basename(filePath, ".md");
1322
2330
  const fileMtime = entity.updated || await stat(filePath).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
@@ -1361,6 +2369,9 @@ ${sanitized.text}
1361
2369
  if (memory.frontmatter.category === "correction") {
1362
2370
  return path.join(root, "corrections", `${memory.frontmatter.id}.md`);
1363
2371
  }
2372
+ if (memory.frontmatter.category === "procedure") {
2373
+ return path.join(root, "procedures", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
2374
+ }
1364
2375
  return path.join(root, "facts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
1365
2376
  }
1366
2377
  async writeMemoryFileAtomic(targetPath, memory) {
@@ -1420,6 +2431,9 @@ ${memory.content}
1420
2431
  }
1421
2432
  await this.moveMemoryToPath(memory, targetPath);
1422
2433
  this.invalidateAllMemoriesCache();
2434
+ if (targetTier === "cold") {
2435
+ this.invalidateColdMemoriesCache();
2436
+ }
1423
2437
  this.bumpMemoryStatusVersion();
1424
2438
  return { changed: true, targetPath };
1425
2439
  }
@@ -1603,6 +2617,9 @@ ${memory.content}
1603
2617
  `;
1604
2618
  await writeFile(memory.path, fileContent, "utf-8");
1605
2619
  this.invalidateAllMemoriesCache();
2620
+ if (memory.path.includes(`${path.sep}cold${path.sep}`)) {
2621
+ this.invalidateColdMemoriesCache();
2622
+ }
1606
2623
  await this.appendGeneratedMemoryLifecycleEventFailOpen(
1607
2624
  "storage.writeMemoryFrontmatter",
1608
2625
  {
@@ -1678,14 +2695,30 @@ ${memory.content}
1678
2695
  const metaPath = path.join(this.stateDir, "meta.json");
1679
2696
  try {
1680
2697
  const raw = await readFile(metaPath, "utf-8");
1681
- return JSON.parse(raw);
2698
+ const parsed = JSON.parse(raw);
2699
+ return {
2700
+ extractionCount: typeof parsed.extractionCount === "number" ? parsed.extractionCount : 0,
2701
+ lastExtractionAt: parsed.lastExtractionAt ?? null,
2702
+ lastConsolidationAt: parsed.lastConsolidationAt ?? null,
2703
+ totalMemories: typeof parsed.totalMemories === "number" ? parsed.totalMemories : 0,
2704
+ totalEntities: typeof parsed.totalEntities === "number" ? parsed.totalEntities : 0,
2705
+ processedExtractionFingerprints: Array.isArray(
2706
+ parsed.processedExtractionFingerprints
2707
+ ) ? parsed.processedExtractionFingerprints.filter(
2708
+ (entry) => entry && typeof entry === "object" && typeof entry.fingerprint === "string" && typeof entry.observedAt === "string"
2709
+ ).map((entry) => ({
2710
+ fingerprint: entry.fingerprint,
2711
+ observedAt: entry.observedAt
2712
+ })) : []
2713
+ };
1682
2714
  } catch {
1683
2715
  return {
1684
2716
  extractionCount: 0,
1685
2717
  lastExtractionAt: null,
1686
2718
  lastConsolidationAt: null,
1687
2719
  totalMemories: 0,
1688
- totalEntities: 0
2720
+ totalEntities: 0,
2721
+ processedExtractionFingerprints: []
1689
2722
  };
1690
2723
  }
1691
2724
  }
@@ -2358,7 +3391,7 @@ ${reflection}
2358
3391
  let entity;
2359
3392
  try {
2360
3393
  const content = await readFile(filePath, "utf-8");
2361
- entity = parseEntityFile(content);
3394
+ entity = parseEntityFile(content, this.entitySchemas);
2362
3395
  } catch {
2363
3396
  log.debug(`addEntityRelationship: entity file ${name}.md not found`);
2364
3397
  return;
@@ -2369,7 +3402,7 @@ ${reflection}
2369
3402
  if (exists) return;
2370
3403
  entity.relationships.push(rel);
2371
3404
  entity.updated = (/* @__PURE__ */ new Date()).toISOString();
2372
- await writeFile(filePath, serializeEntityFile(entity), "utf-8");
3405
+ await writeFile(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
2373
3406
  this.invalidateKnowledgeIndexCache();
2374
3407
  }
2375
3408
  /**
@@ -2381,7 +3414,7 @@ ${reflection}
2381
3414
  let entity;
2382
3415
  try {
2383
3416
  const content = await readFile(filePath, "utf-8");
2384
- entity = parseEntityFile(content);
3417
+ entity = parseEntityFile(content, this.entitySchemas);
2385
3418
  } catch {
2386
3419
  log.debug(`addEntityActivity: entity file ${name}.md not found`);
2387
3420
  return;
@@ -2391,7 +3424,7 @@ ${reflection}
2391
3424
  entity.activity = entity.activity.slice(0, maxEntries);
2392
3425
  }
2393
3426
  entity.updated = (/* @__PURE__ */ new Date()).toISOString();
2394
- await writeFile(filePath, serializeEntityFile(entity), "utf-8");
3427
+ await writeFile(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
2395
3428
  this.invalidateKnowledgeIndexCache();
2396
3429
  }
2397
3430
  /**
@@ -2402,7 +3435,7 @@ ${reflection}
2402
3435
  let entity;
2403
3436
  try {
2404
3437
  const content = await readFile(filePath, "utf-8");
2405
- entity = parseEntityFile(content);
3438
+ entity = parseEntityFile(content, this.entitySchemas);
2406
3439
  } catch {
2407
3440
  log.debug(`addEntityAlias: entity file ${name}.md not found`);
2408
3441
  return;
@@ -2410,28 +3443,142 @@ ${reflection}
2410
3443
  if (entity.aliases.includes(alias)) return;
2411
3444
  entity.aliases.push(alias);
2412
3445
  entity.updated = (/* @__PURE__ */ new Date()).toISOString();
2413
- await writeFile(filePath, serializeEntityFile(entity), "utf-8");
3446
+ await writeFile(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
2414
3447
  this.invalidateKnowledgeIndexCache();
2415
3448
  }
2416
3449
  /**
2417
- * Set or update the summary of an entity file.
3450
+ * Set or rewrite the synthesis layer of an entity file.
2418
3451
  */
2419
- async updateEntitySummary(name, summary) {
3452
+ async updateEntitySynthesis(name, synthesis, options = {}) {
2420
3453
  const filePath = path.join(this.entitiesDir, `${name}.md`);
2421
3454
  let entity;
2422
3455
  try {
2423
3456
  const content = await readFile(filePath, "utf-8");
2424
- entity = parseEntityFile(content);
3457
+ entity = parseEntityFile(content, this.entitySchemas);
2425
3458
  } catch {
2426
- log.debug(`updateEntitySummary: entity file ${name}.md not found`);
3459
+ log.debug(`updateEntitySynthesis: entity file ${name}.md not found`);
2427
3460
  return;
2428
3461
  }
2429
- entity.summary = summary;
2430
- entity.updated = (/* @__PURE__ */ new Date()).toISOString();
2431
- await writeFile(filePath, serializeEntityFile(entity), "utf-8");
3462
+ const updatedAt = options.updatedAt?.trim() || entity.synthesisUpdatedAt?.trim() || void 0;
3463
+ const entityUpdatedAt = options.entityUpdatedAt?.trim() || updatedAt || entity.updated || (/* @__PURE__ */ new Date()).toISOString();
3464
+ const synthesisTimelineCount = Number.isInteger(options.synthesisTimelineCount) && (options.synthesisTimelineCount ?? 0) >= 0 ? options.synthesisTimelineCount : void 0;
3465
+ const synthesisStructuredFactCount = Number.isInteger(options.synthesisStructuredFactCount) && (options.synthesisStructuredFactCount ?? 0) >= 0 ? options.synthesisStructuredFactCount : countEntityStructuredFacts(entity);
3466
+ const synthesisStructuredFactDigest = options.synthesisStructuredFactDigest?.trim() || fingerprintEntityStructuredFacts(entity);
3467
+ entity.synthesis = synthesis.trim();
3468
+ entity.summary = entity.synthesis;
3469
+ entity.synthesisUpdatedAt = updatedAt;
3470
+ entity.synthesisTimelineCount = synthesisTimelineCount;
3471
+ entity.synthesisStructuredFactCount = synthesisStructuredFactCount;
3472
+ entity.synthesisStructuredFactDigest = synthesisStructuredFactDigest;
3473
+ entity.synthesisVersion = Math.max(0, entity.synthesisVersion ?? 0) + (options.incrementVersion === false ? 0 : 1);
3474
+ entity.updated = entityUpdatedAt;
3475
+ await writeFile(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
3476
+ await this.removeEntitySynthesisQueueEntries([
3477
+ .../* @__PURE__ */ new Set([name, normalizeEntityName(entity.name, entity.type)])
3478
+ ]);
2432
3479
  this.invalidateKnowledgeIndexCache();
2433
3480
  this.bumpMemoryStatusVersion();
2434
3481
  }
3482
+ /**
3483
+ * Backward-compatible alias for legacy callers/tests.
3484
+ */
3485
+ async updateEntitySummary(name, summary) {
3486
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3487
+ let synthesisTimelineCount;
3488
+ try {
3489
+ const filePath = path.join(this.entitiesDir, `${name}.md`);
3490
+ const content = await readFile(filePath, "utf-8");
3491
+ synthesisTimelineCount = parseEntityFile(content, this.entitySchemas).timeline.length;
3492
+ } catch {
3493
+ synthesisTimelineCount = void 0;
3494
+ }
3495
+ await this.updateEntitySynthesis(name, summary, {
3496
+ entityUpdatedAt: updatedAt,
3497
+ synthesisTimelineCount,
3498
+ updatedAt
3499
+ });
3500
+ }
3501
+ async readEntitySynthesisQueue() {
3502
+ try {
3503
+ const raw = await readFile(this.entitySynthesisQueuePath, "utf-8");
3504
+ const parsed = JSON.parse(raw);
3505
+ return Array.isArray(parsed.entityNames) ? parsed.entityNames.filter((value) => typeof value === "string") : [];
3506
+ } catch {
3507
+ return [];
3508
+ }
3509
+ }
3510
+ async refreshEntitySynthesisQueue() {
3511
+ const entityNames = await this.listEntityNames();
3512
+ const entityQueueEntries = await Promise.all(
3513
+ entityNames.map(async (entityName) => {
3514
+ const raw = await this.readEntity(entityName);
3515
+ if (!raw) return null;
3516
+ return {
3517
+ entityName,
3518
+ entity: parseEntityFile(raw, this.entitySchemas)
3519
+ };
3520
+ })
3521
+ );
3522
+ const staleEntityNames = entityQueueEntries.filter((entry) => entry !== null).filter(({ entity }) => isEntitySynthesisStale(entity)).sort((left, right) => {
3523
+ const leftTs = latestEntityTimelineTimestamp(left.entity) ?? "";
3524
+ const rightTs = latestEntityTimelineTimestamp(right.entity) ?? "";
3525
+ return compareEntityTimestamps(rightTs, leftTs);
3526
+ }).map(({ entityName }) => entityName);
3527
+ await mkdir(this.stateDir, { recursive: true });
3528
+ await writeFile(
3529
+ this.entitySynthesisQueuePath,
3530
+ JSON.stringify(
3531
+ {
3532
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3533
+ entityNames: staleEntityNames
3534
+ },
3535
+ null,
3536
+ 2
3537
+ ) + "\n",
3538
+ "utf-8"
3539
+ );
3540
+ return staleEntityNames;
3541
+ }
3542
+ async removeEntitySynthesisQueueEntries(entityNames) {
3543
+ if (entityNames.length === 0) return;
3544
+ const queue = await this.readEntitySynthesisQueue();
3545
+ if (queue.length === 0) return;
3546
+ const removals = new Set(entityNames);
3547
+ const nextQueue = queue.filter((name) => !removals.has(name));
3548
+ await mkdir(this.stateDir, { recursive: true });
3549
+ await writeFile(
3550
+ this.entitySynthesisQueuePath,
3551
+ JSON.stringify(
3552
+ {
3553
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3554
+ entityNames: nextQueue
3555
+ },
3556
+ null,
3557
+ 2
3558
+ ) + "\n",
3559
+ "utf-8"
3560
+ );
3561
+ }
3562
+ async migrateEntityFilesToCompiledTruthTimeline() {
3563
+ const entityNames = await this.listEntityNames();
3564
+ let migrated = 0;
3565
+ for (const entityName of entityNames) {
3566
+ const raw = await this.readEntity(entityName);
3567
+ if (!raw) continue;
3568
+ const serialized = serializeEntityFile(parseEntityFile(raw, this.entitySchemas), this.entitySchemas);
3569
+ if (raw.trimEnd() === serialized.trimEnd()) continue;
3570
+ await writeFile(path.join(this.entitiesDir, `${entityName}.md`), serialized, "utf-8");
3571
+ migrated += 1;
3572
+ }
3573
+ if (migrated > 0) {
3574
+ this.invalidateKnowledgeIndexCache();
3575
+ this.bumpMemoryStatusVersion();
3576
+ }
3577
+ return {
3578
+ total: entityNames.length,
3579
+ migrated
3580
+ };
3581
+ }
2435
3582
  // ---------------------------------------------------------------------------
2436
3583
  // Scoring + Knowledge Index (Knowledge Graph v7.0)
2437
3584
  // ---------------------------------------------------------------------------
@@ -2441,7 +3588,8 @@ ${reflection}
2441
3588
  */
2442
3589
  async readAllEntityFiles() {
2443
3590
  const currentVersion = this.getMemoryStatusVersion();
2444
- const cached = getCachedEntities(this.baseDir, currentVersion);
3591
+ const schemaCacheKey = buildEntitySchemaCacheKey(this.entitySchemas);
3592
+ const cached = getCachedEntities(this.baseDir, currentVersion, schemaCacheKey);
2445
3593
  if (cached) return cached;
2446
3594
  try {
2447
3595
  const entries = await readdir(this.entitiesDir);
@@ -2457,10 +3605,10 @@ ${reflection}
2457
3605
  )
2458
3606
  );
2459
3607
  for (const content of results) {
2460
- if (content !== null) entities.push(parseEntityFile(content));
3608
+ if (content !== null) entities.push(parseEntityFile(content, this.entitySchemas));
2461
3609
  }
2462
3610
  }
2463
- setCachedEntities(this.baseDir, entities, currentVersion);
3611
+ setCachedEntities(this.baseDir, entities, currentVersion, schemaCacheKey);
2464
3612
  return entities;
2465
3613
  } catch {
2466
3614
  return [];
@@ -2510,7 +3658,7 @@ ${reflection}
2510
3658
  type: e.type,
2511
3659
  score: _StorageManager.scoreEntity(e, now),
2512
3660
  factCount: e.facts.length,
2513
- summary: e.summary,
3661
+ summary: e.synthesis ?? e.summary,
2514
3662
  topRelationships: e.relationships.slice(0, 3).map((r) => r.target)
2515
3663
  }));
2516
3664
  scored.sort((a, b) => b.score - a.score);
@@ -2573,38 +3721,121 @@ ${rows.join("\n")}
2573
3721
  const mergedEntity = {
2574
3722
  name: "",
2575
3723
  type: "other",
3724
+ created: "",
2576
3725
  updated: "",
3726
+ extraFrontmatterLines: [],
3727
+ preSectionLines: [],
2577
3728
  facts: [],
2578
3729
  summary: void 0,
3730
+ synthesis: void 0,
3731
+ synthesisUpdatedAt: void 0,
3732
+ synthesisTimelineCount: void 0,
3733
+ synthesisStructuredFactCount: void 0,
3734
+ synthesisStructuredFactDigest: void 0,
3735
+ synthesisVersion: void 0,
3736
+ timeline: [],
2579
3737
  relationships: [],
2580
3738
  activity: [],
2581
- aliases: []
3739
+ aliases: [],
3740
+ structuredSections: [],
3741
+ extraSections: []
2582
3742
  };
2583
3743
  for (const file of files) {
2584
3744
  const filePath = path.join(this.entitiesDir, file);
2585
3745
  try {
2586
3746
  const content = await readFile(filePath, "utf-8");
2587
- const parsed = parseEntityFile(content);
3747
+ const parsed = parseEntityFile(content, this.entitySchemas);
2588
3748
  if (!mergedEntity.type || mergedEntity.type === "other") {
2589
3749
  mergedEntity.type = parsed.type;
2590
3750
  }
2591
- if (!mergedEntity.updated || parsed.updated > mergedEntity.updated) {
3751
+ if (!mergedEntity.updated || compareEntityTimestamps(parsed.updated, mergedEntity.updated) > 0) {
2592
3752
  mergedEntity.updated = parsed.updated;
2593
3753
  }
3754
+ const parsedCreated = parsed.created || parsed.updated;
3755
+ const mergedCreated = mergedEntity.created?.trim() || "";
3756
+ const parsedCreatedMs = parsedCreated ? Date.parse(parsedCreated) : Number.NaN;
3757
+ const mergedCreatedMs = mergedCreated ? Date.parse(mergedCreated) : Number.NaN;
3758
+ const parsedCreatedIsValid = Number.isFinite(parsedCreatedMs);
3759
+ const mergedCreatedIsValid = Number.isFinite(mergedCreatedMs);
3760
+ if (parsedCreated && (!mergedCreated || parsedCreatedIsValid && !mergedCreatedIsValid || parsedCreatedIsValid && mergedCreatedIsValid && parsedCreatedMs < mergedCreatedMs || !parsedCreatedIsValid && !mergedCreatedIsValid && compareEntityTimestamps(parsedCreated, mergedCreated) < 0)) {
3761
+ mergedEntity.created = parsedCreated;
3762
+ }
2594
3763
  if (parsed.name.length > mergedEntity.name.length) {
2595
3764
  mergedEntity.name = parsed.name;
2596
3765
  }
2597
- if (!mergedEntity.summary && parsed.summary) {
3766
+ const parsedSynthesisUpdatedAt = parsed.synthesisUpdatedAt?.trim() || void 0;
3767
+ const mergedSynthesisUpdatedAt = mergedEntity.synthesisUpdatedAt?.trim() || void 0;
3768
+ if (parsed.synthesis && (!mergedEntity.synthesis || !mergedSynthesisUpdatedAt && Boolean(parsedSynthesisUpdatedAt) || Boolean(mergedSynthesisUpdatedAt) && Boolean(parsedSynthesisUpdatedAt) && compareEntityTimestamps(parsedSynthesisUpdatedAt, mergedSynthesisUpdatedAt) > 0)) {
3769
+ mergedEntity.synthesis = parsed.synthesis;
3770
+ mergedEntity.summary = parsed.synthesis;
3771
+ mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
3772
+ mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
3773
+ mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
3774
+ mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
3775
+ mergedEntity.synthesisVersion = parsed.synthesisVersion;
3776
+ } else if (!mergedEntity.summary && parsed.summary) {
2598
3777
  mergedEntity.summary = parsed.summary;
3778
+ mergedEntity.synthesis = parsed.summary;
3779
+ mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
3780
+ mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
3781
+ mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
3782
+ mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
3783
+ mergedEntity.synthesisVersion = parsed.synthesisVersion;
2599
3784
  }
2600
- mergedEntity.facts.push(...parsed.facts);
3785
+ mergedEntity.timeline.push(...parsed.timeline);
2601
3786
  mergedEntity.relationships.push(...parsed.relationships);
2602
3787
  mergedEntity.activity.push(...parsed.activity);
2603
3788
  mergedEntity.aliases.push(...parsed.aliases);
3789
+ const mergedStructuredSectionMap = new Map(
3790
+ (mergedEntity.structuredSections ?? []).map((section) => [section.key, {
3791
+ ...section,
3792
+ facts: [...section.facts]
3793
+ }])
3794
+ );
3795
+ for (const section of parsed.structuredSections ?? []) {
3796
+ const existingSection = mergedStructuredSectionMap.get(section.key);
3797
+ if (!existingSection) {
3798
+ mergedStructuredSectionMap.set(section.key, {
3799
+ key: section.key,
3800
+ title: section.title,
3801
+ facts: [...new Set(section.facts.map((fact) => fact.trim()).filter((fact) => fact.length > 0))]
3802
+ });
3803
+ continue;
3804
+ }
3805
+ const mergedFacts = new Set(existingSection.facts.map((fact) => fact.trim()));
3806
+ for (const fact of section.facts) {
3807
+ const trimmed = fact.trim();
3808
+ if (!trimmed) continue;
3809
+ mergedFacts.add(trimmed);
3810
+ }
3811
+ existingSection.facts = Array.from(mergedFacts);
3812
+ if (!existingSection.title.trim() && section.title.trim()) {
3813
+ existingSection.title = section.title;
3814
+ }
3815
+ }
3816
+ mergedEntity.structuredSections = Array.from(mergedStructuredSectionMap.values());
3817
+ mergedEntity.extraFrontmatterLines.push(...parsed.extraFrontmatterLines ?? []);
3818
+ mergedEntity.preSectionLines.push(...parsed.preSectionLines ?? []);
3819
+ mergedEntity.extraSections.push(...(parsed.extraSections ?? []).map((section) => ({
3820
+ title: section.title,
3821
+ lines: [...section.lines]
3822
+ })));
2604
3823
  } catch {
2605
3824
  }
2606
3825
  }
2607
- mergedEntity.facts = [...new Set(mergedEntity.facts)];
3826
+ const timelineKeys = /* @__PURE__ */ new Set();
3827
+ mergedEntity.timeline = mergedEntity.timeline.filter((entry) => {
3828
+ const key = JSON.stringify([
3829
+ entry.timestamp,
3830
+ entry.source ?? "",
3831
+ entry.sessionKey ?? "",
3832
+ entry.principal ?? "",
3833
+ entry.text
3834
+ ]);
3835
+ if (timelineKeys.has(key)) return false;
3836
+ timelineKeys.add(key);
3837
+ return true;
3838
+ });
2608
3839
  const relKeys = /* @__PURE__ */ new Set();
2609
3840
  mergedEntity.relationships = mergedEntity.relationships.filter((r) => {
2610
3841
  const key = `${r.target}::${r.label}`;
@@ -2620,13 +3851,27 @@ ${rows.join("\n")}
2620
3851
  return true;
2621
3852
  }).sort((a, b) => b.date.localeCompare(a.date));
2622
3853
  mergedEntity.aliases = [...new Set(mergedEntity.aliases)];
3854
+ mergedEntity.structuredSections = sortStructuredSectionsBySchema(
3855
+ mergedEntity.type,
3856
+ mergedEntity.structuredSections ?? [],
3857
+ this.entitySchemas
3858
+ );
3859
+ mergedEntity.facts = compileEntityFacts(mergedEntity.timeline, mergedEntity.structuredSections);
3860
+ const extraSectionKeys = /* @__PURE__ */ new Set();
3861
+ mergedEntity.extraSections = (mergedEntity.extraSections ?? []).filter((section) => {
3862
+ const key = `${section.title}::${section.lines.join("\n")}`;
3863
+ if (extraSectionKeys.has(key)) return false;
3864
+ extraSectionKeys.add(key);
3865
+ return true;
3866
+ });
2623
3867
  if (!mergedEntity.name) {
2624
3868
  const dashIdx = canonical.indexOf("-");
2625
3869
  mergedEntity.name = dashIdx !== -1 ? canonical.slice(dashIdx + 1) : canonical;
2626
3870
  }
3871
+ mergedEntity.created = mergedEntity.created || mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
2627
3872
  mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
2628
3873
  const canonicalPath = path.join(this.entitiesDir, `${canonical}.md`);
2629
- await writeFile(canonicalPath, serializeEntityFile(mergedEntity), "utf-8");
3874
+ await writeFile(canonicalPath, serializeEntityFile(mergedEntity, this.entitySchemas), "utf-8");
2630
3875
  for (const file of files) {
2631
3876
  const filePath = path.join(this.entitiesDir, file);
2632
3877
  if (filePath !== canonicalPath) {
@@ -2711,6 +3956,29 @@ ${memory.content}
2711
3956
  const memories = await this.readAllMemories();
2712
3957
  return memories.find((m) => m.frontmatter.id === id) ?? null;
2713
3958
  }
3959
+ /**
3960
+ * Check which of the given memory IDs actually exist on disk.
3961
+ *
3962
+ * Uses a lightweight directory scan (collectActiveMemoryPaths) that reads
3963
+ * file names without parsing frontmatter — much cheaper than readAllMemories()
3964
+ * for simple existence checks like citation usage tracking.
3965
+ *
3966
+ * Returns the subset of `ids` that correspond to real memory files.
3967
+ */
3968
+ async filterExistingMemoryIds(ids) {
3969
+ if (ids.length === 0) return /* @__PURE__ */ new Set();
3970
+ const wantedIds = new Set(ids);
3971
+ const filePaths = await this.collectActiveMemoryPaths();
3972
+ const foundIds = /* @__PURE__ */ new Set();
3973
+ for (const filePath of filePaths) {
3974
+ const basename = path.basename(filePath, ".md");
3975
+ if (wantedIds.has(basename)) {
3976
+ foundIds.add(basename);
3977
+ if (foundIds.size === wantedIds.size) break;
3978
+ }
3979
+ }
3980
+ return foundIds;
3981
+ }
2714
3982
  async getProjectedMemoryState(id) {
2715
3983
  const projected = readProjectedMemoryState(this.baseDir, id);
2716
3984
  if (projected) return projected;
@@ -2802,6 +4070,9 @@ ${sanitized.text}
2802
4070
  let filePath;
2803
4071
  if (category === "correction") {
2804
4072
  filePath = path.join(this.correctionsDir, `${id}.md`);
4073
+ } else if (category === "procedure") {
4074
+ await mkdir(path.join(this.proceduresDir, today), { recursive: true });
4075
+ filePath = path.join(this.proceduresDir, today, `${id}.md`);
2805
4076
  } else {
2806
4077
  filePath = path.join(this.factsDir, today, `${id}.md`);
2807
4078
  }
@@ -3038,8 +4309,12 @@ ${memory.content}
3038
4309
  export {
3039
4310
  normalizeEntityName,
3040
4311
  ContentHashIndex,
4312
+ normalizeAttributePairs,
4313
+ compareEntityTimestamps,
4314
+ fingerprintEntityStructuredFacts,
4315
+ isEntitySynthesisStale,
3041
4316
  parseEntityFile,
3042
4317
  serializeEntityFile,
3043
4318
  StorageManager
3044
4319
  };
3045
- //# sourceMappingURL=chunk-QWUUMMIK.js.map
4320
+ //# sourceMappingURL=chunk-POMSFKTB.js.map