@remnic/core 9.66.1 → 9.66.2

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 (310) hide show
  1. package/dist/access-admin-ops-surface.d.ts +1 -1
  2. package/dist/access-admin-ops-surface.js +26 -26
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-authorization-probe.d.ts +1 -1
  5. package/dist/access-authorization-probe.js +27 -27
  6. package/dist/access-boundary.d.ts +1 -1
  7. package/dist/access-boundary.js +26 -26
  8. package/dist/access-cli.js +90 -88
  9. package/dist/access-cli.js.map +1 -1
  10. package/dist/access-extraction-force-flush.d.ts +1 -1
  11. package/dist/access-extraction-force-flush.js +26 -26
  12. package/dist/access-http-lcm-compaction.d.ts +1 -1
  13. package/dist/access-http-lifecycle-flush.d.ts +1 -1
  14. package/dist/access-http-offline-stream.d.ts +1 -1
  15. package/dist/access-http-query.js +28 -28
  16. package/dist/access-http.d.ts +2 -2
  17. package/dist/access-http.js +41 -41
  18. package/dist/access-identity-continuity-surface.js +26 -26
  19. package/dist/access-lcm-surface.d.ts +1 -1
  20. package/dist/access-lcm-surface.js +26 -26
  21. package/dist/access-mcp.d.ts +1 -1
  22. package/dist/access-mcp.js +33 -33
  23. package/dist/access-namespace-preflight.js +26 -26
  24. package/dist/access-observe-write-surface.d.ts +1 -1
  25. package/dist/access-observe-write-surface.js +26 -26
  26. package/dist/access-operations-batch.js +28 -28
  27. package/dist/access-operations.d.ts +6 -6
  28. package/dist/access-operations.js +32 -32
  29. package/dist/access-recall-concurrency.d.ts +1 -1
  30. package/dist/access-recall-concurrency.js +26 -26
  31. package/dist/access-recall-response.d.ts +1 -1
  32. package/dist/access-recall-response.js +26 -26
  33. package/dist/access-recall-surface.d.ts +1 -1
  34. package/dist/access-recall-surface.js +26 -26
  35. package/dist/access-schema.d.ts +89 -89
  36. package/dist/access-service-helpers.d.ts +1 -1
  37. package/dist/access-service.d.ts +1 -1
  38. package/dist/access-service.js +26 -26
  39. package/dist/access-surface-catalog.d.ts +1 -1
  40. package/dist/access-surface-catalog.js +28 -28
  41. package/dist/active-recall.js +3 -3
  42. package/dist/adapters/index.js +4 -4
  43. package/dist/adapters/registry.js +2 -2
  44. package/dist/briefing.js +10 -10
  45. package/dist/buffer.js +4 -4
  46. package/dist/causal-behavior.js +2 -2
  47. package/dist/causal-chain.js +2 -2
  48. package/dist/causal-consolidation.js +13 -13
  49. package/dist/causal-retrieval.js +2 -2
  50. package/dist/causal-trajectory-graph.js +2 -2
  51. package/dist/causal-trajectory.js +1 -1
  52. package/dist/{chunk-LYUUA6QB.js → chunk-25OLNWYG.js} +2 -2
  53. package/dist/{chunk-2X6BGKQL.js → chunk-2QSGJTDG.js} +10 -10
  54. package/dist/{chunk-ZZZWEDF4.js → chunk-2VZHB5US.js} +2 -2
  55. package/dist/{chunk-JKTV3JNX.js → chunk-36HJ36FK.js} +3 -3
  56. package/dist/{chunk-XKNHFDTT.js → chunk-3A2Z4OOS.js} +2 -2
  57. package/dist/{chunk-TCF4MU7L.js → chunk-47WWTCJY.js} +1001 -101
  58. package/dist/chunk-47WWTCJY.js.map +1 -0
  59. package/dist/{chunk-RDGKVH6T.js → chunk-4NKIGSJM.js} +2 -2
  60. package/dist/{chunk-QBNE55VA.js → chunk-4PG2CKI6.js} +2 -2
  61. package/dist/{chunk-S4CDUXQY.js → chunk-6545JDIM.js} +2 -2
  62. package/dist/{chunk-2FA7U5XY.js → chunk-65HXVEVG.js} +2 -2
  63. package/dist/{chunk-U6NGKS32.js → chunk-6BYJQ3L2.js} +8 -8
  64. package/dist/{chunk-OMX6TKXH.js → chunk-6PUF7LUO.js} +2 -2
  65. package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
  66. package/dist/{chunk-VG4SSPAW.js → chunk-AI5M4FVQ.js} +2 -2
  67. package/dist/{chunk-MUEGIKKY.js → chunk-B4B3QDV3.js} +21 -20
  68. package/dist/chunk-B4B3QDV3.js.map +1 -0
  69. package/dist/{chunk-YMOPYG3K.js → chunk-BIJAWZYW.js} +6 -6
  70. package/dist/{chunk-M5ERTI4I.js → chunk-BLHKR4GJ.js} +2 -2
  71. package/dist/chunk-BLUYQFRH.js +36 -0
  72. package/dist/chunk-BLUYQFRH.js.map +1 -0
  73. package/dist/{chunk-CEH2AFXG.js → chunk-C24UZSFW.js} +6 -6
  74. package/dist/{chunk-EH65UV2Z.js → chunk-C3ZYZKDJ.js} +2 -2
  75. package/dist/{chunk-NFP4KPTA.js → chunk-CDLPF3PX.js} +2 -2
  76. package/dist/{chunk-FFUMTH27.js → chunk-CQ6IWZDI.js} +2 -2
  77. package/dist/{chunk-KWIGDKM2.js → chunk-D2YNNDML.js} +2 -2
  78. package/dist/{chunk-65JOGLIS.js → chunk-D3DUA64S.js} +2 -2
  79. package/dist/{chunk-2ITZ74A6.js → chunk-DSEHPUID.js} +11 -11
  80. package/dist/chunk-E4X2YX2F.js +22 -0
  81. package/dist/chunk-E4X2YX2F.js.map +1 -0
  82. package/dist/{chunk-AF5H7TKI.js → chunk-EH7BNIUN.js} +2 -2
  83. package/dist/{chunk-IGZCAHFL.js → chunk-EQRD4NMH.js} +2 -2
  84. package/dist/{chunk-CX4OLNIH.js → chunk-ER2UPUMG.js} +2 -2
  85. package/dist/{chunk-SZMYGDSM.js → chunk-F2JXJABQ.js} +2 -2
  86. package/dist/{chunk-CHGG74CY.js → chunk-F4W5HTED.js} +2 -2
  87. package/dist/{chunk-H2IRSXZN.js → chunk-FFWONAAW.js} +3 -3
  88. package/dist/chunk-FYDW33ZB.js +13 -0
  89. package/dist/chunk-FYDW33ZB.js.map +1 -0
  90. package/dist/{chunk-TNGH4ZYI.js → chunk-GNWLTCKC.js} +2 -2
  91. package/dist/{chunk-OJUEUQDJ.js → chunk-JJ6ZEFXC.js} +2 -2
  92. package/dist/{chunk-U65VJSCJ.js → chunk-JMXHVJ2V.js} +10 -10
  93. package/dist/{chunk-NACLNNFQ.js → chunk-KSZKQTZS.js} +2 -2
  94. package/dist/{chunk-TCQRIIIN.js → chunk-LSR5LQXN.js} +2 -2
  95. package/dist/chunk-MAG6M3B6.js +51 -0
  96. package/dist/chunk-MAG6M3B6.js.map +1 -0
  97. package/dist/{chunk-B4AOWRYF.js → chunk-NTFXBILO.js} +4 -4
  98. package/dist/{chunk-2PSX5BJU.js → chunk-NZPFCP5A.js} +2 -2
  99. package/dist/{chunk-C5IUOBCN.js → chunk-OERZXNSK.js} +2 -2
  100. package/dist/{chunk-ZPS724LT.js → chunk-OIDICRH7.js} +2 -2
  101. package/dist/{chunk-LTBKKSCX.js → chunk-OLBZ2WGG.js} +2 -2
  102. package/dist/{chunk-PD66CVSD.js → chunk-OUKBMQ2O.js} +2 -2
  103. package/dist/{chunk-GPFZFCRN.js → chunk-PP7R754B.js} +1 -1
  104. package/dist/{chunk-GAJK3DQ5.js → chunk-PRRUK2ZH.js} +2 -2
  105. package/dist/{chunk-TBCDF556.js → chunk-PW3GKWH3.js} +3 -3
  106. package/dist/chunk-Q3SHEAXR.js +80 -0
  107. package/dist/chunk-Q3SHEAXR.js.map +1 -0
  108. package/dist/{chunk-KI64TTBL.js → chunk-R2Q6FTOX.js} +2 -2
  109. package/dist/{chunk-KJXCD7VR.js → chunk-RCPKBW7K.js} +4 -4
  110. package/dist/{chunk-INUBU6I5.js → chunk-RH3XLQG4.js} +2 -2
  111. package/dist/{chunk-JBPKEARU.js → chunk-RKNJBZ55.js} +4 -4
  112. package/dist/{chunk-NSTNGMSS.js → chunk-U4HZEW2B.js} +5 -5
  113. package/dist/{chunk-XOHYGIWF.js → chunk-UHQE37NI.js} +2 -2
  114. package/dist/{chunk-D3UXGE5D.js → chunk-UMZUKOJU.js} +7 -7
  115. package/dist/{chunk-S4Z2OBTX.js → chunk-UVYL5G7A.js} +3 -3
  116. package/dist/{chunk-4XLTUR6R.js → chunk-VKDBDJIX.js} +3 -3
  117. package/dist/{chunk-UW4RYPWC.js → chunk-VSMSYDIH.js} +2 -2
  118. package/dist/{chunk-LJABGXC4.js → chunk-W5IYHQA5.js} +3 -3
  119. package/dist/{chunk-7UOXCCCI.js → chunk-WKWY7YF3.js} +2 -2
  120. package/dist/{chunk-YWPLOJ7T.js → chunk-Y2YM4C75.js} +2 -2
  121. package/dist/{chunk-NUF7VSEX.js → chunk-YZ5VC2JJ.js} +2 -2
  122. package/dist/{chunk-EWKVYCFP.js → chunk-ZBIEX4XN.js} +1 -1
  123. package/dist/{chunk-GBEOUCBB.js → chunk-ZMVW2HJJ.js} +2 -2
  124. package/dist/{chunk-RPIS5FQ7.js → chunk-ZXROBS53.js} +44 -44
  125. package/dist/{cli-CsfFMe6n.d.ts → cli-BOuEb4Ia.d.ts} +1 -1
  126. package/dist/cli.d.ts +2 -2
  127. package/dist/cli.js +61 -61
  128. package/dist/coding/export-okf-codegraph.js +10 -10
  129. package/dist/coding/pre-action-gate.js +2 -2
  130. package/dist/compounding/engine.js +10 -10
  131. package/dist/config.js +3 -3
  132. package/dist/connectors/codex-materialize-runner.js +10 -10
  133. package/dist/connectors/index.js +10 -10
  134. package/dist/convergence-refresh.js +12 -12
  135. package/dist/corpus-watermark.js +12 -12
  136. package/dist/entity-retrieval.js +10 -10
  137. package/dist/external-wiki-access.d.ts +1 -1
  138. package/dist/external-wiki-access.js +27 -27
  139. package/dist/external-wiki-mcp-tools.d.ts +1 -1
  140. package/dist/extraction-span-wire.d.ts +17 -0
  141. package/dist/extraction-span-wire.js +13 -0
  142. package/dist/extraction-span-wire.js.map +1 -0
  143. package/dist/extraction-span.d.ts +17 -0
  144. package/dist/extraction-span.js +8 -0
  145. package/dist/extraction.js +13 -13
  146. package/dist/{graph-edge-decay-A4SDFMMS.js → graph-edge-decay-FXGELBFA.js} +4 -4
  147. package/dist/graph-edge-decay-FXGELBFA.js.map +1 -0
  148. package/dist/graph-snapshot.js +3 -3
  149. package/dist/graph.js +3 -3
  150. package/dist/harmonic-retrieval.js +1 -1
  151. package/dist/index.d.ts +887 -681
  152. package/dist/index.js +161 -108
  153. package/dist/lcm/engine.js +3 -3
  154. package/dist/lcm/index.js +11 -11
  155. package/dist/location/index.js +12 -12
  156. package/dist/maintenance/memory-governance.js +10 -10
  157. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  158. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  159. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  160. package/dist/memory-subject-cli.d.ts +1 -1
  161. package/dist/memory-subject.d.ts +1 -1
  162. package/dist/namespaces/migrate.js +11 -11
  163. package/dist/namespaces/search.js +6 -6
  164. package/dist/namespaces/storage.js +10 -10
  165. package/dist/operator-doctor-corpus.js +13 -13
  166. package/dist/operator-toolkit.js +23 -23
  167. package/dist/orchestration/maintenance.js +18 -18
  168. package/dist/orchestrator.js +90 -88
  169. package/dist/{public-http-Cwvl4QZr.d.ts → public-http-CmQRjQCD.d.ts} +1 -1
  170. package/dist/qmd.js +4 -4
  171. package/dist/recall-deep-access.d.ts +30 -0
  172. package/dist/recall-deep-access.js +50 -0
  173. package/dist/recall-deep-access.js.map +1 -0
  174. package/dist/recall-deep-render.d.ts +11 -0
  175. package/dist/recall-deep-render.js +28 -0
  176. package/dist/recall-deep-render.js.map +1 -0
  177. package/dist/recall-deep.js +6 -73
  178. package/dist/recall-deep.js.map +1 -1
  179. package/dist/recall-navigate.js +6 -42
  180. package/dist/recall-navigate.js.map +1 -1
  181. package/dist/recall-state-view-render.d.ts +18 -0
  182. package/dist/recall-state-view-render.js +22 -0
  183. package/dist/recall-state-view-render.js.map +1 -0
  184. package/dist/recall-state-view-widen.d.ts +13 -0
  185. package/dist/recall-state-view-widen.js +11 -0
  186. package/dist/recall-state-view-widen.js.map +1 -0
  187. package/dist/recall-state-view-wire.d.ts +3 -4
  188. package/dist/recall-state-view-wire.js +2 -1
  189. package/dist/resume-bundles.js +4 -4
  190. package/dist/schemas.d.ts +176 -176
  191. package/dist/search/factory.js +5 -5
  192. package/dist/search/index.js +9 -9
  193. package/dist/semantic-consolidation.js +11 -11
  194. package/dist/semantic-rule-promotion.js +10 -10
  195. package/dist/semantic-rule-verifier.js +10 -10
  196. package/dist/shared-context/manager.d.ts +6 -6
  197. package/dist/source-agent-qualifier.js +12 -12
  198. package/dist/storage.js +9 -9
  199. package/dist/support-passport/index.d.ts +4 -4
  200. package/dist/support-passport/index.js +29 -29
  201. package/dist/transfer/export-okf.js +11 -11
  202. package/dist/transfer/types.d.ts +78 -78
  203. package/dist/verified-recall.js +10 -10
  204. package/dist/who-knows-cli.d.ts +1 -1
  205. package/dist/who-knows-cli.js +11 -11
  206. package/dist/who-knows.d.ts +1 -1
  207. package/dist/who-knows.js +10 -10
  208. package/package.json +2 -2
  209. package/src/activity/index.ts +3 -0
  210. package/src/activity/journal-vault-read.test.ts +153 -0
  211. package/src/activity/journal-vault-read.ts +214 -0
  212. package/src/activity/journal.ts +1 -1
  213. package/src/activity/timeline/analysis-batch.test.ts +56 -0
  214. package/src/activity/timeline/analysis-batch.ts +45 -0
  215. package/src/activity/timeline/analysis.test.ts +413 -0
  216. package/src/activity/timeline/analysis.ts +362 -0
  217. package/src/activity/timeline/index.ts +5 -0
  218. package/src/activity/timeline/journal-recap-persist.test.ts +148 -0
  219. package/src/activity/timeline/journal-recap-persist.ts +37 -0
  220. package/src/activity/timeline/weekly-persist.test.ts +121 -0
  221. package/src/activity/timeline/weekly-persist.ts +103 -0
  222. package/src/activity/vault-path.test.ts +38 -0
  223. package/src/activity/vault-path.ts +59 -0
  224. package/src/activity/vault-publish.test.ts +102 -0
  225. package/src/activity/vault-publish.ts +92 -13
  226. package/src/dedup/merge-on-write-wire.test.ts +101 -0
  227. package/src/dedup/merge-on-write-wire.ts +34 -0
  228. package/src/extraction-span-wire.test.ts +20 -0
  229. package/src/extraction-span-wire.ts +17 -0
  230. package/src/extraction-span.test.ts +59 -0
  231. package/src/extraction-span.ts +39 -0
  232. package/src/index.ts +1 -0
  233. package/src/recall-deep-access.test.ts +58 -0
  234. package/src/recall-deep-access.ts +71 -0
  235. package/src/recall-deep-render.test.ts +68 -0
  236. package/src/recall-deep-render.ts +32 -0
  237. package/src/recall-state-view-render.test.ts +94 -0
  238. package/src/recall-state-view-render.ts +24 -0
  239. package/src/recall-state-view-widen.test.ts +66 -0
  240. package/src/recall-state-view-widen.ts +49 -0
  241. package/src/recall-state-view-wire.ts +6 -16
  242. package/src/shared-context/staleness.test.ts +36 -0
  243. package/src/shared-context/staleness.ts +38 -0
  244. package/src/shared-context/trajectories.test.ts +77 -0
  245. package/src/shared-context/trajectories.ts +49 -0
  246. package/dist/chunk-MUEGIKKY.js.map +0 -1
  247. package/dist/chunk-TCF4MU7L.js.map +0 -1
  248. package/dist/chunk-WFCWP4XU.js +0 -17
  249. package/dist/chunk-WFCWP4XU.js.map +0 -1
  250. package/dist/{access-service-C2tV2-uL.d.ts → access-service-Dz6I9AZ-.d.ts} +64 -64
  251. /package/dist/{chunk-LYUUA6QB.js.map → chunk-25OLNWYG.js.map} +0 -0
  252. /package/dist/{chunk-2X6BGKQL.js.map → chunk-2QSGJTDG.js.map} +0 -0
  253. /package/dist/{chunk-ZZZWEDF4.js.map → chunk-2VZHB5US.js.map} +0 -0
  254. /package/dist/{chunk-JKTV3JNX.js.map → chunk-36HJ36FK.js.map} +0 -0
  255. /package/dist/{chunk-XKNHFDTT.js.map → chunk-3A2Z4OOS.js.map} +0 -0
  256. /package/dist/{chunk-RDGKVH6T.js.map → chunk-4NKIGSJM.js.map} +0 -0
  257. /package/dist/{chunk-QBNE55VA.js.map → chunk-4PG2CKI6.js.map} +0 -0
  258. /package/dist/{chunk-S4CDUXQY.js.map → chunk-6545JDIM.js.map} +0 -0
  259. /package/dist/{chunk-2FA7U5XY.js.map → chunk-65HXVEVG.js.map} +0 -0
  260. /package/dist/{chunk-U6NGKS32.js.map → chunk-6BYJQ3L2.js.map} +0 -0
  261. /package/dist/{chunk-OMX6TKXH.js.map → chunk-6PUF7LUO.js.map} +0 -0
  262. /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
  263. /package/dist/{chunk-VG4SSPAW.js.map → chunk-AI5M4FVQ.js.map} +0 -0
  264. /package/dist/{chunk-YMOPYG3K.js.map → chunk-BIJAWZYW.js.map} +0 -0
  265. /package/dist/{chunk-M5ERTI4I.js.map → chunk-BLHKR4GJ.js.map} +0 -0
  266. /package/dist/{chunk-CEH2AFXG.js.map → chunk-C24UZSFW.js.map} +0 -0
  267. /package/dist/{chunk-EH65UV2Z.js.map → chunk-C3ZYZKDJ.js.map} +0 -0
  268. /package/dist/{chunk-NFP4KPTA.js.map → chunk-CDLPF3PX.js.map} +0 -0
  269. /package/dist/{chunk-FFUMTH27.js.map → chunk-CQ6IWZDI.js.map} +0 -0
  270. /package/dist/{chunk-KWIGDKM2.js.map → chunk-D2YNNDML.js.map} +0 -0
  271. /package/dist/{chunk-65JOGLIS.js.map → chunk-D3DUA64S.js.map} +0 -0
  272. /package/dist/{chunk-2ITZ74A6.js.map → chunk-DSEHPUID.js.map} +0 -0
  273. /package/dist/{chunk-AF5H7TKI.js.map → chunk-EH7BNIUN.js.map} +0 -0
  274. /package/dist/{chunk-IGZCAHFL.js.map → chunk-EQRD4NMH.js.map} +0 -0
  275. /package/dist/{chunk-CX4OLNIH.js.map → chunk-ER2UPUMG.js.map} +0 -0
  276. /package/dist/{chunk-SZMYGDSM.js.map → chunk-F2JXJABQ.js.map} +0 -0
  277. /package/dist/{chunk-CHGG74CY.js.map → chunk-F4W5HTED.js.map} +0 -0
  278. /package/dist/{chunk-H2IRSXZN.js.map → chunk-FFWONAAW.js.map} +0 -0
  279. /package/dist/{chunk-TNGH4ZYI.js.map → chunk-GNWLTCKC.js.map} +0 -0
  280. /package/dist/{chunk-OJUEUQDJ.js.map → chunk-JJ6ZEFXC.js.map} +0 -0
  281. /package/dist/{chunk-U65VJSCJ.js.map → chunk-JMXHVJ2V.js.map} +0 -0
  282. /package/dist/{chunk-NACLNNFQ.js.map → chunk-KSZKQTZS.js.map} +0 -0
  283. /package/dist/{chunk-TCQRIIIN.js.map → chunk-LSR5LQXN.js.map} +0 -0
  284. /package/dist/{chunk-B4AOWRYF.js.map → chunk-NTFXBILO.js.map} +0 -0
  285. /package/dist/{chunk-2PSX5BJU.js.map → chunk-NZPFCP5A.js.map} +0 -0
  286. /package/dist/{chunk-C5IUOBCN.js.map → chunk-OERZXNSK.js.map} +0 -0
  287. /package/dist/{chunk-ZPS724LT.js.map → chunk-OIDICRH7.js.map} +0 -0
  288. /package/dist/{chunk-LTBKKSCX.js.map → chunk-OLBZ2WGG.js.map} +0 -0
  289. /package/dist/{chunk-PD66CVSD.js.map → chunk-OUKBMQ2O.js.map} +0 -0
  290. /package/dist/{chunk-GPFZFCRN.js.map → chunk-PP7R754B.js.map} +0 -0
  291. /package/dist/{chunk-GAJK3DQ5.js.map → chunk-PRRUK2ZH.js.map} +0 -0
  292. /package/dist/{chunk-TBCDF556.js.map → chunk-PW3GKWH3.js.map} +0 -0
  293. /package/dist/{chunk-KI64TTBL.js.map → chunk-R2Q6FTOX.js.map} +0 -0
  294. /package/dist/{chunk-KJXCD7VR.js.map → chunk-RCPKBW7K.js.map} +0 -0
  295. /package/dist/{chunk-INUBU6I5.js.map → chunk-RH3XLQG4.js.map} +0 -0
  296. /package/dist/{chunk-JBPKEARU.js.map → chunk-RKNJBZ55.js.map} +0 -0
  297. /package/dist/{chunk-NSTNGMSS.js.map → chunk-U4HZEW2B.js.map} +0 -0
  298. /package/dist/{chunk-XOHYGIWF.js.map → chunk-UHQE37NI.js.map} +0 -0
  299. /package/dist/{chunk-D3UXGE5D.js.map → chunk-UMZUKOJU.js.map} +0 -0
  300. /package/dist/{chunk-S4Z2OBTX.js.map → chunk-UVYL5G7A.js.map} +0 -0
  301. /package/dist/{chunk-4XLTUR6R.js.map → chunk-VKDBDJIX.js.map} +0 -0
  302. /package/dist/{chunk-UW4RYPWC.js.map → chunk-VSMSYDIH.js.map} +0 -0
  303. /package/dist/{chunk-LJABGXC4.js.map → chunk-W5IYHQA5.js.map} +0 -0
  304. /package/dist/{chunk-7UOXCCCI.js.map → chunk-WKWY7YF3.js.map} +0 -0
  305. /package/dist/{chunk-YWPLOJ7T.js.map → chunk-Y2YM4C75.js.map} +0 -0
  306. /package/dist/{chunk-NUF7VSEX.js.map → chunk-YZ5VC2JJ.js.map} +0 -0
  307. /package/dist/{chunk-EWKVYCFP.js.map → chunk-ZBIEX4XN.js.map} +0 -0
  308. /package/dist/{chunk-GBEOUCBB.js.map → chunk-ZMVW2HJJ.js.map} +0 -0
  309. /package/dist/{chunk-RPIS5FQ7.js.map → chunk-ZXROBS53.js.map} +0 -0
  310. /package/dist/{graph-edge-decay-A4SDFMMS.js.map → extraction-span.js.map} +0 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Versioned weekly time/activity snapshot store (issue #2052).
3
+ *
4
+ * Files live at `<memoryDir>/activity/weekly/<ns>/<week>--<identity>.json`,
5
+ * outside recall fallback dirs. Same inputs skip rewrite by content hash.
6
+ * A new sourceRevision or configHash is a new file.
7
+ */
8
+ import { randomBytes } from "node:crypto";
9
+ import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
10
+ import path from "node:path";
11
+
12
+ import { namespaceIdentityToken } from "../../namespaces/identity.js";
13
+ import { hashActivityBody, isValidActivityDate } from "../digest.js";
14
+ import type { WeeklyActivitySummary } from "./weekly.js";
15
+
16
+ export const WEEKLY_SNAPSHOT_FORMAT_VERSION = 1;
17
+ export const WEEKLY_SNAPSHOT_DIR = path.join("activity", "weekly");
18
+
19
+ export interface PersistWeeklySnapshotInput {
20
+ memoryDir: string;
21
+ namespace: string;
22
+ summary: WeeklyActivitySummary;
23
+ sourceRevision: string;
24
+ configHash: string;
25
+ }
26
+
27
+ export interface PersistWeeklySnapshotResult {
28
+ path: string;
29
+ written: boolean;
30
+ }
31
+
32
+ function requireToken(value: string, name: string): string {
33
+ if (typeof value !== "string" || value.trim().length === 0) {
34
+ throw new TypeError(`${name} must be a non-empty string`);
35
+ }
36
+ return value.trim();
37
+ }
38
+
39
+ export function weeklySnapshotPath(input: PersistWeeklySnapshotInput): string {
40
+ const memoryDir = requireToken(input.memoryDir, "memoryDir");
41
+ const namespace = requireToken(input.namespace, "namespace");
42
+ const sourceRevision = requireToken(input.sourceRevision, "sourceRevision");
43
+ const configHash = requireToken(input.configHash, "configHash");
44
+ const weekStartUtc = input.summary?.weekStartUtc;
45
+ if (typeof weekStartUtc !== "string") {
46
+ throw new TypeError("summary.weekStartUtc must be an ISO timestamp");
47
+ }
48
+ const weekDate = weekStartUtc.slice(0, 10);
49
+ if (!isValidActivityDate(weekDate)) {
50
+ throw new RangeError("summary.weekStartUtc must be an ISO timestamp");
51
+ }
52
+ const identity = hashActivityBody(
53
+ `${input.summary.weekStartUtc}\0${input.summary.weekEndUtc}\0${sourceRevision}\0${configHash}`,
54
+ );
55
+ return path.join(
56
+ path.resolve(memoryDir),
57
+ WEEKLY_SNAPSHOT_DIR,
58
+ namespaceIdentityToken(namespace),
59
+ `${weekDate}--${identity}.json`,
60
+ );
61
+ }
62
+
63
+ /** Persist one weekly snapshot. Same bytes are a no-op. */
64
+ export function persistWeeklySnapshot(input: PersistWeeklySnapshotInput): PersistWeeklySnapshotResult {
65
+ const namespace = requireToken(input.namespace, "namespace");
66
+ const sourceRevision = requireToken(input.sourceRevision, "sourceRevision");
67
+ const configHash = requireToken(input.configHash, "configHash");
68
+ const filePath = weeklySnapshotPath(input);
69
+ const payload = {
70
+ formatVersion: WEEKLY_SNAPSHOT_FORMAT_VERSION,
71
+ namespace,
72
+ sourceRevision,
73
+ configHash,
74
+ summary: input.summary,
75
+ };
76
+ const contentHash = hashActivityBody(JSON.stringify(payload));
77
+ const text = `${JSON.stringify({ ...payload, contentHash })}\n`;
78
+
79
+ try {
80
+ if (readFileSync(filePath, "utf8") === text) {
81
+ return { path: filePath, written: false };
82
+ }
83
+ } catch (error) {
84
+ if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) {
85
+ throw error;
86
+ }
87
+ }
88
+
89
+ mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
90
+ const tmpPath = `${filePath}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
91
+ writeFileSync(tmpPath, text, { encoding: "utf8", mode: 0o600 });
92
+ try {
93
+ renameSync(tmpPath, filePath);
94
+ } catch (error) {
95
+ try {
96
+ unlinkSync(tmpPath);
97
+ } catch {
98
+ // Best-effort cleanup of the temp file.
99
+ }
100
+ throw error;
101
+ }
102
+ return { path: filePath, written: true };
103
+ }
@@ -0,0 +1,38 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { resolveVaultNotePath } from "./vault-path.js";
5
+
6
+ test("resolveVaultNotePath expands date tokens", () => {
7
+ assert.equal(resolveVaultNotePath("{yyyy}-{MM}-{dd}.md", "2026-08-18"), "2026-08-18.md");
8
+ assert.equal(
9
+ resolveVaultNotePath("Daily Notes/{yyyy}/{MM}/{yyyy}-{MM}-{dd}.md", "2026-08-18"),
10
+ "Daily Notes/2026/08/2026-08-18.md",
11
+ );
12
+ assert.equal(resolveVaultNotePath("{yy}/{M}/{d}.md", "2026-08-08"), "26/8/8.md");
13
+ assert.equal(
14
+ resolveVaultNotePath("{yyyy}-{MM}-{dd}.md", "2026-08-18", { timezone: "America/Chicago" }),
15
+ "2026-08-18.md",
16
+ );
17
+ });
18
+
19
+ test("resolveVaultNotePath expands ISO week {ww} as zero-padded", () => {
20
+ assert.equal(resolveVaultNotePath("W{ww}.md", "2021-01-01"), "W53.md");
21
+ assert.equal(resolveVaultNotePath("W{ww}.md", "2024-01-01"), "W01.md");
22
+ assert.equal(
23
+ resolveVaultNotePath("{yyyy}/W{ww}/{yyyy}-{MM}-{dd}.md", "2021-01-01"),
24
+ "2021/W53/2021-01-01.md",
25
+ );
26
+ });
27
+
28
+ test("resolveVaultNotePath rejects traversal and absolute templates", () => {
29
+ assert.throws(() => resolveVaultNotePath("../outside/{yyyy}.md", "2026-08-18"), /\.\.|absolute|traversal/i);
30
+ assert.throws(() => resolveVaultNotePath("{yyyy}/../../secret.md", "2026-08-18"), /\.\.|absolute|traversal/i);
31
+ assert.throws(() => resolveVaultNotePath("/abs/{yyyy}.md", "2026-08-18"), /\.\.|absolute|traversal/i);
32
+ assert.throws(() => resolveVaultNotePath("C:/vault/{yyyy}.md", "2026-08-18"), /\.\.|absolute|traversal/i);
33
+ });
34
+
35
+ test("resolveVaultNotePath requires a real YYYY-MM-DD date", () => {
36
+ assert.throws(() => resolveVaultNotePath("{yyyy}.md", "2026-02-30"), /YYYY-MM-DD/);
37
+ assert.throws(() => resolveVaultNotePath("{yyyy}.md", "20260818"), /YYYY-MM-DD/);
38
+ });
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Vault note path templates (issue #1985).
3
+ *
4
+ * Expands date tokens on a YYYY-MM-DD day. Returns a vault-relative path.
5
+ * Rejects `..` segments and absolute templates.
6
+ */
7
+ import path from "node:path";
8
+
9
+ import { assertValidTimezone, isValidActivityDate } from "./digest.js";
10
+
11
+ const TOKEN_RE = /\{yyyy\}|\{yy\}|\{MM\}|\{dd\}|\{M\}|\{d\}|\{ww\}/g;
12
+
13
+ export function resolveVaultNotePath(
14
+ template: string,
15
+ date: string,
16
+ options?: { timezone?: string },
17
+ ): string {
18
+ if (!isValidActivityDate(date)) {
19
+ throw new RangeError(`Invalid vault date "${date}"; expected YYYY-MM-DD.`);
20
+ }
21
+ if (options?.timezone !== undefined) {
22
+ assertValidTimezone(options.timezone);
23
+ }
24
+ if (typeof template !== "string" || template.length === 0) {
25
+ throw new RangeError("Vault note path template must be a non-empty relative path.");
26
+ }
27
+ if (path.posix.isAbsolute(template) || path.win32.isAbsolute(template)) {
28
+ throw new RangeError("Vault note path template must be relative; absolute templates are rejected.");
29
+ }
30
+
31
+ const [yyyy, mm, dd] = date.split("-");
32
+ const tokens: Record<string, string> = {
33
+ "{yyyy}": yyyy,
34
+ "{yy}": yyyy.slice(2),
35
+ "{MM}": mm,
36
+ "{dd}": dd,
37
+ "{M}": String(Number(mm)),
38
+ "{d}": String(Number(dd)),
39
+ "{ww}": isoWeekNumber(date),
40
+ };
41
+ const expanded = template.replace(TOKEN_RE, (token) => tokens[token] ?? token);
42
+ if (expanded.split(/[\\/]/).some((segment) => segment === "..")) {
43
+ throw new RangeError("Vault note path template must not contain `..` segments.");
44
+ }
45
+ if (path.posix.isAbsolute(expanded) || path.win32.isAbsolute(expanded)) {
46
+ throw new RangeError("Vault note path template must be relative; absolute templates are rejected.");
47
+ }
48
+ return expanded;
49
+ }
50
+
51
+ function isoWeekNumber(date: string): string {
52
+ const [year, month, day] = date.split("-").map(Number);
53
+ const utc = new Date(Date.UTC(year, month - 1, day));
54
+ const dayNum = utc.getUTCDay() || 7;
55
+ utc.setUTCDate(utc.getUTCDate() + 4 - dayNum);
56
+ const yearStart = new Date(Date.UTC(utc.getUTCFullYear(), 0, 1));
57
+ const weekNo = Math.ceil(((utc.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
58
+ return String(weekNo).padStart(2, "0");
59
+ }
@@ -164,3 +164,105 @@ test("publishVaultRegion never creates a missing file", () => {
164
164
  assert.equal(existsSync(path.join(vaultPath, relativeFile)), false);
165
165
  });
166
166
  });
167
+
168
+ test("applyManagedRegion heading replaces only the named section", () => {
169
+ const fileText = [
170
+ "---",
171
+ "title: daily",
172
+ "---",
173
+ "",
174
+ "scratchpad",
175
+ "## Timeline",
176
+ "old cards",
177
+ "## Notes",
178
+ "keep me",
179
+ "",
180
+ ].join("\n");
181
+ const result = applyManagedRegion(fileText, {
182
+ strategy: "heading",
183
+ name: "Timeline",
184
+ content: "new cards",
185
+ });
186
+ assert.equal(result.ok, true);
187
+ if (!result.ok) return;
188
+ assert.equal(
189
+ result.text,
190
+ [
191
+ "---",
192
+ "title: daily",
193
+ "---",
194
+ "",
195
+ "scratchpad",
196
+ "## Timeline",
197
+ "new cards",
198
+ "## Notes",
199
+ "keep me",
200
+ "",
201
+ ].join("\n"),
202
+ );
203
+
204
+ const crlf = fileText.replace(/\n/g, "\r\n");
205
+ const crlfResult = applyManagedRegion(crlf, {
206
+ strategy: "heading",
207
+ name: "Timeline",
208
+ content: "new cards\nline two",
209
+ });
210
+ assert.equal(crlfResult.ok, true);
211
+ if (!crlfResult.ok) return;
212
+ assert.equal(
213
+ crlfResult.text,
214
+ [
215
+ "---",
216
+ "title: daily",
217
+ "---",
218
+ "",
219
+ "scratchpad",
220
+ "## Timeline",
221
+ "new cards",
222
+ "line two",
223
+ "## Notes",
224
+ "keep me",
225
+ "",
226
+ ].join("\r\n"),
227
+ );
228
+
229
+ const prefix = "---\nkeep: me\n---\n\nscratchpad\n## Timeline";
230
+ const suffix = "\n## Notes\nkeep me\n";
231
+ const outside = `${prefix}\nold\n${suffix}`;
232
+ const headingAt = outside.indexOf("## Timeline");
233
+ const notesAt = outside.indexOf("\n## Notes");
234
+ const headingLineEnd = headingAt + "## Timeline".length;
235
+ const before = Buffer.from(outside.slice(0, headingLineEnd), "utf8");
236
+ const after = Buffer.from(outside.slice(notesAt), "utf8");
237
+ const byteResult = applyManagedRegion(outside, {
238
+ strategy: "heading",
239
+ name: "Timeline",
240
+ content: "replacement",
241
+ });
242
+ assert.equal(byteResult.ok, true);
243
+ if (!byteResult.ok) return;
244
+ const nextHeading = byteResult.text.indexOf("## Timeline");
245
+ const nextNotes = byteResult.text.indexOf("\n## Notes");
246
+ assert.deepEqual(Buffer.from(byteResult.text.slice(0, nextHeading + "## Timeline".length), "utf8"), before);
247
+ assert.deepEqual(Buffer.from(byteResult.text.slice(nextNotes), "utf8"), after);
248
+ });
249
+
250
+ test("applyManagedRegion heading is a no-op when the heading is missing", () => {
251
+ const fileText = "# Daily\n\nno timeline here\n";
252
+ const result = applyManagedRegion(fileText, {
253
+ strategy: "heading",
254
+ name: "Timeline",
255
+ content: "new cards",
256
+ });
257
+ assert.deepEqual(result, { ok: false, reason: "no_heading", text: fileText });
258
+ });
259
+
260
+ test("applyManagedRegion heading refuses duplicate headings and lists line numbers", () => {
261
+ const fileText = ["# Daily", "", "## Timeline", "first", "", "## Timeline", "second", ""].join("\n");
262
+ const result = applyManagedRegion(fileText, {
263
+ strategy: "heading",
264
+ name: "Timeline",
265
+ content: "new cards",
266
+ });
267
+ assert.deepEqual(result, { ok: false, reason: "duplicate_heading", lines: [3, 6], text: fileText });
268
+ });
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Managed-region vault publisher (issue #1985 first slice).
2
+ * Managed-region vault publisher (issue #1985).
3
3
  *
4
- * Replaces only the bytes between `<!-- remnic:<name>:start -->` and
5
- * `<!-- remnic:<name>:end -->`. Missing markers leave the file unchanged.
6
- * `activity.timeline.vault.enabled` stays default-false until a later parse
7
- * slice; this module does not read config.
4
+ * Markers replace bytes between `<!-- remnic:<name>:start -->` and
5
+ * `<!-- remnic:<name>:end -->`. Heading replaces the body after a unique
6
+ * ATX heading until the next same-or-higher heading. The heading line stays.
7
+ * This module does not read config.
8
8
  */
9
9
  import { createHash, randomBytes } from "node:crypto";
10
10
  import { readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
@@ -14,7 +14,8 @@ import { expandTildePath } from "../utils/path.js";
14
14
 
15
15
  export type ApplyManagedRegionResult =
16
16
  | { ok: true; text: string }
17
- | { ok: false; reason: "no_marker"; text: string };
17
+ | { ok: false; reason: "no_marker" | "no_heading"; text: string }
18
+ | { ok: false; reason: "duplicate_heading"; lines: readonly number[]; text: string };
18
19
 
19
20
  export type PublishVaultRegionResult =
20
21
  | { ok: true; status: "updated" | "unchanged" }
@@ -22,8 +23,10 @@ export type PublishVaultRegionResult =
22
23
 
23
24
  export function applyManagedRegion(
24
25
  fileText: string,
25
- opts: { strategy: "markers"; name: string; content: string },
26
+ opts: { strategy: "markers" | "heading"; name: string; content: string },
26
27
  ): ApplyManagedRegionResult {
28
+ if (opts.strategy === "heading") return applyHeadingRegion(fileText, opts.name, opts.content);
29
+
27
30
  const startMarker = `<!-- remnic:${opts.name}:start -->`;
28
31
  const endMarker = `<!-- remnic:${opts.name}:end -->`;
29
32
  const startIdx = fileText.indexOf(startMarker);
@@ -31,17 +34,93 @@ export function applyManagedRegion(
31
34
  const endIdx = fileText.indexOf(endMarker, startIdx + startMarker.length);
32
35
  if (endIdx === -1) return { ok: false, reason: "no_marker", text: fileText };
33
36
 
34
- const eol = fileText.includes("\r\n") ? "\r\n" : "\n";
35
- let body = opts.content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
36
- if (eol === "\r\n") body = body.replace(/\n/g, "\r\n");
37
- if (body.endsWith(eol)) body = body.slice(0, -eol.length);
38
-
37
+ const { body, eol } = ownedBody(fileText, opts.content);
39
38
  return {
40
39
  ok: true,
41
40
  text: `${fileText.slice(0, startIdx + startMarker.length)}${eol}${body}${eol}${fileText.slice(endIdx)}`,
42
41
  };
43
42
  }
44
43
 
44
+ function applyHeadingRegion(fileText: string, name: string, content: string): ApplyManagedRegionResult {
45
+ const wanted = name.trim();
46
+ const hits: Array<{ line: number; level: number; next: number }> = [];
47
+ for (const row of fileLines(fileText)) {
48
+ const heading = parseAtxHeading(row.line);
49
+ if (heading && heading.text === wanted) {
50
+ hits.push({ line: row.number, level: heading.level, next: row.next });
51
+ }
52
+ }
53
+ if (hits.length === 0) return { ok: false, reason: "no_heading", text: fileText };
54
+ if (hits.length > 1) {
55
+ return { ok: false, reason: "duplicate_heading", lines: hits.map((hit) => hit.line), text: fileText };
56
+ }
57
+
58
+ const hit = hits[0]!;
59
+ let sectionEnd = fileText.length;
60
+ for (const row of fileLines(fileText)) {
61
+ if (row.start < hit.next) continue;
62
+ const heading = parseAtxHeading(row.line);
63
+ if (heading && heading.level <= hit.level) {
64
+ sectionEnd = row.start;
65
+ break;
66
+ }
67
+ }
68
+
69
+ const { body, eol } = ownedBody(fileText, content);
70
+ const prefix =
71
+ hit.next === fileText.length && !fileText.endsWith("\n") && !fileText.endsWith("\r")
72
+ ? `${fileText}${eol}`
73
+ : fileText.slice(0, hit.next);
74
+ return { ok: true, text: `${prefix}${body}${eol}${fileText.slice(sectionEnd)}` };
75
+ }
76
+
77
+ function ownedBody(fileText: string, content: string): { body: string; eol: string } {
78
+ const eol = fileText.includes("\r\n") ? "\r\n" : "\n";
79
+ let body = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
80
+ if (eol === "\r\n") body = body.replace(/\n/g, "\r\n");
81
+ if (body.endsWith(eol)) body = body.slice(0, -eol.length);
82
+ return { body, eol };
83
+ }
84
+
85
+ function fileLines(fileText: string): Array<{ line: string; start: number; next: number; number: number }> {
86
+ const rows: Array<{ line: string; start: number; next: number; number: number }> = [];
87
+ let start = 0;
88
+ let number = 1;
89
+ while (start < fileText.length) {
90
+ let i = start;
91
+ while (i < fileText.length && fileText[i] !== "\n" && fileText[i] !== "\r") i++;
92
+ let next = i;
93
+ if (fileText[i] === "\r" && fileText[i + 1] === "\n") next = i + 2;
94
+ else if (fileText[i] === "\n" || fileText[i] === "\r") next = i + 1;
95
+ else next = fileText.length;
96
+ rows.push({ line: fileText.slice(start, i), start, next, number });
97
+ number += 1;
98
+ start = next;
99
+ }
100
+ return rows;
101
+ }
102
+
103
+ function parseAtxHeading(line: string): { level: number; text: string } | null {
104
+ let level = 0;
105
+ while (level < line.length && level < 6 && line[level] === "#") level++;
106
+ if (level === 0) return null;
107
+ const after = line[level];
108
+ if (after !== " " && after !== "\t") return null;
109
+
110
+ let start = level + 1;
111
+ let end = line.length;
112
+ while (start < end && (line[start] === " " || line[start] === "\t")) start++;
113
+ while (end > start && (line[end - 1] === " " || line[end - 1] === "\t")) end--;
114
+
115
+ let hashEnd = end;
116
+ while (hashEnd > start && line[hashEnd - 1] === "#") hashEnd--;
117
+ if (hashEnd < end && hashEnd > start && (line[hashEnd - 1] === " " || line[hashEnd - 1] === "\t")) {
118
+ end = hashEnd;
119
+ while (end > start && (line[end - 1] === " " || line[end - 1] === "\t")) end--;
120
+ }
121
+ return { level, text: line.slice(start, end) };
122
+ }
123
+
45
124
  export function publishVaultRegion(input: {
46
125
  vaultPath: string;
47
126
  relativeFile: string;
@@ -79,7 +158,7 @@ export function publishVaultRegion(input: {
79
158
  name: input.name,
80
159
  content: input.content,
81
160
  });
82
- if (!applied.ok) return { ok: false, reason: applied.reason };
161
+ if (!applied.ok) return { ok: false, reason: "no_marker" };
83
162
 
84
163
  const prevHash = createHash("sha256").update(existing, "utf8").digest("hex");
85
164
  const nextHash = createHash("sha256").update(applied.text, "utf8").digest("hex");
@@ -0,0 +1,101 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import type { MergeJudge, MergeJudgeVerdict, MergeOnWritePair } from "./merge-on-write.js";
4
+ import { applyMergeOnWriteAtPersist } from "./merge-on-write-wire.js";
5
+
6
+ type PersistResult = { action: "merged"; id: string } | { action: "created" };
7
+
8
+ function pair(overrides: Partial<MergeOnWritePair> = {}): MergeOnWritePair {
9
+ return {
10
+ category: "fact",
11
+ score: 0.85,
12
+ incomingContent: "User prefers dark mode on laptops.",
13
+ existingContent: "User likes dark mode.",
14
+ existingId: "mem-1",
15
+ ...overrides,
16
+ };
17
+ }
18
+
19
+ function trackers() {
20
+ const merged: string[] = [];
21
+ const created: number[] = [];
22
+ return {
23
+ merged,
24
+ created,
25
+ writeMerged: async (id: string): Promise<PersistResult> => {
26
+ merged.push(id);
27
+ return { action: "merged", id };
28
+ },
29
+ writeNew: async (): Promise<PersistResult> => {
30
+ created.push(1);
31
+ return { action: "created" };
32
+ },
33
+ };
34
+ }
35
+
36
+ const mergeJudge: MergeJudge = async (): Promise<MergeJudgeVerdict> => "merge";
37
+ const createJudge: MergeJudge = async (): Promise<MergeJudgeVerdict> => "create";
38
+
39
+ test("merge-on-write-wire: disabled writes a new fact", async () => {
40
+ let judged = 0;
41
+ const io = trackers();
42
+ const result = await applyMergeOnWriteAtPersist({
43
+ enabled: false,
44
+ pair: pair(),
45
+ judge: async (): Promise<MergeJudgeVerdict> => {
46
+ judged += 1;
47
+ return "merge";
48
+ },
49
+ writeMerged: io.writeMerged,
50
+ writeNew: io.writeNew,
51
+ });
52
+ assert.equal(result.action, "created");
53
+ assert.deepEqual(io.merged, []);
54
+ assert.equal(io.created.length, 1);
55
+ assert.equal(judged, 0);
56
+ });
57
+
58
+ test("merge-on-write-wire: mergeMin 0 writes a new fact", async () => {
59
+ let judged = 0;
60
+ const io = trackers();
61
+ const result = await applyMergeOnWriteAtPersist({
62
+ enabled: true,
63
+ mergeMin: 0,
64
+ pair: pair(),
65
+ judge: mergeJudge,
66
+ writeMerged: io.writeMerged,
67
+ writeNew: io.writeNew,
68
+ });
69
+ assert.equal(result.action, "created");
70
+ assert.deepEqual(io.merged, []);
71
+ assert.equal(io.created.length, 1);
72
+ assert.equal(judged, 0);
73
+ });
74
+
75
+ test("merge-on-write-wire: merge updates the existing id", async () => {
76
+ const io = trackers();
77
+ const result = await applyMergeOnWriteAtPersist({
78
+ enabled: true,
79
+ pair: pair(),
80
+ judge: mergeJudge,
81
+ writeMerged: io.writeMerged,
82
+ writeNew: io.writeNew,
83
+ });
84
+ assert.deepEqual(result, { action: "merged", id: "mem-1" });
85
+ assert.deepEqual(io.merged, ["mem-1"]);
86
+ assert.equal(io.created.length, 0);
87
+ });
88
+
89
+ test("merge-on-write-wire: create writes a new fact", async () => {
90
+ const io = trackers();
91
+ const result = await applyMergeOnWriteAtPersist({
92
+ enabled: true,
93
+ pair: pair(),
94
+ judge: createJudge,
95
+ writeMerged: io.writeMerged,
96
+ writeNew: io.writeNew,
97
+ });
98
+ assert.equal(result.action, "created");
99
+ assert.deepEqual(io.merged, []);
100
+ assert.equal(io.created.length, 1);
101
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Persist seam for merge-on-write (issue #2330 leftover slice).
3
+ *
4
+ * Default off. Failures and disabled gates create. Persist callers inject
5
+ * writeMerged / writeNew. This module does not touch extraction-persist.
6
+ */
7
+
8
+ import {
9
+ applyMergeOnWrite,
10
+ type MergeJudge,
11
+ type MergeOnWritePair,
12
+ } from "./merge-on-write.js";
13
+
14
+ export async function applyMergeOnWriteAtPersist<T>(opts: {
15
+ enabled?: boolean;
16
+ pair: MergeOnWritePair;
17
+ judge: MergeJudge;
18
+ mergeMin?: number;
19
+ skipThreshold?: number;
20
+ writeMerged: (id: string) => T | Promise<T>;
21
+ writeNew: () => T | Promise<T>;
22
+ }): Promise<T> {
23
+ const decision = await applyMergeOnWrite({
24
+ enabled: opts.enabled,
25
+ pair: opts.pair,
26
+ judge: opts.judge,
27
+ mergeMin: opts.mergeMin,
28
+ skipThreshold: opts.skipThreshold,
29
+ });
30
+ if (decision === "merge" && opts.pair.existingId) {
31
+ return opts.writeMerged(opts.pair.existingId);
32
+ }
33
+ return opts.writeNew();
34
+ }
@@ -0,0 +1,20 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { applySpanMode } from "./extraction-span-wire.js";
4
+
5
+ const TEXT = "Alice moved to Seattle";
6
+
7
+ test("applySpanMode: disabled or 0 is identity", () => {
8
+ const span = { text: TEXT, start: 6, end: 11 };
9
+ assert.deepEqual(applySpanMode({ enabled: false, ...span }), { text: TEXT });
10
+ assert.deepEqual(applySpanMode({ enabled: 0, ...span }), { text: TEXT });
11
+ assert.deepEqual(applySpanMode(span), { text: TEXT });
12
+ });
13
+
14
+ test("applySpanMode: enabled slices half-open [start, end)", () => {
15
+ assert.deepEqual(applySpanMode({ enabled: true, text: TEXT, start: 6, end: 11 }), {
16
+ text: "moved",
17
+ start: 6,
18
+ end: 11,
19
+ });
20
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Extraction seam for span mode (issue #2333 leftover slice).
3
+ *
4
+ * Default off. Callers pass `enabled`. This module does not touch
5
+ * extraction.ts; Phase A still gates production wiring.
6
+ */
7
+
8
+ import { parseSpanOffsets, type ParsedSpan } from "./extraction-span.js";
9
+
10
+ export function applySpanMode(opts: {
11
+ enabled?: boolean | 0 | 1;
12
+ text: string;
13
+ start: number;
14
+ end: number;
15
+ }): ParsedSpan {
16
+ return parseSpanOffsets(opts.text, { start: opts.start, end: opts.end }, opts.enabled);
17
+ }
@@ -0,0 +1,59 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { parseSpanOffsets } from "./extraction-span.js";
4
+
5
+ const TEXT = "Alice moved to Seattle";
6
+
7
+ test("parseSpanOffsets: disabled or 0 is identity", () => {
8
+ const offsets = { start: 6, end: 11 };
9
+ assert.deepEqual(parseSpanOffsets(TEXT, offsets, false), { text: TEXT });
10
+ assert.deepEqual(parseSpanOffsets(TEXT, offsets, 0), { text: TEXT });
11
+ assert.deepEqual(parseSpanOffsets(TEXT, offsets), { text: TEXT });
12
+ });
13
+
14
+ test("parseSpanOffsets: enabled slices half-open [start, end)", () => {
15
+ assert.deepEqual(parseSpanOffsets(TEXT, { start: 6, end: 11 }, true), {
16
+ text: "moved",
17
+ start: 6,
18
+ end: 11,
19
+ });
20
+ });
21
+
22
+ test("parseSpanOffsets: boundary offsets stay inside the string", () => {
23
+ assert.deepEqual(parseSpanOffsets(TEXT, { start: 0, end: TEXT.length }, true), {
24
+ text: TEXT,
25
+ start: 0,
26
+ end: TEXT.length,
27
+ });
28
+ assert.deepEqual(parseSpanOffsets(TEXT, { start: 0, end: 1 }, true), {
29
+ text: "A",
30
+ start: 0,
31
+ end: 1,
32
+ });
33
+ assert.deepEqual(parseSpanOffsets(TEXT, { start: TEXT.length - 1, end: TEXT.length }, true), {
34
+ text: "e",
35
+ start: TEXT.length - 1,
36
+ end: TEXT.length,
37
+ });
38
+ });
39
+
40
+ test("parseSpanOffsets: reversed and out-of-range offsets reject", () => {
41
+ assert.throws(() => parseSpanOffsets(TEXT, { start: 12, end: 6 }, true), /reversed|out of range|invalid span/i);
42
+ assert.throws(() => parseSpanOffsets(TEXT, { start: -1, end: 6 }, true), /out of range|invalid span/i);
43
+ assert.throws(() => parseSpanOffsets(TEXT, { start: 0, end: TEXT.length + 1 }, true), /out of range|invalid span/i);
44
+ });
45
+
46
+ test("parseSpanOffsets: empty source and empty span", () => {
47
+ assert.deepEqual(parseSpanOffsets("", { start: 0, end: 0 }, false), { text: "" });
48
+ assert.deepEqual(parseSpanOffsets("", { start: 0, end: 0 }, true), {
49
+ text: "",
50
+ start: 0,
51
+ end: 0,
52
+ });
53
+ assert.deepEqual(parseSpanOffsets(TEXT, { start: 6, end: 6 }, true), {
54
+ text: "",
55
+ start: 6,
56
+ end: 6,
57
+ });
58
+ assert.throws(() => parseSpanOffsets("", { start: 0, end: 1 }, true), /out of range|invalid span/i);
59
+ });