@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,362 @@
1
+ /**
2
+ * Optional AI analysis over deterministic timeline cards (issue #2050 first slice).
3
+ *
4
+ * Injected provider only. parseConfig, registries, and surfaces wait.
5
+ * Failures leave cards unchanged. Disabled makes zero provider calls.
6
+ */
7
+ import { extractJsonCandidates } from "../../json-extract.js";
8
+ import { log } from "../../logger.js";
9
+ import { DEFAULT_TIMELINE_CATEGORIES } from "./categories.js";
10
+ import type { TimelineCard, TimelineCategory, TimelineCorrection, TimelineEvidenceRange, TimelineObservation } from "./types.js";
11
+
12
+ export const TIMELINE_ANALYSIS_PROMPT_VERSION = 1;
13
+ export const TIMELINE_ANALYSIS_BATCH_SIZE = 40;
14
+ export const TIMELINE_ANALYSIS_BATCH_OVERLAP = 2;
15
+ export const TIMELINE_ANALYSIS_DEFAULT_TIMEOUT_MS = 15_000;
16
+
17
+ export class TimelineAnalysisConfigError extends Error {
18
+ override readonly name = "TimelineAnalysisConfigError";
19
+ readonly code = "invalid_config" as const;
20
+ constructor(message: string) {
21
+ super(message);
22
+ }
23
+ }
24
+
25
+ export type TimelineAnalysisStatus = "ok" | "disabled" | "provider_failed" | "invalid_output";
26
+
27
+ export interface TimelineAnalysisCompleteInput {
28
+ prompt: string;
29
+ provider: string;
30
+ model: string;
31
+ signal: AbortSignal;
32
+ }
33
+
34
+ export type TimelineAnalysisComplete = (input: TimelineAnalysisCompleteInput) => Promise<string>;
35
+
36
+ export interface TimelineAnalysisInput {
37
+ enabled: boolean;
38
+ cards: readonly TimelineCard[];
39
+ observations: readonly TimelineObservation[];
40
+ provider?: string;
41
+ model?: string;
42
+ complete?: TimelineAnalysisComplete;
43
+ timeoutMs?: number;
44
+ date?: string;
45
+ timezone?: string;
46
+ categories?: readonly TimelineCategory[];
47
+ preferences?: readonly string[];
48
+ priorEdits?: readonly TimelineCorrection[];
49
+ signal?: AbortSignal;
50
+ }
51
+
52
+ export interface TimelineAnalysisResult {
53
+ status: TimelineAnalysisStatus;
54
+ cards: TimelineCard[];
55
+ provider?: string;
56
+ model?: string;
57
+ promptVersion?: number;
58
+ }
59
+
60
+ interface AnalysisOp {
61
+ cardId: string;
62
+ title?: string;
63
+ summary?: string;
64
+ categoryId?: string;
65
+ confidence?: number;
66
+ uncertainty?: string;
67
+ evidenceRange: TimelineEvidenceRange;
68
+ }
69
+
70
+ const INSTRUCTIONS = [
71
+ "Use only supplied evidence.",
72
+ "Preserve chronology.",
73
+ "Do not invent people, places, or tasks.",
74
+ "Avoid productivity or emotional claims.",
75
+ "Merge adjacent activity only when the evidence justifies it.",
76
+ "Return strict JSON: {\"ops\":[...]} with evidenceRange, title, summary, categoryId, confidence, uncertainty.",
77
+ "Cite evidence ranges for every op. Empty ops is a valid no-op.",
78
+ ].join(" ");
79
+
80
+ function fail(
81
+ status: Exclude<TimelineAnalysisStatus, "ok" | "disabled">,
82
+ cards: readonly TimelineCard[],
83
+ provider: string,
84
+ model: string,
85
+ ): TimelineAnalysisResult {
86
+ log.info(`timeline analysis status=${status} provider=${provider} model=${model} promptVersion=${TIMELINE_ANALYSIS_PROMPT_VERSION}`);
87
+ return {
88
+ status,
89
+ cards: cards as TimelineCard[],
90
+ provider,
91
+ model,
92
+ promptVersion: TIMELINE_ANALYSIS_PROMPT_VERSION,
93
+ };
94
+ }
95
+
96
+ function requiredToken(value: string | undefined, name: string): string {
97
+ if (typeof value !== "string" || value.trim().length === 0) {
98
+ throw new TimelineAnalysisConfigError(`${name} is required`);
99
+ }
100
+ return value.trim();
101
+ }
102
+
103
+ function evidenceKeyOf(observation: TimelineObservation): string {
104
+ const parsed = Date.parse(observation.capturedAtUtc);
105
+ const instant = Number.isFinite(parsed) ? new Date(parsed).toISOString() : observation.capturedAtUtc;
106
+ return `${observation.machine}|${instant}|${observation.contentHash}`;
107
+ }
108
+
109
+ function sortObservations(observations: readonly TimelineObservation[]): TimelineObservation[] {
110
+ return [...observations].sort((a, b) => {
111
+ const time = Date.parse(a.capturedAtUtc) - Date.parse(b.capturedAtUtc);
112
+ if (time !== 0) return time;
113
+ if (a.contentHash !== b.contentHash) return a.contentHash < b.contentHash ? -1 : 1;
114
+ return a.id - b.id;
115
+ });
116
+ }
117
+
118
+ function batchObservations(observations: readonly TimelineObservation[]): TimelineObservation[][] {
119
+ if (observations.length === 0) return [[]];
120
+ const size = TIMELINE_ANALYSIS_BATCH_SIZE;
121
+ const overlap = TIMELINE_ANALYSIS_BATCH_OVERLAP;
122
+ const step = size - overlap;
123
+ const batches: TimelineObservation[][] = [];
124
+ for (let start = 0; start < observations.length; start += step) {
125
+ batches.push(observations.slice(start, start + size));
126
+ if (start + size >= observations.length) break;
127
+ }
128
+ return batches;
129
+ }
130
+
131
+ function safeObservation(observation: TimelineObservation) {
132
+ return {
133
+ id: observation.id,
134
+ machine: observation.machine,
135
+ capturedAtUtc: observation.capturedAtUtc,
136
+ app: observation.app,
137
+ windowTitle: observation.windowTitle,
138
+ ...(observation.browserUrl ? { browserUrl: observation.browserUrl } : {}),
139
+ contentHash: observation.contentHash,
140
+ };
141
+ }
142
+
143
+ function safeCard(card: TimelineCard) {
144
+ return {
145
+ id: card.id,
146
+ kind: card.kind,
147
+ title: card.title,
148
+ summary: card.summary,
149
+ categoryId: card.categoryId,
150
+ confidence: card.confidence,
151
+ startUtc: card.startUtc,
152
+ endUtc: card.endUtc,
153
+ machine: card.machine,
154
+ evidenceIds: card.evidenceIds,
155
+ evidenceRange: card.evidenceRange,
156
+ ...(card.manualEdit ? { manualEdit: card.manualEdit } : {}),
157
+ };
158
+ }
159
+
160
+ export function buildTimelineAnalysisPrompt(input: {
161
+ date?: string;
162
+ timezone?: string;
163
+ cards: readonly TimelineCard[];
164
+ observations: readonly TimelineObservation[];
165
+ categories: readonly TimelineCategory[];
166
+ preferences?: readonly string[];
167
+ priorEdits?: readonly TimelineCorrection[];
168
+ }): string {
169
+ const payload = {
170
+ date: input.date ?? null,
171
+ timezone: input.timezone ?? null,
172
+ categories: input.categories.map((category) => ({ id: category.id, name: category.name })),
173
+ preferences: input.preferences ?? [],
174
+ priorEdits: input.priorEdits ?? [],
175
+ cards: input.cards.map(safeCard),
176
+ observations: input.observations.map(safeObservation),
177
+ };
178
+ return `${INSTRUCTIONS}\n${JSON.stringify(payload)}`;
179
+ }
180
+
181
+ async function callComplete(
182
+ complete: TimelineAnalysisComplete,
183
+ prompt: string,
184
+ provider: string,
185
+ model: string,
186
+ timeoutMs: number,
187
+ parent?: AbortSignal,
188
+ ): Promise<string> {
189
+ const { promise: timeoutPromise, reject } = Promise.withResolvers<string>();
190
+ const failOnce = (message: string, name: string) => {
191
+ reject(Object.assign(new Error(message), { name }));
192
+ };
193
+ const timer = setTimeout(() => failOnce("timeline analysis timed out", "TimeoutError"), timeoutMs);
194
+ if (parent?.aborted) {
195
+ clearTimeout(timer);
196
+ throw Object.assign(new Error("timeline analysis aborted"), { name: "AbortError" });
197
+ }
198
+ const onAbort = () => failOnce("timeline analysis aborted", "AbortError");
199
+ parent?.addEventListener("abort", onAbort, { once: true });
200
+ const signal = parent ? AbortSignal.any([parent, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs);
201
+ try {
202
+ return await Promise.race([complete({ prompt, provider, model, signal }), timeoutPromise]);
203
+ } finally {
204
+ clearTimeout(timer);
205
+ parent?.removeEventListener("abort", onAbort);
206
+ }
207
+ }
208
+
209
+ function parseOps(raw: string): AnalysisOp[] | null {
210
+ if (typeof raw !== "string" || raw.trim().length === 0) return null;
211
+ for (const candidate of extractJsonCandidates(raw)) {
212
+ try {
213
+ const parsed: unknown = JSON.parse(candidate);
214
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || !("ops" in parsed)) continue;
215
+ const ops = parsed.ops;
216
+ if (!Array.isArray(ops)) continue;
217
+ const out: AnalysisOp[] = [];
218
+ let valid = true;
219
+ for (const item of ops) {
220
+ const op = asOp(item);
221
+ if (!op) {
222
+ valid = false;
223
+ break;
224
+ }
225
+ out.push(op);
226
+ }
227
+ if (valid) return out;
228
+ } catch {
229
+ continue;
230
+ }
231
+ }
232
+ return null;
233
+ }
234
+
235
+ function asOp(value: unknown): AnalysisOp | null {
236
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
237
+ if (!("cardId" in value) || typeof value.cardId !== "string" || value.cardId.length === 0) return null;
238
+ if (!("evidenceRange" in value)) return null;
239
+ const range = value.evidenceRange;
240
+ if (!range || typeof range !== "object" || Array.isArray(range)) return null;
241
+ if (!("firstKey" in range) || typeof range.firstKey !== "string" || range.firstKey.length === 0) return null;
242
+ if (!("lastKey" in range) || typeof range.lastKey !== "string" || range.lastKey.length === 0) return null;
243
+ if ("title" in value && value.title !== undefined && typeof value.title !== "string") return null;
244
+ if ("summary" in value && value.summary !== undefined && typeof value.summary !== "string") return null;
245
+ if ("categoryId" in value && value.categoryId !== undefined && typeof value.categoryId !== "string") return null;
246
+ if ("uncertainty" in value && value.uncertainty !== undefined && typeof value.uncertainty !== "string") return null;
247
+ if (
248
+ "confidence" in value &&
249
+ value.confidence !== undefined &&
250
+ (typeof value.confidence !== "number" || value.confidence < 0 || value.confidence > 1)
251
+ ) {
252
+ return null;
253
+ }
254
+ return {
255
+ cardId: value.cardId,
256
+ title: "title" in value && typeof value.title === "string" ? value.title : undefined,
257
+ summary: "summary" in value && typeof value.summary === "string" ? value.summary : undefined,
258
+ categoryId: "categoryId" in value && typeof value.categoryId === "string" ? value.categoryId : undefined,
259
+ confidence: "confidence" in value && typeof value.confidence === "number" ? value.confidence : undefined,
260
+ uncertainty: "uncertainty" in value && typeof value.uncertainty === "string" ? value.uncertainty : undefined,
261
+ evidenceRange: { firstKey: range.firstKey, lastKey: range.lastKey },
262
+ };
263
+ }
264
+
265
+ function knownEvidenceKeys(cards: readonly TimelineCard[], observations: readonly TimelineObservation[]): Set<string> {
266
+ const keys = new Set<string>();
267
+ for (const observation of observations) keys.add(evidenceKeyOf(observation));
268
+ for (const card of cards) {
269
+ if (card.evidenceRange) {
270
+ keys.add(card.evidenceRange.firstKey);
271
+ keys.add(card.evidenceRange.lastKey);
272
+ }
273
+ }
274
+ return keys;
275
+ }
276
+
277
+ function applyOps(
278
+ cards: readonly TimelineCard[],
279
+ ops: readonly AnalysisOp[],
280
+ categories: readonly TimelineCategory[],
281
+ evidenceKeys: ReadonlySet<string>,
282
+ ): TimelineCard[] | null {
283
+ if (ops.length === 0) return cards as TimelineCard[];
284
+ const byId = new Map(cards.map((card) => [card.id, card]));
285
+ const allowedCategories = new Set(categories.map((category) => category.id));
286
+ const next = new Map<string, TimelineCard>();
287
+ for (const op of ops) {
288
+ const card = byId.get(op.cardId);
289
+ if (!card) return null;
290
+ if (!evidenceKeys.has(op.evidenceRange.firstKey) || !evidenceKeys.has(op.evidenceRange.lastKey)) return null;
291
+ if (op.categoryId !== undefined && !allowedCategories.has(op.categoryId)) return null;
292
+ if (card.manualEdit) continue;
293
+ next.set(card.id, {
294
+ ...card,
295
+ ...(op.title !== undefined ? { title: op.title } : {}),
296
+ ...(op.summary !== undefined ? { summary: op.summary } : {}),
297
+ ...(op.categoryId !== undefined ? { categoryId: op.categoryId } : {}),
298
+ ...(op.confidence !== undefined ? { confidence: op.confidence } : {}),
299
+ });
300
+ }
301
+ if (next.size === 0) return cards as TimelineCard[];
302
+ return cards.map((card) => next.get(card.id) ?? card);
303
+ }
304
+
305
+ /** Analyze one day of cards. Failures return the input cards unchanged. */
306
+ export async function analyzeTimelineCards(input: TimelineAnalysisInput): Promise<TimelineAnalysisResult> {
307
+ const cards = input.cards as TimelineCard[];
308
+ if (!input.enabled) {
309
+ return { status: "disabled", cards };
310
+ }
311
+
312
+ const provider = requiredToken(input.provider, "provider");
313
+ const model = requiredToken(input.model, "model");
314
+ const complete = input.complete;
315
+ if (typeof complete !== "function") {
316
+ throw new TimelineAnalysisConfigError("complete is required");
317
+ }
318
+ const timeoutMs = input.timeoutMs ?? TIMELINE_ANALYSIS_DEFAULT_TIMEOUT_MS;
319
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
320
+ throw new TimelineAnalysisConfigError("timeoutMs must be a positive integer");
321
+ }
322
+ if (input.cards.length === 0 && input.observations.length === 0) {
323
+ return fail("invalid_output", cards, provider, model);
324
+ }
325
+
326
+ const categories = input.categories ?? DEFAULT_TIMELINE_CATEGORIES;
327
+ const ordered = sortObservations(input.observations);
328
+ const batches = batchObservations(ordered);
329
+ const merged = new Map<string, AnalysisOp>();
330
+
331
+ try {
332
+ for (const batch of batches) {
333
+ const prompt = buildTimelineAnalysisPrompt({
334
+ date: input.date,
335
+ timezone: input.timezone,
336
+ cards: input.cards,
337
+ observations: batch,
338
+ categories,
339
+ preferences: input.preferences,
340
+ priorEdits: input.priorEdits,
341
+ });
342
+ const raw = await callComplete(complete, prompt, provider, model, timeoutMs, input.signal);
343
+ const ops = parseOps(raw);
344
+ if (!ops) return fail("invalid_output", cards, provider, model);
345
+ for (const op of ops) merged.set(op.cardId, op);
346
+ }
347
+ } catch {
348
+ return fail("provider_failed", cards, provider, model);
349
+ }
350
+
351
+ const applied = applyOps(cards, [...merged.values()], categories, knownEvidenceKeys(cards, ordered));
352
+ if (!applied) return fail("invalid_output", cards, provider, model);
353
+
354
+ log.info(`timeline analysis status=ok provider=${provider} model=${model} promptVersion=${TIMELINE_ANALYSIS_PROMPT_VERSION}`);
355
+ return {
356
+ status: "ok",
357
+ cards: applied,
358
+ provider,
359
+ model,
360
+ promptVersion: TIMELINE_ANALYSIS_PROMPT_VERSION,
361
+ };
362
+ }
@@ -6,3 +6,8 @@ export * from "./build.js";
6
6
  export * from "./corrections.js";
7
7
  export * from "./query.js";
8
8
  export * from "./journal-recap.js";
9
+ export * from "./journal-recap-persist.js";
10
+ export * from "./weekly.js";
11
+ export * from "./weekly-persist.js";
12
+ export * from "./analysis.js";
13
+ export * from "./analysis-batch.js";
@@ -0,0 +1,148 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { journalPath } from "../journal.js";
8
+ import { persistDeterministicJournal } from "./journal-recap-persist.js";
9
+ import { renderDeterministicJournal } from "./journal-recap.js";
10
+ import type { TimelineCard } from "./types.js";
11
+
12
+ const DATE = "2026-08-17";
13
+ const TZ = "UTC";
14
+
15
+ function card(
16
+ overrides: Partial<TimelineCard> & Pick<TimelineCard, "id" | "startUtc" | "endUtc">,
17
+ ): TimelineCard {
18
+ return {
19
+ kind: "activity",
20
+ title: "Untitled",
21
+ summary: "none",
22
+ categoryId: "development",
23
+ confidence: 1,
24
+ dayKey: DATE,
25
+ timezone: TZ,
26
+ machine: "ws-a",
27
+ evidenceIds: [],
28
+ evidenceRange: null,
29
+ ...overrides,
30
+ };
31
+ }
32
+
33
+ function activityCards(): TimelineCard[] {
34
+ return [
35
+ card({
36
+ id: "act-1",
37
+ title: "Terminal",
38
+ startUtc: "2026-08-17T14:00:00.000Z",
39
+ endUtc: "2026-08-17T15:30:00.000Z",
40
+ }),
41
+ ];
42
+ }
43
+
44
+ function otherCards(): TimelineCard[] {
45
+ return [
46
+ card({
47
+ id: "act-2",
48
+ title: "Browser",
49
+ startUtc: "2026-08-17T16:00:00.000Z",
50
+ endUtc: "2026-08-17T17:00:00.000Z",
51
+ }),
52
+ ];
53
+ }
54
+
55
+ function withMemoryDir(fn: (memoryDir: string) => void): void {
56
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-journal-recap-persist-"));
57
+ try {
58
+ fn(memoryDir);
59
+ } finally {
60
+ rmSync(memoryDir, { recursive: true, force: true });
61
+ }
62
+ }
63
+
64
+ test("persist is a hard no-op without force", () => {
65
+ withMemoryDir((memoryDir) => {
66
+ const first = persistDeterministicJournal({
67
+ memoryDir,
68
+ date: DATE,
69
+ cards: activityCards(),
70
+ timezone: TZ,
71
+ });
72
+ assert.equal(first.wrote, true);
73
+ assert.equal(first.path, journalPath(memoryDir, DATE));
74
+ const original = readFileSync(first.path);
75
+ assert.equal(
76
+ original.toString("utf8"),
77
+ renderDeterministicJournal(activityCards(), { date: DATE, timezone: TZ }),
78
+ );
79
+
80
+ const second = persistDeterministicJournal({
81
+ memoryDir,
82
+ date: DATE,
83
+ cards: otherCards(),
84
+ timezone: TZ,
85
+ });
86
+ assert.equal(second.wrote, false);
87
+ assert.equal(second.path, first.path);
88
+ assert.deepEqual(readFileSync(second.path), original);
89
+ });
90
+ });
91
+
92
+ test("persist with force overwrites the existing file", () => {
93
+ withMemoryDir((memoryDir) => {
94
+ persistDeterministicJournal({
95
+ memoryDir,
96
+ date: DATE,
97
+ cards: activityCards(),
98
+ timezone: TZ,
99
+ });
100
+ const original = readFileSync(journalPath(memoryDir, DATE), "utf8");
101
+ const forced = persistDeterministicJournal({
102
+ memoryDir,
103
+ date: DATE,
104
+ cards: otherCards(),
105
+ timezone: TZ,
106
+ force: true,
107
+ });
108
+ assert.equal(forced.wrote, true);
109
+ const next = readFileSync(forced.path, "utf8");
110
+ assert.notEqual(next, original);
111
+ assert.equal(next, renderDeterministicJournal(otherCards(), { date: DATE, timezone: TZ }));
112
+ });
113
+ });
114
+
115
+ test("empty day still writes a valid journal file", () => {
116
+ withMemoryDir((memoryDir) => {
117
+ const result = persistDeterministicJournal({
118
+ memoryDir,
119
+ date: DATE,
120
+ cards: [],
121
+ timezone: TZ,
122
+ });
123
+ assert.equal(result.wrote, true);
124
+ const relative = path.relative(memoryDir, result.path).replace(/\\/g, "/");
125
+ assert.equal(relative, "journal/2026-08-17.md");
126
+ assert.equal(
127
+ readFileSync(result.path, "utf8"),
128
+ renderDeterministicJournal([], { date: DATE, timezone: TZ }),
129
+ );
130
+ });
131
+ });
132
+
133
+ test("existing foreign file without force stays byte-identical", () => {
134
+ withMemoryDir((memoryDir) => {
135
+ const filePath = journalPath(memoryDir, DATE);
136
+ mkdirSync(path.dirname(filePath), { recursive: true });
137
+ writeFileSync(filePath, "user notes\n");
138
+ const original = readFileSync(filePath);
139
+ const result = persistDeterministicJournal({
140
+ memoryDir,
141
+ date: DATE,
142
+ cards: activityCards(),
143
+ timezone: TZ,
144
+ });
145
+ assert.equal(result.wrote, false);
146
+ assert.deepEqual(readFileSync(filePath), original);
147
+ });
148
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Persist a deterministic daily journal recap (issue #2051).
3
+ *
4
+ * Writes journal/<YYYY-MM-DD>.md through writeJournalFile. Existing files
5
+ * stay byte-identical unless force is set. Body is renderDeterministicJournal.
6
+ */
7
+ import { journalPath, writeJournalFile } from "../journal.js";
8
+ import { renderDeterministicJournal } from "./journal-recap.js";
9
+ import type { TimelineCard } from "./types.js";
10
+
11
+ export interface PersistDeterministicJournalInput {
12
+ memoryDir: string;
13
+ date: string;
14
+ cards: readonly TimelineCard[];
15
+ timezone: string;
16
+ force?: boolean;
17
+ }
18
+
19
+ export interface PersistDeterministicJournalResult {
20
+ path: string;
21
+ wrote: boolean;
22
+ }
23
+
24
+ export function persistDeterministicJournal(
25
+ input: PersistDeterministicJournalInput,
26
+ ): PersistDeterministicJournalResult {
27
+ const filePath = journalPath(input.memoryDir, input.date);
28
+ const wrote = writeJournalFile(
29
+ filePath,
30
+ renderDeterministicJournal(input.cards, {
31
+ date: input.date,
32
+ timezone: input.timezone,
33
+ }),
34
+ input.force === true,
35
+ );
36
+ return { path: filePath, wrote };
37
+ }
@@ -0,0 +1,121 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { RECALL_FALLBACK_DIRS } from "../../utils/category-dir.js";
8
+ import type { TimelineCategory } from "./types.js";
9
+ import { persistWeeklySnapshot } from "./weekly-persist.js";
10
+ import { buildWeeklyActivitySummary } from "./weekly.js";
11
+
12
+ const WEEK_START = "2026-07-13T00:00:00.000Z";
13
+ const WEEK_END = "2026-07-20T00:00:00.000Z";
14
+ const OTHER_WEEK_START = "2026-07-20T00:00:00.000Z";
15
+ const OTHER_WEEK_END = "2026-07-27T00:00:00.000Z";
16
+
17
+ const CATEGORIES: TimelineCategory[] = [
18
+ { id: "development", name: "Development", color: "#333333", description: "d", order: 1 },
19
+ ];
20
+
21
+ function summary(weekStartUtc = WEEK_START, weekEndUtc = WEEK_END) {
22
+ return buildWeeklyActivitySummary([], {
23
+ timezone: "UTC",
24
+ weekStartUtc,
25
+ weekEndUtc,
26
+ categories: CATEGORIES,
27
+ });
28
+ }
29
+
30
+ function withMemoryDir(fn: (memoryDir: string) => void): void {
31
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-weekly-persist-"));
32
+ try {
33
+ fn(memoryDir);
34
+ } finally {
35
+ rmSync(memoryDir, { recursive: true, force: true });
36
+ }
37
+ }
38
+
39
+ function assertOutsideRecallRoots(filePath: string, memoryDir: string): void {
40
+ const relative = path.relative(memoryDir, filePath);
41
+ assert.ok(relative.startsWith(`activity${path.sep}weekly${path.sep}`));
42
+ assert.ok(
43
+ !RECALL_FALLBACK_DIRS.some((dir) => relative === dir || relative.startsWith(`${dir}${path.sep}`)),
44
+ );
45
+ }
46
+
47
+ test("same inputs skip rewrite by content hash", () => {
48
+ withMemoryDir((memoryDir) => {
49
+ const input = {
50
+ memoryDir,
51
+ namespace: "team",
52
+ summary: summary(),
53
+ sourceRevision: "rev-1",
54
+ configHash: "cfg-1",
55
+ };
56
+ const first = persistWeeklySnapshot(input);
57
+ assert.equal(first.written, true);
58
+ assertOutsideRecallRoots(first.path, memoryDir);
59
+ const firstBytes = readFileSync(first.path, "utf8");
60
+ const firstStat = statSync(first.path);
61
+ const snapshot = JSON.parse(firstBytes) as { contentHash: string };
62
+ assert.equal(typeof snapshot.contentHash, "string");
63
+ assert.equal(snapshot.contentHash.length, 64);
64
+
65
+ const second = persistWeeklySnapshot(input);
66
+ assert.equal(second.written, false);
67
+ assert.equal(second.path, first.path);
68
+ assert.equal(readFileSync(first.path, "utf8"), firstBytes);
69
+ assert.equal(statSync(first.path).mtimeMs, firstStat.mtimeMs);
70
+ });
71
+ });
72
+
73
+ test("changed sourceRevision or configHash writes a new file and leaves other weeks", () => {
74
+ withMemoryDir((memoryDir) => {
75
+ const base = {
76
+ memoryDir,
77
+ namespace: "team",
78
+ summary: summary(),
79
+ sourceRevision: "rev-1",
80
+ configHash: "cfg-1",
81
+ };
82
+ const original = persistWeeklySnapshot(base);
83
+ const originalBytes = readFileSync(original.path, "utf8");
84
+
85
+ const revised = persistWeeklySnapshot({ ...base, sourceRevision: "rev-2" });
86
+ assert.notEqual(revised.path, original.path);
87
+ assert.equal(revised.written, true);
88
+ assert.equal(readFileSync(original.path, "utf8"), originalBytes);
89
+
90
+ const reconfigured = persistWeeklySnapshot({ ...base, configHash: "cfg-2" });
91
+ assert.notEqual(reconfigured.path, original.path);
92
+ assert.notEqual(reconfigured.path, revised.path);
93
+ assert.equal(readFileSync(original.path, "utf8"), originalBytes);
94
+
95
+ const otherWeek = persistWeeklySnapshot({
96
+ ...base,
97
+ summary: summary(OTHER_WEEK_START, OTHER_WEEK_END),
98
+ });
99
+ assert.notEqual(otherWeek.path, original.path);
100
+ assert.equal(readFileSync(original.path, "utf8"), originalBytes);
101
+ assert.equal(JSON.parse(readFileSync(otherWeek.path, "utf8")).summary.weekStartUtc, OTHER_WEEK_START);
102
+ });
103
+ });
104
+
105
+ test("snapshots are namespace scoped", () => {
106
+ withMemoryDir((memoryDir) => {
107
+ const shared = {
108
+ memoryDir,
109
+ summary: summary(),
110
+ sourceRevision: "rev-1",
111
+ configHash: "cfg-1",
112
+ };
113
+ const alpha = persistWeeklySnapshot({ ...shared, namespace: "alpha" });
114
+ const beta = persistWeeklySnapshot({ ...shared, namespace: "beta" });
115
+ assert.notEqual(alpha.path, beta.path);
116
+ assert.equal(JSON.parse(readFileSync(alpha.path, "utf8")).namespace, "alpha");
117
+ assert.equal(JSON.parse(readFileSync(beta.path, "utf8")).namespace, "beta");
118
+ assert.equal(readdirSync(path.dirname(alpha.path)).length, 1);
119
+ assert.equal(readdirSync(path.dirname(beta.path)).length, 1);
120
+ });
121
+ });