@remnic/core 9.66.2 → 9.66.4

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 (547) hide show
  1. package/dist/access-admin-ops-surface.js +31 -31
  2. package/dist/access-audit.js +2 -2
  3. package/dist/access-authorization-probe.js +32 -32
  4. package/dist/access-boundary.js +31 -31
  5. package/dist/access-cli.js +97 -97
  6. package/dist/access-extraction-force-flush.js +31 -31
  7. package/dist/access-http-query.js +33 -33
  8. package/dist/access-http.js +49 -49
  9. package/dist/access-identity-continuity-surface.js +31 -31
  10. package/dist/access-lcm-surface.js +31 -31
  11. package/dist/access-mcp.js +39 -39
  12. package/dist/access-namespace-preflight.js +31 -31
  13. package/dist/access-observe-write-surface.js +31 -31
  14. package/dist/access-offline-manifest.js +3 -3
  15. package/dist/access-operations-batch.js +34 -34
  16. package/dist/access-operations.js +38 -38
  17. package/dist/access-recall-concurrency.js +31 -31
  18. package/dist/access-recall-response.js +31 -31
  19. package/dist/access-recall-surface.js +31 -31
  20. package/dist/access-schema.js +5 -5
  21. package/dist/access-service.js +31 -31
  22. package/dist/access-surface-catalog.js +33 -33
  23. package/dist/active-recall.js +5 -5
  24. package/dist/adapters/index.js +7 -7
  25. package/dist/adapters/registry.js +3 -3
  26. package/dist/briefing.js +8 -8
  27. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-GWVG7LGC.js} +2 -2
  28. package/dist/causal-behavior.js +2 -2
  29. package/dist/causal-chain.js +2 -2
  30. package/dist/causal-consolidation.js +11 -11
  31. package/dist/causal-retrieval.js +2 -2
  32. package/dist/causal-trajectory-graph.js +2 -2
  33. package/dist/causal-trajectory.js +1 -1
  34. package/dist/{chunk-36HJ36FK.js → chunk-27JH2KKM.js} +3 -3
  35. package/dist/{chunk-NZPFCP5A.js → chunk-2PSX5BJU.js} +2 -2
  36. package/dist/{chunk-RCPKBW7K.js → chunk-322MSJYS.js} +5 -5
  37. package/dist/{chunk-C3ZYZKDJ.js → chunk-3E4REX7E.js} +2 -2
  38. package/dist/{chunk-Y2YM4C75.js → chunk-4BRBSON5.js} +2 -2
  39. package/dist/{chunk-B4B3QDV3.js → chunk-4EX26ZS3.js} +24 -24
  40. package/dist/{chunk-ER2UPUMG.js → chunk-562Q236T.js} +2 -2
  41. package/dist/{chunk-F2JXJABQ.js → chunk-5BIOXQGN.js} +2 -2
  42. package/dist/{chunk-DSEHPUID.js → chunk-5C3OQ253.js} +2 -2
  43. package/dist/{chunk-LSR5LQXN.js → chunk-5VLXQFOS.js} +2 -2
  44. package/dist/{chunk-2QSGJTDG.js → chunk-636UUVOZ.js} +10 -10
  45. package/dist/{chunk-AI5M4FVQ.js → chunk-6DTQSLER.js} +2 -2
  46. package/dist/{chunk-CDLPF3PX.js → chunk-6IL5FFHH.js} +2 -2
  47. package/dist/{chunk-VKDBDJIX.js → chunk-6L6V2OIX.js} +3 -3
  48. package/dist/{chunk-ZXROBS53.js → chunk-6NA527W2.js} +48 -48
  49. package/dist/{chunk-R2Q6FTOX.js → chunk-7IEFFOIC.js} +2 -2
  50. package/dist/{chunk-4PG2CKI6.js → chunk-AQHDMA4A.js} +2 -2
  51. package/dist/{chunk-RKNJBZ55.js → chunk-AU7Q3LSC.js} +7 -7
  52. package/dist/{chunk-6BYJQ3L2.js → chunk-BE4Y6GK6.js} +8 -8
  53. package/dist/{chunk-PW3GKWH3.js → chunk-BQVWC67C.js} +4 -4
  54. package/dist/{chunk-KQAFEZQX.js → chunk-BUKK5SWA.js} +2 -2
  55. package/dist/{chunk-UVYL5G7A.js → chunk-C3LUYZLX.js} +3 -3
  56. package/dist/{chunk-JMXHVJ2V.js → chunk-DFWO37KT.js} +14 -14
  57. package/dist/{chunk-WKWY7YF3.js → chunk-F5FHDDOV.js} +2 -2
  58. package/dist/{chunk-U4HZEW2B.js → chunk-FH4BB7F4.js} +7 -7
  59. package/dist/{chunk-UHQE37NI.js → chunk-G5TYMG7R.js} +2 -2
  60. package/dist/{chunk-PP7R754B.js → chunk-GPFZFCRN.js} +1 -1
  61. package/dist/{chunk-EH7BNIUN.js → chunk-HAF3PUMO.js} +3 -3
  62. package/dist/{chunk-J2R275FN.js → chunk-HQJM3BKH.js} +2 -2
  63. package/dist/{chunk-O7ERGZDA.js → chunk-I7NBTRLR.js} +2 -2
  64. package/dist/{chunk-6545JDIM.js → chunk-IHK5KTOC.js} +2 -2
  65. package/dist/{chunk-25OLNWYG.js → chunk-IP7KIW7W.js} +2 -2
  66. package/dist/{chunk-XJJYSX6F.js → chunk-JCUG7AXZ.js} +4 -4
  67. package/dist/{chunk-OERZXNSK.js → chunk-JGXMA6HL.js} +2 -2
  68. package/dist/{chunk-CQ6IWZDI.js → chunk-JVDAIASW.js} +2 -2
  69. package/dist/{chunk-F4W5HTED.js → chunk-K3CQQNMX.js} +2 -2
  70. package/dist/{chunk-D2YNNDML.js → chunk-KWIGDKM2.js} +2 -2
  71. package/dist/{chunk-227UFSEQ.js → chunk-LEPOP46W.js} +4 -4
  72. package/dist/{chunk-OLBZ2WGG.js → chunk-LTBKKSCX.js} +2 -2
  73. package/dist/{chunk-VSMSYDIH.js → chunk-MARZW7DB.js} +2 -2
  74. package/dist/chunk-MKD3OMPS.js +20 -0
  75. package/dist/chunk-MKD3OMPS.js.map +1 -0
  76. package/dist/{chunk-W5IYHQA5.js → chunk-MS2I7JVN.js} +3 -3
  77. package/dist/{chunk-BLHKR4GJ.js → chunk-NHACZAM3.js} +2 -2
  78. package/dist/{chunk-2VZHB5US.js → chunk-NTZ3452G.js} +2 -2
  79. package/dist/{chunk-65HXVEVG.js → chunk-OT3GGOFW.js} +2 -2
  80. package/dist/{chunk-2NLLXCJG.js → chunk-OWHERGF2.js} +2 -2
  81. package/dist/{chunk-OUKBMQ2O.js → chunk-PD66CVSD.js} +2 -2
  82. package/dist/{chunk-EQRD4NMH.js → chunk-PMMNP5GZ.js} +2 -2
  83. package/dist/{chunk-47WWTCJY.js → chunk-RNRDH4ZO.js} +308 -94
  84. package/dist/chunk-RNRDH4ZO.js.map +1 -0
  85. package/dist/{chunk-PRRUK2ZH.js → chunk-RNVXKTIA.js} +2 -2
  86. package/dist/{chunk-ZBIEX4XN.js → chunk-SACAOBDB.js} +1 -1
  87. package/dist/{chunk-JJ6ZEFXC.js → chunk-SNFWXG5G.js} +2 -2
  88. package/dist/{chunk-NTFXBILO.js → chunk-SY7ASLC6.js} +5 -5
  89. package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
  90. package/dist/{chunk-X7Y7WX73.js → chunk-UVYI6VIX.js} +1 -1
  91. package/dist/{chunk-YZ5VC2JJ.js → chunk-VVWPY5U7.js} +2 -2
  92. package/dist/{chunk-OIDICRH7.js → chunk-W7O7PH5E.js} +2 -2
  93. package/dist/{chunk-RH3XLQG4.js → chunk-WTRFM4SF.js} +2 -2
  94. package/dist/{chunk-6PUF7LUO.js → chunk-Y5U72X3D.js} +2 -2
  95. package/dist/{chunk-FFWONAAW.js → chunk-Y7LFWOX6.js} +3 -3
  96. package/dist/{chunk-D3DUA64S.js → chunk-YJDKSDIU.js} +2 -2
  97. package/dist/{chunk-BIJAWZYW.js → chunk-YMOPYG3K.js} +6 -6
  98. package/dist/{chunk-ZMVW2HJJ.js → chunk-YUYARGKM.js} +2 -2
  99. package/dist/{chunk-UMZUKOJU.js → chunk-Z3YEGSR4.js} +4 -4
  100. package/dist/{chunk-D6Q5EPOV.js → chunk-ZYKNDRUJ.js} +3 -3
  101. package/dist/cli.js +73 -73
  102. package/dist/coding/export-okf-codegraph.js +8 -8
  103. package/dist/coding/pre-action-gate.js +2 -2
  104. package/dist/compounding/engine.js +8 -8
  105. package/dist/config.js +5 -5
  106. package/dist/connectors/codex-materialize-runner.js +8 -8
  107. package/dist/connectors/index.js +8 -8
  108. package/dist/convergence-refresh.js +10 -10
  109. package/dist/conversation-index/backend.js +2 -2
  110. package/dist/corpus-watermark.js +10 -10
  111. package/dist/entity-retrieval.js +8 -8
  112. package/dist/external-wiki-access.js +32 -32
  113. package/dist/extraction-span-bench.d.ts +14 -0
  114. package/dist/extraction-span-bench.js +17 -0
  115. package/dist/extraction-span-bench.js.map +1 -0
  116. package/dist/extraction-span-clip.d.ts +22 -0
  117. package/dist/extraction-span-clip.js +19 -0
  118. package/dist/extraction-span-clip.js.map +1 -0
  119. package/dist/extraction-span-contains.d.ts +12 -0
  120. package/dist/extraction-span-contains.js +17 -0
  121. package/dist/extraction-span-contains.js.map +1 -0
  122. package/dist/extraction-span-gap.d.ts +14 -0
  123. package/dist/extraction-span-gap.js +18 -0
  124. package/dist/extraction-span-gap.js.map +1 -0
  125. package/dist/extraction-span-join.d.ts +23 -0
  126. package/dist/extraction-span-join.js +19 -0
  127. package/dist/extraction-span-join.js.map +1 -0
  128. package/dist/extraction-span-length.d.ts +11 -0
  129. package/dist/extraction-span-length.js +17 -0
  130. package/dist/extraction-span-length.js.map +1 -0
  131. package/dist/extraction-span-overlap.d.ts +14 -0
  132. package/dist/extraction-span-overlap.js +16 -0
  133. package/dist/extraction-span-overlap.js.map +1 -0
  134. package/dist/extraction-span-shift.d.ts +15 -0
  135. package/dist/extraction-span-shift.js +17 -0
  136. package/dist/extraction-span-shift.js.map +1 -0
  137. package/dist/extraction-span-slice.d.ts +12 -0
  138. package/dist/extraction-span-slice.js +17 -0
  139. package/dist/extraction-span-slice.js.map +1 -0
  140. package/dist/extraction-span-validate.d.ts +20 -0
  141. package/dist/extraction-span-validate.js +20 -0
  142. package/dist/extraction-span-validate.js.map +1 -0
  143. package/dist/extraction.js +8 -8
  144. package/dist/{graph-edge-decay-FXGELBFA.js → graph-edge-decay-A4SDFMMS.js} +4 -4
  145. package/dist/graph-snapshot.js +3 -3
  146. package/dist/graph.js +3 -3
  147. package/dist/harmonic-retrieval.js +1 -1
  148. package/dist/index.d.ts +181 -1
  149. package/dist/index.js +140 -112
  150. package/dist/lcm/engine.js +2 -2
  151. package/dist/lcm/index.js +7 -7
  152. package/dist/location/index.js +10 -10
  153. package/dist/maintenance/memory-governance.js +8 -8
  154. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +8 -8
  155. package/dist/maintenance/rebuild-memory-projection.js +9 -9
  156. package/dist/namespaces/migrate.js +9 -9
  157. package/dist/namespaces/search.js +6 -6
  158. package/dist/namespaces/storage.js +8 -8
  159. package/dist/offline-sync.js +2 -2
  160. package/dist/operator-doctor-corpus.js +11 -11
  161. package/dist/operator-toolkit.js +23 -23
  162. package/dist/orchestration/maintenance.js +16 -16
  163. package/dist/orchestrator.js +97 -97
  164. package/dist/recall-deep-budget-left.d.ts +8 -0
  165. package/dist/recall-deep-budget-left.js +15 -0
  166. package/dist/recall-deep-budget-left.js.map +1 -0
  167. package/dist/recall-deep-budget.d.ts +9 -0
  168. package/dist/recall-deep-budget.js +15 -0
  169. package/dist/recall-deep-budget.js.map +1 -0
  170. package/dist/recall-deep-expand.d.ts +12 -0
  171. package/dist/recall-deep-expand.js +17 -0
  172. package/dist/recall-deep-expand.js.map +1 -0
  173. package/dist/recall-deep-policy.d.ts +17 -0
  174. package/dist/recall-deep-policy.js +18 -0
  175. package/dist/recall-deep-policy.js.map +1 -0
  176. package/dist/recall-deep-query.d.ts +15 -0
  177. package/dist/recall-deep-query.js +12 -0
  178. package/dist/recall-deep-query.js.map +1 -0
  179. package/dist/recall-deep-refine.d.ts +12 -0
  180. package/dist/recall-deep-refine.js +17 -0
  181. package/dist/recall-deep-refine.js.map +1 -0
  182. package/dist/recall-deep-step-count.d.ts +8 -0
  183. package/dist/recall-deep-step-count.js +15 -0
  184. package/dist/recall-deep-step-count.js.map +1 -0
  185. package/dist/recall-deep-stop-reason.d.ts +17 -0
  186. package/dist/recall-deep-stop-reason.js +23 -0
  187. package/dist/recall-deep-stop-reason.js.map +1 -0
  188. package/dist/recall-deep-stop.d.ts +17 -0
  189. package/dist/recall-deep-stop.js +23 -0
  190. package/dist/recall-deep-stop.js.map +1 -0
  191. package/dist/recall-deep-working-set.d.ts +9 -0
  192. package/dist/recall-deep-working-set.js +14 -0
  193. package/dist/recall-deep-working-set.js.map +1 -0
  194. package/dist/recall-navigate-access.d.ts +39 -0
  195. package/dist/recall-navigate-access.js +31 -0
  196. package/dist/recall-navigate-access.js.map +1 -0
  197. package/dist/recall-navigate-action.d.ts +17 -0
  198. package/dist/recall-navigate-action.js +14 -0
  199. package/dist/recall-navigate-action.js.map +1 -0
  200. package/dist/recall-navigate-budget.d.ts +16 -0
  201. package/dist/recall-navigate-budget.js +18 -0
  202. package/dist/recall-navigate-budget.js.map +1 -0
  203. package/dist/recall-navigate-cost.d.ts +8 -0
  204. package/dist/recall-navigate-cost.js +15 -0
  205. package/dist/recall-navigate-cost.js.map +1 -0
  206. package/dist/recall-navigate-depth.d.ts +8 -0
  207. package/dist/recall-navigate-depth.js +15 -0
  208. package/dist/recall-navigate-depth.js.map +1 -0
  209. package/dist/recall-navigate-link.d.ts +17 -0
  210. package/dist/recall-navigate-link.js +20 -0
  211. package/dist/recall-navigate-link.js.map +1 -0
  212. package/dist/recall-navigate-max-children.d.ts +8 -0
  213. package/dist/recall-navigate-max-children.js +15 -0
  214. package/dist/recall-navigate-max-children.js.map +1 -0
  215. package/dist/recall-navigate-node.d.ts +15 -0
  216. package/dist/recall-navigate-node.js +13 -0
  217. package/dist/recall-navigate-node.js.map +1 -0
  218. package/dist/recall-navigate-path.d.ts +9 -0
  219. package/dist/recall-navigate-path.js +14 -0
  220. package/dist/recall-navigate-path.js.map +1 -0
  221. package/dist/recall-navigate-render.d.ts +15 -0
  222. package/dist/recall-navigate-render.js +23 -0
  223. package/dist/recall-navigate-render.js.map +1 -0
  224. package/dist/recall-state-view-current.d.ts +18 -0
  225. package/dist/recall-state-view-current.js +14 -0
  226. package/dist/recall-state-view-current.js.map +1 -0
  227. package/dist/recall-state-view-inject.d.ts +15 -0
  228. package/dist/recall-state-view-inject.js +16 -0
  229. package/dist/recall-state-view-inject.js.map +1 -0
  230. package/dist/recall-state-view-kind.d.ts +17 -0
  231. package/dist/recall-state-view-kind.js +14 -0
  232. package/dist/recall-state-view-kind.js.map +1 -0
  233. package/dist/recall-state-view-label.d.ts +17 -0
  234. package/dist/recall-state-view-label.js +16 -0
  235. package/dist/recall-state-view-label.js.map +1 -0
  236. package/dist/recall-state-view-render.js +3 -14
  237. package/dist/recall-state-view-render.js.map +1 -1
  238. package/dist/recall-state-view-successor.d.ts +15 -0
  239. package/dist/recall-state-view-successor.js +13 -0
  240. package/dist/recall-state-view-successor.js.map +1 -0
  241. package/dist/recall-state-view-superseded.d.ts +15 -0
  242. package/dist/recall-state-view-superseded.js +14 -0
  243. package/dist/recall-state-view-superseded.js.map +1 -0
  244. package/dist/recall-state-view-transition.d.ts +18 -0
  245. package/dist/recall-state-view-transition.js +14 -0
  246. package/dist/recall-state-view-transition.js.map +1 -0
  247. package/dist/resume-bundles.js +6 -6
  248. package/dist/search/factory.js +5 -5
  249. package/dist/search/index.js +9 -9
  250. package/dist/semantic-consolidation.js +9 -9
  251. package/dist/semantic-rule-promotion.js +8 -8
  252. package/dist/semantic-rule-verifier.js +8 -8
  253. package/dist/source-agent-qualifier.js +8 -8
  254. package/dist/storage.js +7 -7
  255. package/dist/support-passport/index.js +34 -34
  256. package/dist/transfer/backup.js +2 -2
  257. package/dist/transfer/capsule-export.js +2 -2
  258. package/dist/transfer/capsule-import.js +2 -2
  259. package/dist/transfer/export-okf.js +9 -9
  260. package/dist/verified-recall.js +8 -8
  261. package/dist/who-knows-cli.js +9 -9
  262. package/dist/who-knows.js +8 -8
  263. package/package.json +2 -2
  264. package/src/activity/dwell.test.ts +28 -0
  265. package/src/activity/dwell.ts +22 -0
  266. package/src/activity/from-ms.test.ts +25 -0
  267. package/src/activity/from-ms.ts +13 -0
  268. package/src/activity/heading-level.test.ts +23 -0
  269. package/src/activity/heading-level.ts +21 -0
  270. package/src/activity/heading-title.test.ts +21 -0
  271. package/src/activity/heading-title.ts +26 -0
  272. package/src/activity/index.ts +6 -0
  273. package/src/activity/journal-heading.test.ts +22 -0
  274. package/src/activity/journal-heading.ts +18 -0
  275. package/src/activity/journal-mode.test.ts +32 -0
  276. package/src/activity/journal-mode.ts +20 -0
  277. package/src/activity/journal-path.test.ts +25 -0
  278. package/src/activity/journal-path.ts +27 -0
  279. package/src/activity/journal-section.test.ts +37 -0
  280. package/src/activity/journal-section.ts +75 -0
  281. package/src/activity/journal-source.test.ts +37 -0
  282. package/src/activity/journal-source.ts +23 -0
  283. package/src/activity/privacy-enabled.test.ts +30 -0
  284. package/src/activity/privacy-enabled.ts +33 -0
  285. package/src/activity/privacy-keys.test.ts +22 -0
  286. package/src/activity/privacy-keys.ts +12 -0
  287. package/src/activity/privacy-redact.test.ts +25 -0
  288. package/src/activity/privacy-redact.ts +23 -0
  289. package/src/activity/privacy-window.test.ts +49 -0
  290. package/src/activity/privacy-window.ts +32 -0
  291. package/src/activity/retain-days.test.ts +20 -0
  292. package/src/activity/retain-days.ts +14 -0
  293. package/src/activity/timeline/analysis-claim.test.ts +33 -0
  294. package/src/activity/timeline/analysis-claim.ts +16 -0
  295. package/src/activity/timeline/analysis-dup.test.ts +28 -0
  296. package/src/activity/timeline/analysis-dup.ts +10 -0
  297. package/src/activity/timeline/analysis-empty.test.ts +21 -0
  298. package/src/activity/timeline/analysis-empty.ts +14 -0
  299. package/src/activity/timeline/analysis-evidence.test.ts +32 -0
  300. package/src/activity/timeline/analysis-evidence.ts +20 -0
  301. package/src/activity/timeline/analysis-max.test.ts +26 -0
  302. package/src/activity/timeline/analysis-max.ts +14 -0
  303. package/src/activity/timeline/analysis-order.test.ts +28 -0
  304. package/src/activity/timeline/analysis-order.ts +10 -0
  305. package/src/activity/timeline/analysis-overlap.test.ts +26 -0
  306. package/src/activity/timeline/analysis-overlap.ts +17 -0
  307. package/src/activity/timeline/analysis-prompt.test.ts +50 -0
  308. package/src/activity/timeline/analysis-prompt.ts +24 -0
  309. package/src/activity/timeline/index.ts +8 -0
  310. package/src/activity/timeline/recap-card-count.test.ts +19 -0
  311. package/src/activity/timeline/recap-card-count.ts +13 -0
  312. package/src/activity/timeline/recap-card-id.test.ts +21 -0
  313. package/src/activity/timeline/recap-card-id.ts +16 -0
  314. package/src/activity/timeline/recap-date.test.ts +21 -0
  315. package/src/activity/timeline/recap-date.ts +19 -0
  316. package/src/activity/timeline/recap-empty.test.ts +21 -0
  317. package/src/activity/timeline/recap-empty.ts +14 -0
  318. package/src/activity/timeline/recap-export.test.ts +78 -0
  319. package/src/activity/timeline/recap-export.ts +41 -0
  320. package/src/activity/timeline/recap-id.test.ts +28 -0
  321. package/src/activity/timeline/recap-id.ts +10 -0
  322. package/src/activity/timeline/recap-markdown.test.ts +56 -0
  323. package/src/activity/timeline/recap-markdown.ts +26 -0
  324. package/src/activity/timeline/recap-sort-key.test.ts +18 -0
  325. package/src/activity/timeline/recap-sort-key.ts +14 -0
  326. package/src/activity/timeline/recap-tz.test.ts +35 -0
  327. package/src/activity/timeline/recap-tz.ts +19 -0
  328. package/src/activity/timeline/week-count.test.ts +19 -0
  329. package/src/activity/timeline/week-count.ts +13 -0
  330. package/src/activity/timeline/week-days.test.ts +26 -0
  331. package/src/activity/timeline/week-days.ts +23 -0
  332. package/src/activity/timeline/week-export.test.ts +59 -0
  333. package/src/activity/timeline/week-export.ts +44 -0
  334. package/src/activity/timeline/week-iso.test.ts +29 -0
  335. package/src/activity/timeline/week-iso.ts +20 -0
  336. package/src/activity/timeline/week-monday.test.ts +17 -0
  337. package/src/activity/timeline/week-monday.ts +12 -0
  338. package/src/activity/timeline/week-range.test.ts +23 -0
  339. package/src/activity/timeline/week-range.ts +26 -0
  340. package/src/activity/timeline/week-snapshot.test.ts +66 -0
  341. package/src/activity/timeline/week-snapshot.ts +40 -0
  342. package/src/activity/timeline/week-span.test.ts +21 -0
  343. package/src/activity/timeline/week-span.ts +18 -0
  344. package/src/activity/timeline/week-sunday.test.ts +17 -0
  345. package/src/activity/timeline/week-sunday.ts +12 -0
  346. package/src/activity/to-ms.test.ts +25 -0
  347. package/src/activity/to-ms.ts +13 -0
  348. package/src/activity/vault-begin.test.ts +20 -0
  349. package/src/activity/vault-begin.ts +13 -0
  350. package/src/activity/vault-end.test.ts +20 -0
  351. package/src/activity/vault-end.ts +13 -0
  352. package/src/activity/vault-frontmatter.test.ts +28 -0
  353. package/src/activity/vault-frontmatter.ts +39 -0
  354. package/src/activity/vault-join.test.ts +21 -0
  355. package/src/activity/vault-join.ts +31 -0
  356. package/src/activity/vault-marker.test.ts +23 -0
  357. package/src/activity/vault-marker.ts +27 -0
  358. package/src/activity/vault-name-trim.test.ts +22 -0
  359. package/src/activity/vault-name-trim.ts +16 -0
  360. package/src/activity/vault-region-pair.test.ts +26 -0
  361. package/src/activity/vault-region-pair.ts +24 -0
  362. package/src/activity/vault-region.test.ts +24 -0
  363. package/src/activity/vault-region.ts +24 -0
  364. package/src/activity/vault-suffix.test.ts +20 -0
  365. package/src/activity/vault-suffix.ts +12 -0
  366. package/src/activity/vault-wikilink.test.ts +27 -0
  367. package/src/activity/vault-wikilink.ts +28 -0
  368. package/src/activity/window-order.test.ts +27 -0
  369. package/src/activity/window-order.ts +24 -0
  370. package/src/dedup/merge-count.test.ts +19 -0
  371. package/src/dedup/merge-count.ts +13 -0
  372. package/src/dedup/merge-dup.test.ts +21 -0
  373. package/src/dedup/merge-dup.ts +16 -0
  374. package/src/dedup/merge-empty.test.ts +22 -0
  375. package/src/dedup/merge-empty.ts +18 -0
  376. package/src/dedup/merge-id.test.ts +18 -0
  377. package/src/dedup/merge-id.ts +14 -0
  378. package/src/dedup/merge-min-score.test.ts +37 -0
  379. package/src/dedup/merge-min-score.ts +22 -0
  380. package/src/dedup/merge-pair.test.ts +19 -0
  381. package/src/dedup/merge-pair.ts +13 -0
  382. package/src/dedup/merge-score.test.ts +20 -0
  383. package/src/dedup/merge-score.ts +14 -0
  384. package/src/dedup/merge-self.test.ts +19 -0
  385. package/src/dedup/merge-self.ts +17 -0
  386. package/src/dedup/merge-threshold.test.ts +46 -0
  387. package/src/dedup/merge-threshold.ts +26 -0
  388. package/src/dedup/merge-tie.test.ts +22 -0
  389. package/src/dedup/merge-tie.ts +13 -0
  390. package/src/extraction-span-bench.test.ts +27 -0
  391. package/src/extraction-span-bench.ts +28 -0
  392. package/src/extraction-span-clip.test.ts +71 -0
  393. package/src/extraction-span-clip.ts +26 -0
  394. package/src/extraction-span-contains.test.ts +30 -0
  395. package/src/extraction-span-contains.ts +20 -0
  396. package/src/extraction-span-gap.test.ts +29 -0
  397. package/src/extraction-span-gap.ts +25 -0
  398. package/src/extraction-span-join.test.ts +50 -0
  399. package/src/extraction-span-join.ts +30 -0
  400. package/src/extraction-span-length.test.ts +25 -0
  401. package/src/extraction-span-length.ts +16 -0
  402. package/src/extraction-span-overlap.test.ts +34 -0
  403. package/src/extraction-span-overlap.ts +23 -0
  404. package/src/extraction-span-shift.test.ts +25 -0
  405. package/src/extraction-span-shift.ts +20 -0
  406. package/src/extraction-span-slice.test.ts +32 -0
  407. package/src/extraction-span-slice.ts +20 -0
  408. package/src/extraction-span-validate.test.ts +42 -0
  409. package/src/extraction-span-validate.ts +27 -0
  410. package/src/recall-deep-budget-left.test.ts +20 -0
  411. package/src/recall-deep-budget-left.ts +13 -0
  412. package/src/recall-deep-budget.test.ts +19 -0
  413. package/src/recall-deep-budget.ts +14 -0
  414. package/src/recall-deep-expand.test.ts +23 -0
  415. package/src/recall-deep-expand.ts +19 -0
  416. package/src/recall-deep-policy.test.ts +32 -0
  417. package/src/recall-deep-policy.ts +22 -0
  418. package/src/recall-deep-query.test.ts +23 -0
  419. package/src/recall-deep-query.ts +15 -0
  420. package/src/recall-deep-refine.test.ts +23 -0
  421. package/src/recall-deep-refine.ts +19 -0
  422. package/src/recall-deep-step-count.test.ts +20 -0
  423. package/src/recall-deep-step-count.ts +13 -0
  424. package/src/recall-deep-stop-reason.test.ts +46 -0
  425. package/src/recall-deep-stop-reason.ts +27 -0
  426. package/src/recall-deep-stop.test.ts +34 -0
  427. package/src/recall-deep-stop.ts +31 -0
  428. package/src/recall-deep-working-set.test.ts +21 -0
  429. package/src/recall-deep-working-set.ts +14 -0
  430. package/src/recall-navigate-access.test.ts +73 -0
  431. package/src/recall-navigate-access.ts +48 -0
  432. package/src/recall-navigate-action.test.ts +32 -0
  433. package/src/recall-navigate-action.ts +20 -0
  434. package/src/recall-navigate-budget.test.ts +23 -0
  435. package/src/recall-navigate-budget.ts +22 -0
  436. package/src/recall-navigate-cost.test.ts +20 -0
  437. package/src/recall-navigate-cost.ts +13 -0
  438. package/src/recall-navigate-depth.test.ts +20 -0
  439. package/src/recall-navigate-depth.ts +13 -0
  440. package/src/recall-navigate-link.test.ts +19 -0
  441. package/src/recall-navigate-link.ts +26 -0
  442. package/src/recall-navigate-max-children.test.ts +20 -0
  443. package/src/recall-navigate-max-children.ts +13 -0
  444. package/src/recall-navigate-node.test.ts +21 -0
  445. package/src/recall-navigate-node.ts +16 -0
  446. package/src/recall-navigate-path.test.ts +21 -0
  447. package/src/recall-navigate-path.ts +14 -0
  448. package/src/recall-navigate-render.test.ts +63 -0
  449. package/src/recall-navigate-render.ts +31 -0
  450. package/src/recall-state-view-current.test.ts +23 -0
  451. package/src/recall-state-view-current.ts +24 -0
  452. package/src/recall-state-view-inject.test.ts +32 -0
  453. package/src/recall-state-view-inject.ts +14 -0
  454. package/src/recall-state-view-kind.test.ts +19 -0
  455. package/src/recall-state-view-kind.ts +20 -0
  456. package/src/recall-state-view-label.test.ts +36 -0
  457. package/src/recall-state-view-label.ts +22 -0
  458. package/src/recall-state-view-successor.test.ts +21 -0
  459. package/src/recall-state-view-successor.ts +16 -0
  460. package/src/recall-state-view-superseded.test.ts +20 -0
  461. package/src/recall-state-view-superseded.ts +17 -0
  462. package/src/recall-state-view-transition.test.ts +30 -0
  463. package/src/recall-state-view-transition.ts +24 -0
  464. package/src/shared-context/authority.test.ts +35 -0
  465. package/src/shared-context/authority.ts +25 -0
  466. package/src/shared-context/claim-key.test.ts +21 -0
  467. package/src/shared-context/claim-key.ts +16 -0
  468. package/src/shared-context/contradictions.test.ts +39 -0
  469. package/src/shared-context/contradictions.ts +32 -0
  470. package/src/shared-context/envelope-actor.test.ts +21 -0
  471. package/src/shared-context/envelope-actor.ts +16 -0
  472. package/src/shared-context/envelope-at.test.ts +19 -0
  473. package/src/shared-context/envelope-at.ts +17 -0
  474. package/src/shared-context/envelope-id.test.ts +21 -0
  475. package/src/shared-context/envelope-id.ts +16 -0
  476. package/src/shared-context/envelope-required.test.ts +24 -0
  477. package/src/shared-context/envelope-required.ts +15 -0
  478. package/src/shared-context/envelope-ttl.test.ts +29 -0
  479. package/src/shared-context/envelope-ttl.ts +35 -0
  480. package/src/shared-context/provenance.test.ts +35 -0
  481. package/src/shared-context/provenance.ts +68 -0
  482. package/dist/chunk-47WWTCJY.js.map +0 -1
  483. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
  484. /package/dist/{chunk-36HJ36FK.js.map → chunk-27JH2KKM.js.map} +0 -0
  485. /package/dist/{chunk-NZPFCP5A.js.map → chunk-2PSX5BJU.js.map} +0 -0
  486. /package/dist/{chunk-RCPKBW7K.js.map → chunk-322MSJYS.js.map} +0 -0
  487. /package/dist/{chunk-C3ZYZKDJ.js.map → chunk-3E4REX7E.js.map} +0 -0
  488. /package/dist/{chunk-Y2YM4C75.js.map → chunk-4BRBSON5.js.map} +0 -0
  489. /package/dist/{chunk-B4B3QDV3.js.map → chunk-4EX26ZS3.js.map} +0 -0
  490. /package/dist/{chunk-ER2UPUMG.js.map → chunk-562Q236T.js.map} +0 -0
  491. /package/dist/{chunk-F2JXJABQ.js.map → chunk-5BIOXQGN.js.map} +0 -0
  492. /package/dist/{chunk-DSEHPUID.js.map → chunk-5C3OQ253.js.map} +0 -0
  493. /package/dist/{chunk-LSR5LQXN.js.map → chunk-5VLXQFOS.js.map} +0 -0
  494. /package/dist/{chunk-2QSGJTDG.js.map → chunk-636UUVOZ.js.map} +0 -0
  495. /package/dist/{chunk-AI5M4FVQ.js.map → chunk-6DTQSLER.js.map} +0 -0
  496. /package/dist/{chunk-CDLPF3PX.js.map → chunk-6IL5FFHH.js.map} +0 -0
  497. /package/dist/{chunk-VKDBDJIX.js.map → chunk-6L6V2OIX.js.map} +0 -0
  498. /package/dist/{chunk-ZXROBS53.js.map → chunk-6NA527W2.js.map} +0 -0
  499. /package/dist/{chunk-R2Q6FTOX.js.map → chunk-7IEFFOIC.js.map} +0 -0
  500. /package/dist/{chunk-4PG2CKI6.js.map → chunk-AQHDMA4A.js.map} +0 -0
  501. /package/dist/{chunk-RKNJBZ55.js.map → chunk-AU7Q3LSC.js.map} +0 -0
  502. /package/dist/{chunk-6BYJQ3L2.js.map → chunk-BE4Y6GK6.js.map} +0 -0
  503. /package/dist/{chunk-PW3GKWH3.js.map → chunk-BQVWC67C.js.map} +0 -0
  504. /package/dist/{chunk-KQAFEZQX.js.map → chunk-BUKK5SWA.js.map} +0 -0
  505. /package/dist/{chunk-UVYL5G7A.js.map → chunk-C3LUYZLX.js.map} +0 -0
  506. /package/dist/{chunk-JMXHVJ2V.js.map → chunk-DFWO37KT.js.map} +0 -0
  507. /package/dist/{chunk-WKWY7YF3.js.map → chunk-F5FHDDOV.js.map} +0 -0
  508. /package/dist/{chunk-U4HZEW2B.js.map → chunk-FH4BB7F4.js.map} +0 -0
  509. /package/dist/{chunk-UHQE37NI.js.map → chunk-G5TYMG7R.js.map} +0 -0
  510. /package/dist/{chunk-PP7R754B.js.map → chunk-GPFZFCRN.js.map} +0 -0
  511. /package/dist/{chunk-EH7BNIUN.js.map → chunk-HAF3PUMO.js.map} +0 -0
  512. /package/dist/{chunk-J2R275FN.js.map → chunk-HQJM3BKH.js.map} +0 -0
  513. /package/dist/{chunk-O7ERGZDA.js.map → chunk-I7NBTRLR.js.map} +0 -0
  514. /package/dist/{chunk-6545JDIM.js.map → chunk-IHK5KTOC.js.map} +0 -0
  515. /package/dist/{chunk-25OLNWYG.js.map → chunk-IP7KIW7W.js.map} +0 -0
  516. /package/dist/{chunk-XJJYSX6F.js.map → chunk-JCUG7AXZ.js.map} +0 -0
  517. /package/dist/{chunk-OERZXNSK.js.map → chunk-JGXMA6HL.js.map} +0 -0
  518. /package/dist/{chunk-CQ6IWZDI.js.map → chunk-JVDAIASW.js.map} +0 -0
  519. /package/dist/{chunk-F4W5HTED.js.map → chunk-K3CQQNMX.js.map} +0 -0
  520. /package/dist/{chunk-D2YNNDML.js.map → chunk-KWIGDKM2.js.map} +0 -0
  521. /package/dist/{chunk-227UFSEQ.js.map → chunk-LEPOP46W.js.map} +0 -0
  522. /package/dist/{chunk-OLBZ2WGG.js.map → chunk-LTBKKSCX.js.map} +0 -0
  523. /package/dist/{chunk-VSMSYDIH.js.map → chunk-MARZW7DB.js.map} +0 -0
  524. /package/dist/{chunk-W5IYHQA5.js.map → chunk-MS2I7JVN.js.map} +0 -0
  525. /package/dist/{chunk-BLHKR4GJ.js.map → chunk-NHACZAM3.js.map} +0 -0
  526. /package/dist/{chunk-2VZHB5US.js.map → chunk-NTZ3452G.js.map} +0 -0
  527. /package/dist/{chunk-65HXVEVG.js.map → chunk-OT3GGOFW.js.map} +0 -0
  528. /package/dist/{chunk-2NLLXCJG.js.map → chunk-OWHERGF2.js.map} +0 -0
  529. /package/dist/{chunk-OUKBMQ2O.js.map → chunk-PD66CVSD.js.map} +0 -0
  530. /package/dist/{chunk-EQRD4NMH.js.map → chunk-PMMNP5GZ.js.map} +0 -0
  531. /package/dist/{chunk-PRRUK2ZH.js.map → chunk-RNVXKTIA.js.map} +0 -0
  532. /package/dist/{chunk-ZBIEX4XN.js.map → chunk-SACAOBDB.js.map} +0 -0
  533. /package/dist/{chunk-JJ6ZEFXC.js.map → chunk-SNFWXG5G.js.map} +0 -0
  534. /package/dist/{chunk-NTFXBILO.js.map → chunk-SY7ASLC6.js.map} +0 -0
  535. /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
  536. /package/dist/{chunk-X7Y7WX73.js.map → chunk-UVYI6VIX.js.map} +0 -0
  537. /package/dist/{chunk-YZ5VC2JJ.js.map → chunk-VVWPY5U7.js.map} +0 -0
  538. /package/dist/{chunk-OIDICRH7.js.map → chunk-W7O7PH5E.js.map} +0 -0
  539. /package/dist/{chunk-RH3XLQG4.js.map → chunk-WTRFM4SF.js.map} +0 -0
  540. /package/dist/{chunk-6PUF7LUO.js.map → chunk-Y5U72X3D.js.map} +0 -0
  541. /package/dist/{chunk-FFWONAAW.js.map → chunk-Y7LFWOX6.js.map} +0 -0
  542. /package/dist/{chunk-D3DUA64S.js.map → chunk-YJDKSDIU.js.map} +0 -0
  543. /package/dist/{chunk-BIJAWZYW.js.map → chunk-YMOPYG3K.js.map} +0 -0
  544. /package/dist/{chunk-ZMVW2HJJ.js.map → chunk-YUYARGKM.js.map} +0 -0
  545. /package/dist/{chunk-UMZUKOJU.js.map → chunk-Z3YEGSR4.js.map} +0 -0
  546. /package/dist/{chunk-D6Q5EPOV.js.map → chunk-ZYKNDRUJ.js.map} +0 -0
  547. /package/dist/{graph-edge-decay-FXGELBFA.js.map → graph-edge-decay-A4SDFMMS.js.map} +0 -0
@@ -0,0 +1,20 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { isRegionBeginLine } from "./vault-begin.js";
5
+
6
+ test("isRegionBeginLine matches a trimmed begin comment", () => {
7
+ assert.equal(isRegionBeginLine("<!-- remnic:begin timeline -->", "timeline"), true);
8
+ assert.equal(isRegionBeginLine(" <!-- remnic:begin timeline -->\t", "timeline"), true);
9
+ });
10
+
11
+ test("isRegionBeginLine rejects a mismatch", () => {
12
+ assert.equal(isRegionBeginLine("<!-- remnic:end timeline -->", "timeline"), false);
13
+ assert.equal(isRegionBeginLine("<!-- remnic:begin weekly -->", "timeline"), false);
14
+ assert.equal(isRegionBeginLine("<!-- remnic:timeline:start -->", "timeline"), false);
15
+ assert.equal(isRegionBeginLine("<!-- remnic:begin timeline --> extra", "timeline"), false);
16
+ });
17
+
18
+ test("isRegionBeginLine throws on an empty name", () => {
19
+ assert.throws(() => isRegionBeginLine("<!-- remnic:begin -->", ""), /empty/i);
20
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Match a managed-region begin marker line (issue #1985).
3
+ *
4
+ * True iff the trimmed line equals `<!-- remnic:begin NAME -->`.
5
+ * Empty name throws.
6
+ */
7
+
8
+ export function isRegionBeginLine(line: string, name: string): boolean {
9
+ if (typeof name !== "string" || name.length === 0) {
10
+ throw new RangeError("Region name must be non-empty.");
11
+ }
12
+ return line.trim() === `<!-- remnic:begin ${name} -->`;
13
+ }
@@ -0,0 +1,20 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { isRegionEndLine } from "./vault-end.js";
5
+
6
+ test("isRegionEndLine matches a trimmed end comment", () => {
7
+ assert.equal(isRegionEndLine("<!-- remnic:end timeline -->", "timeline"), true);
8
+ assert.equal(isRegionEndLine(" <!-- remnic:end timeline -->\t", "timeline"), true);
9
+ });
10
+
11
+ test("isRegionEndLine rejects a mismatch", () => {
12
+ assert.equal(isRegionEndLine("<!-- remnic:begin timeline -->", "timeline"), false);
13
+ assert.equal(isRegionEndLine("<!-- remnic:end weekly -->", "timeline"), false);
14
+ assert.equal(isRegionEndLine("<!-- remnic:timeline:end -->", "timeline"), false);
15
+ assert.equal(isRegionEndLine("<!-- remnic:end timeline --> extra", "timeline"), false);
16
+ });
17
+
18
+ test("isRegionEndLine throws on an empty name", () => {
19
+ assert.throws(() => isRegionEndLine("<!-- remnic:end -->", ""), /empty/i);
20
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Match a managed-region end marker line (issue #1985).
3
+ *
4
+ * True iff the trimmed line equals `<!-- remnic:end NAME -->`.
5
+ * Empty name throws.
6
+ */
7
+
8
+ export function isRegionEndLine(line: string, name: string): boolean {
9
+ if (typeof name !== "string" || name.length === 0) {
10
+ throw new RangeError("Region name must be non-empty.");
11
+ }
12
+ return line.trim() === `<!-- remnic:end ${name} -->`;
13
+ }
@@ -0,0 +1,28 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { applyVaultFrontmatter } from "./vault-frontmatter.js";
5
+
6
+ test("applyVaultFrontmatter merges listed keys and sorts", () => {
7
+ const merged = applyVaultFrontmatter("zeta: old\nalpha: keep\n", {
8
+ zeta: "new",
9
+ remnic_cards: "3",
10
+ });
11
+ assert.equal(merged, "alpha: keep\nremnic_cards: 3\nzeta: new");
12
+ });
13
+
14
+ test("applyVaultFrontmatter rejects keys with newlines", () => {
15
+ assert.throws(
16
+ () => applyVaultFrontmatter("alpha: keep", { "bad\nkey": "1" }),
17
+ /newline/i,
18
+ );
19
+ assert.throws(
20
+ () => applyVaultFrontmatter("alpha: keep", { "bad\rkey": "1" }),
21
+ /newline/i,
22
+ );
23
+ });
24
+
25
+ test("applyVaultFrontmatter leaves the original body unchanged when updates are empty", () => {
26
+ const existing = "zeta: 1\n# comment stays\nalpha: 2\n";
27
+ assert.equal(applyVaultFrontmatter(existing, {}), existing);
28
+ });
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Merge vault note frontmatter keys (issue #1985).
3
+ *
4
+ * Pure string helper. Empty updates leave the original YAML unchanged.
5
+ * Listed keys stay. Output keys are sorted. Keys with newlines are rejected.
6
+ */
7
+
8
+ function assertNoNewlineKey(key: string): void {
9
+ if (key.includes("\n") || key.includes("\r")) {
10
+ throw new RangeError("Vault frontmatter keys must not contain newlines.");
11
+ }
12
+ }
13
+
14
+ export function applyVaultFrontmatter(
15
+ existingYaml: string,
16
+ updates: Readonly<Record<string, string>>,
17
+ ): string {
18
+ const entries = Object.entries(updates);
19
+ if (entries.length === 0) return existingYaml;
20
+
21
+ const merged = new Map<string, string>();
22
+ for (const line of existingYaml.split("\n")) {
23
+ const idx = line.indexOf(":");
24
+ if (idx === -1) continue;
25
+ const key = line.slice(0, idx).trim();
26
+ if (key.length === 0) continue;
27
+ assertNoNewlineKey(key);
28
+ merged.set(key, line.slice(idx + 1).trim());
29
+ }
30
+ for (const [key, value] of entries) {
31
+ assertNoNewlineKey(key);
32
+ merged.set(key, value);
33
+ }
34
+
35
+ return [...merged.keys()]
36
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
37
+ .map((key) => `${key}: ${merged.get(key)}`)
38
+ .join("\n");
39
+ }
@@ -0,0 +1,21 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { joinVaultSegments } from "./vault-join.js";
5
+
6
+ test("joinVaultSegments joins relative parts with /", () => {
7
+ assert.equal(
8
+ joinVaultSegments(["Daily Notes", "2026", "08", "2026-08-18.md"]),
9
+ "Daily Notes/2026/08/2026-08-18.md",
10
+ );
11
+ });
12
+
13
+ test("joinVaultSegments rejects ..", () => {
14
+ assert.throws(() => joinVaultSegments(["Daily Notes", "..", "secret"]), /\.\./);
15
+ assert.throws(() => joinVaultSegments(["foo/../bar"]), /\.\./);
16
+ });
17
+
18
+ test("joinVaultSegments rejects empty", () => {
19
+ assert.throws(() => joinVaultSegments([]), /empty/i);
20
+ assert.throws(() => joinVaultSegments(["Daily Notes", ""]), /empty/i);
21
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Join vault-relative path segments (issue #1985).
3
+ *
4
+ * Joins with `/`. Rejects an empty list, empty parts, `..`, absolute
5
+ * parts, backslash, and newline.
6
+ */
7
+ import path from "node:path";
8
+
9
+ export function joinVaultSegments(parts: readonly string[]): string {
10
+ if (parts.length === 0) {
11
+ throw new RangeError("Vault path parts must be a non-empty list.");
12
+ }
13
+ for (const part of parts) {
14
+ if (typeof part !== "string" || part.length === 0) {
15
+ throw new RangeError("Vault path part must be non-empty.");
16
+ }
17
+ if (part.includes("\\")) {
18
+ throw new RangeError("Vault path part must not contain a backslash.");
19
+ }
20
+ if (part.includes("\n") || part.includes("\r")) {
21
+ throw new RangeError("Vault path part must not contain a newline.");
22
+ }
23
+ if (path.posix.isAbsolute(part) || path.win32.isAbsolute(part)) {
24
+ throw new RangeError("Vault path part must be relative.");
25
+ }
26
+ if (part.split("/").includes("..")) {
27
+ throw new RangeError("Vault path part must not contain `..`.");
28
+ }
29
+ }
30
+ return parts.join("/");
31
+ }
@@ -0,0 +1,23 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { formatRegionMarkers } from "./vault-marker.js";
5
+
6
+ test("formatRegionMarkers returns begin and end HTML comments", () => {
7
+ assert.deepEqual(formatRegionMarkers("timeline"), {
8
+ begin: "<!-- remnic:begin timeline -->",
9
+ end: "<!-- remnic:end timeline -->",
10
+ });
11
+ });
12
+
13
+ test("formatRegionMarkers rejects an empty name", () => {
14
+ assert.throws(() => formatRegionMarkers(""), /empty/i);
15
+ });
16
+
17
+ test("formatRegionMarkers rejects a newline in the name", () => {
18
+ assert.throws(() => formatRegionMarkers("time\nline"), /newline/i);
19
+ });
20
+
21
+ test("formatRegionMarkers rejects marker text in the name", () => {
22
+ assert.throws(() => formatRegionMarkers("foo-->bar"), /-->/);
23
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Format managed-region HTML comment markers (issue #1985).
3
+ *
4
+ * Rejects empty names, newlines, and `-->` so the closer cannot leak
5
+ * out of `<!-- remnic:begin NAME -->`.
6
+ */
7
+
8
+ export type RegionMarkers = {
9
+ begin: string;
10
+ end: string;
11
+ };
12
+
13
+ export function formatRegionMarkers(name: string): RegionMarkers {
14
+ if (typeof name !== "string" || name.length === 0) {
15
+ throw new RangeError("Region name must be non-empty.");
16
+ }
17
+ if (name.includes("\n") || name.includes("\r")) {
18
+ throw new RangeError("Region name must not contain a newline.");
19
+ }
20
+ if (name.includes("-->")) {
21
+ throw new RangeError("Region name must not contain -->.");
22
+ }
23
+ return {
24
+ begin: `<!-- remnic:begin ${name} -->`,
25
+ end: `<!-- remnic:end ${name} -->`,
26
+ };
27
+ }
@@ -0,0 +1,22 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { trimRegionName } from "./vault-name-trim.js";
5
+
6
+ test("trimRegionName accepts a name", () => {
7
+ assert.equal(trimRegionName("timeline"), "timeline");
8
+ });
9
+
10
+ test("trimRegionName rejects an empty name", () => {
11
+ assert.throws(() => trimRegionName(""), /empty/i);
12
+ assert.throws(() => trimRegionName(" "), /empty/i);
13
+ });
14
+
15
+ test("trimRegionName rejects a newline in the name", () => {
16
+ assert.throws(() => trimRegionName("time\nline"), /newline/i);
17
+ assert.throws(() => trimRegionName("time\rline"), /newline/i);
18
+ });
19
+
20
+ test("trimRegionName trims surrounding whitespace", () => {
21
+ assert.equal(trimRegionName(" timeline "), "timeline");
22
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Trim a managed-region name (issue #1985).
3
+ *
4
+ * Empty after trim throws. Newline throws. Else the trimmed string.
5
+ */
6
+
7
+ export function trimRegionName(value: string): string {
8
+ const name = value.trim();
9
+ if (name.length === 0) {
10
+ throw new RangeError("Region name must be non-empty.");
11
+ }
12
+ if (name.includes("\n") || name.includes("\r")) {
13
+ throw new RangeError("Region name must not contain a newline.");
14
+ }
15
+ return name;
16
+ }
@@ -0,0 +1,26 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { assertBeginEndPair } from "./vault-region-pair.js";
5
+
6
+ test("assertBeginEndPair accepts a matching pair", () => {
7
+ assert.deepEqual(assertBeginEndPair({ beginName: "timeline", endName: "timeline" }), { ok: true });
8
+ assert.deepEqual(assertBeginEndPair({ beginName: " timeline ", endName: "timeline" }), {
9
+ ok: true,
10
+ });
11
+ assert.deepEqual(assertBeginEndPair({ beginName: "timeline", endName: " timeline " }), {
12
+ ok: true,
13
+ });
14
+ });
15
+
16
+ test("assertBeginEndPair rejects a name mismatch", () => {
17
+ assert.deepEqual(assertBeginEndPair({ beginName: "timeline", endName: "weekly" }), {
18
+ ok: false,
19
+ error: "name_mismatch",
20
+ });
21
+ });
22
+
23
+ test("assertBeginEndPair throws on an empty name", () => {
24
+ assert.throws(() => assertBeginEndPair({ beginName: "", endName: "timeline" }), /empty/i);
25
+ assert.throws(() => assertBeginEndPair({ beginName: "timeline", endName: " " }), /empty/i);
26
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Assert a managed-region begin/end name pair (issue #1985).
3
+ *
4
+ * Trim both names. Empty throws. Mismatch is name_mismatch. Else ok.
5
+ */
6
+
7
+ export type BeginEndPairInput = {
8
+ beginName: string;
9
+ endName: string;
10
+ };
11
+
12
+ export type BeginEndPairResult = { ok: true } | { ok: false; error: "name_mismatch" };
13
+
14
+ export function assertBeginEndPair({ beginName, endName }: BeginEndPairInput): BeginEndPairResult {
15
+ const begin = beginName.trim();
16
+ const end = endName.trim();
17
+ if (begin.length === 0 || end.length === 0) {
18
+ throw new RangeError("Region name must be non-empty.");
19
+ }
20
+ if (begin !== end) {
21
+ return { ok: false, error: "name_mismatch" };
22
+ }
23
+ return { ok: true };
24
+ }
@@ -0,0 +1,24 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { validateRegionName } from "./vault-region.js";
5
+
6
+ test("validateRegionName trims a valid name", () => {
7
+ assert.deepEqual(validateRegionName("timeline"), { ok: true, name: "timeline" });
8
+ assert.deepEqual(validateRegionName(" weekly "), { ok: true, name: "weekly" });
9
+ });
10
+
11
+ test("validateRegionName rejects empty and whitespace-only names", () => {
12
+ assert.deepEqual(validateRegionName(""), { ok: false, error: "invalid_name" });
13
+ assert.deepEqual(validateRegionName(" "), { ok: false, error: "invalid_name" });
14
+ });
15
+
16
+ test("validateRegionName rejects names with newlines", () => {
17
+ assert.deepEqual(validateRegionName("time\nline"), { ok: false, error: "invalid_name" });
18
+ assert.deepEqual(validateRegionName("time\rline"), { ok: false, error: "invalid_name" });
19
+ });
20
+
21
+ test("validateRegionName rejects the HTML comment closer", () => {
22
+ assert.deepEqual(validateRegionName("foo-->bar"), { ok: false, error: "invalid_name" });
23
+ assert.deepEqual(validateRegionName("timeline-->"), { ok: false, error: "invalid_name" });
24
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Validate a managed-region name for vault markers (issue #1985).
3
+ *
4
+ * Empty, whitespace-only, newline, and `-->` names are rejected so they
5
+ * cannot break `<!-- remnic:<name>:start -->` markers.
6
+ */
7
+
8
+ export type ValidateRegionNameResult =
9
+ | { ok: true; name: string }
10
+ | { ok: false; error: "invalid_name" };
11
+
12
+ export function validateRegionName(name: string): ValidateRegionNameResult {
13
+ if (typeof name !== "string") {
14
+ return { ok: false, error: "invalid_name" };
15
+ }
16
+ if (name.includes("\n") || name.includes("\r") || name.includes("-->")) {
17
+ return { ok: false, error: "invalid_name" };
18
+ }
19
+ const trimmed = name.trim();
20
+ if (trimmed.length === 0) {
21
+ return { ok: false, error: "invalid_name" };
22
+ }
23
+ return { ok: true, name: trimmed };
24
+ }
@@ -0,0 +1,20 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { stripVaultMdSuffix } from "./vault-suffix.js";
5
+
6
+ test("stripVaultMdSuffix drops a trailing .md", () => {
7
+ assert.equal(stripVaultMdSuffix("Daily Notes/2026-08-18.md"), "Daily Notes/2026-08-18");
8
+ });
9
+
10
+ test("stripVaultMdSuffix leaves a path with no suffix unchanged", () => {
11
+ assert.equal(stripVaultMdSuffix("Daily Notes/2026-08-18"), "Daily Notes/2026-08-18");
12
+ });
13
+
14
+ test("stripVaultMdSuffix throws on empty input", () => {
15
+ assert.throws(() => stripVaultMdSuffix(""), /empty/i);
16
+ });
17
+
18
+ test("stripVaultMdSuffix does not strip .markdown", () => {
19
+ assert.equal(stripVaultMdSuffix("Daily Notes/2026-08-18.markdown"), "Daily Notes/2026-08-18.markdown");
20
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Strip a trailing `.md` suffix from a vault note path (issue #1985).
3
+ *
4
+ * Case-sensitive. Does not strip `.markdown`. Empty input is rejected.
5
+ */
6
+
7
+ export function stripVaultMdSuffix(notePath: string): string {
8
+ if (typeof notePath !== "string" || notePath.length === 0) {
9
+ throw new RangeError("Vault path must be non-empty.");
10
+ }
11
+ return notePath.endsWith(".md") ? notePath.slice(0, -3) : notePath;
12
+ }
@@ -0,0 +1,27 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { formatVaultWikilink } from "./vault-wikilink.js";
5
+
6
+ test("formatVaultWikilink returns a wikilink without the .md suffix", () => {
7
+ assert.equal(formatVaultWikilink("Daily Notes/2026-08-18.md"), "[[Daily Notes/2026-08-18]]");
8
+ assert.equal(formatVaultWikilink("Places/ACME Office"), "[[Places/ACME Office]]");
9
+ });
10
+
11
+ test("formatVaultWikilink rejects absolute paths", () => {
12
+ assert.throws(() => formatVaultWikilink("/abs/note.md"), /absolute/i);
13
+ assert.throws(() => formatVaultWikilink("C:/vault/note.md"), /absolute/i);
14
+ });
15
+
16
+ test("formatVaultWikilink rejects parent segments", () => {
17
+ assert.throws(() => formatVaultWikilink("../outside.md"), /\.\./);
18
+ assert.throws(() => formatVaultWikilink("notes/../../secret.md"), /\.\./);
19
+ });
20
+
21
+ test("formatVaultWikilink rejects empty and newline paths", () => {
22
+ assert.throws(() => formatVaultWikilink(""), /empty/i);
23
+ assert.throws(() => formatVaultWikilink(" "), /empty/i);
24
+ assert.throws(() => formatVaultWikilink(".md"), /empty/i);
25
+ assert.throws(() => formatVaultWikilink("foo\nbar.md"), /newline/i);
26
+ assert.throws(() => formatVaultWikilink("foo\rbar.md"), /newline/i);
27
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Format a vault-relative note path as an Obsidian wikilink (issue #1985).
3
+ *
4
+ * Relative paths only. Rejects absolute paths, `..` segments, empty input,
5
+ * and newlines. Strips a trailing `.md` suffix.
6
+ */
7
+ import path from "node:path";
8
+
9
+ export function formatVaultWikilink(notePath: string): string {
10
+ if (typeof notePath !== "string" || notePath.trim().length === 0) {
11
+ throw new RangeError("Vault wikilink path must be a non-empty relative path.");
12
+ }
13
+ if (notePath.includes("\n") || notePath.includes("\r")) {
14
+ throw new RangeError("Vault wikilink path must not contain newlines.");
15
+ }
16
+ if (path.posix.isAbsolute(notePath) || path.win32.isAbsolute(notePath)) {
17
+ throw new RangeError("Vault wikilink path must be relative; absolute paths are rejected.");
18
+ }
19
+ if (notePath.split(/[\\/]/).some((segment) => segment === "..")) {
20
+ throw new RangeError("Vault wikilink path must not contain `..` segments.");
21
+ }
22
+
23
+ const stripped = notePath.endsWith(".md") ? notePath.slice(0, -3) : notePath;
24
+ if (stripped.length === 0) {
25
+ throw new RangeError("Vault wikilink path must be a non-empty relative path.");
26
+ }
27
+ return `[[${stripped}]]`;
28
+ }
@@ -0,0 +1,27 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { assertHalfOpenWindow } from "./window-order.js";
5
+
6
+ test("ordered window is ok", () => {
7
+ assert.deepEqual(assertHalfOpenWindow({ fromMs: 0, toMs: 1 }), { ok: true });
8
+ });
9
+
10
+ test("equal bounds are empty_window", () => {
11
+ assert.deepEqual(assertHalfOpenWindow({ fromMs: 10, toMs: 10 }), {
12
+ ok: false,
13
+ error: "empty_window",
14
+ });
15
+ });
16
+
17
+ test("inverted bounds are empty_window", () => {
18
+ assert.deepEqual(assertHalfOpenWindow({ fromMs: 20, toMs: 10 }), {
19
+ ok: false,
20
+ error: "empty_window",
21
+ });
22
+ });
23
+
24
+ test("NaN bounds throw", () => {
25
+ assert.throws(() => assertHalfOpenWindow({ fromMs: Number.NaN, toMs: 1 }), /finite/);
26
+ assert.throws(() => assertHalfOpenWindow({ fromMs: 0, toMs: Number.NaN }), /finite/);
27
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Assert a half-open activity window [fromMs, toMs) (issue #2053 leftover).
3
+ *
4
+ * Non-finite bounds throw. `toMs <= fromMs` is empty_window.
5
+ */
6
+
7
+ export type AssertHalfOpenWindowResult =
8
+ | { ok: true }
9
+ | { ok: false; error: "empty_window" };
10
+
11
+ /** Validate `[fromMs, toMs)`. Empty or inverted windows fail closed. */
12
+ export function assertHalfOpenWindow(window: {
13
+ fromMs: number;
14
+ toMs: number;
15
+ }): AssertHalfOpenWindowResult {
16
+ const { fromMs, toMs } = window;
17
+ if (!Number.isFinite(fromMs) || !Number.isFinite(toMs)) {
18
+ throw new RangeError(
19
+ `activity window bounds must be finite; got fromMs=${fromMs} toMs=${toMs}`,
20
+ );
21
+ }
22
+ if (toMs <= fromMs) return { ok: false, error: "empty_window" };
23
+ return { ok: true };
24
+ }
@@ -0,0 +1,19 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { countMergeCandidates } from "./merge-count.js";
5
+
6
+ test("empty list is 0", () => {
7
+ assert.equal(countMergeCandidates([]), 0);
8
+ });
9
+
10
+ test("counts unique ids", () => {
11
+ assert.equal(countMergeCandidates(["alpha", "beta", "alpha"]), 2);
12
+ assert.equal(countMergeCandidates(["alpha", "beta", "gamma"]), 3);
13
+ });
14
+
15
+ test("drops empty strings and does not mutate", () => {
16
+ const ids = ["alpha", "", "beta", "", "alpha"];
17
+ assert.equal(countMergeCandidates(ids), 2);
18
+ assert.deepEqual(ids, ["alpha", "", "beta", "", "alpha"]);
19
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Merge-candidate counter (issue #2330 leftover).
3
+ *
4
+ * Unique ids after dropping empty strings. Does not mutate input.
5
+ */
6
+
7
+ export function countMergeCandidates(ids: readonly string[]): number {
8
+ const unique = new Set<string>();
9
+ for (const id of ids) {
10
+ if (id !== "") unique.add(id);
11
+ }
12
+ return unique.size;
13
+ }
@@ -0,0 +1,21 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { hasDuplicateMergeIds } from "./merge-dup.js";
5
+
6
+ test("none is false", () => {
7
+ assert.equal(hasDuplicateMergeIds([]), false);
8
+ assert.equal(hasDuplicateMergeIds(["alpha"]), false);
9
+ assert.equal(hasDuplicateMergeIds(["alpha", "beta"]), false);
10
+ });
11
+
12
+ test("duplicate non-empty id is true", () => {
13
+ assert.equal(hasDuplicateMergeIds(["alpha", "beta", "alpha"]), true);
14
+ });
15
+
16
+ test("empty strings are ignored and input is not mutated", () => {
17
+ const ids = ["alpha", "", "", "beta"];
18
+ assert.equal(hasDuplicateMergeIds(ids), false);
19
+ assert.equal(hasDuplicateMergeIds(["", "", "alpha", "", "alpha"]), true);
20
+ assert.deepEqual(ids, ["alpha", "", "", "beta"]);
21
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Merge-id duplicate detector (issue #2330 leftover).
3
+ *
4
+ * True if any non-empty id appears twice. Empty strings are ignored.
5
+ * Does not mutate the input list.
6
+ */
7
+
8
+ export function hasDuplicateMergeIds(ids: readonly string[]): boolean {
9
+ const seen = new Set<string>();
10
+ for (const id of ids) {
11
+ if (id === "") continue;
12
+ if (seen.has(id)) return true;
13
+ seen.add(id);
14
+ }
15
+ return false;
16
+ }
@@ -0,0 +1,22 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { rejectEmptyMergeIds } from "./merge-empty.js";
5
+
6
+ test("empty set is empty_set", () => {
7
+ assert.deepEqual(rejectEmptyMergeIds([]), { ok: false, error: "empty_set" });
8
+ });
9
+
10
+ test("empty id is empty_id", () => {
11
+ assert.deepEqual(rejectEmptyMergeIds([""]), { ok: false, error: "empty_id" });
12
+ assert.deepEqual(rejectEmptyMergeIds(["alpha", ""]), { ok: false, error: "empty_id" });
13
+ assert.deepEqual(rejectEmptyMergeIds(["", "alpha"]), { ok: false, error: "empty_id" });
14
+ });
15
+
16
+ test("ok returns the same ids in order and does not mutate", () => {
17
+ const ids = ["zeta", "alpha", "beta"];
18
+ const result = rejectEmptyMergeIds(ids);
19
+ assert.deepEqual(result, { ok: true, ids: ["zeta", "alpha", "beta"] });
20
+ assert.equal(result.ok && result.ids, ids);
21
+ assert.deepEqual(ids, ["zeta", "alpha", "beta"]);
22
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Merge-id empty-set guard (issue #2330 leftover).
3
+ *
4
+ * Empty list → empty_set. Any empty string → empty_id.
5
+ * Otherwise returns the same ids in order. Does not mutate input.
6
+ */
7
+
8
+ export type RejectEmptyMergeIdsResult =
9
+ | { ok: true; ids: readonly string[] }
10
+ | { ok: false; error: "empty_set" | "empty_id" };
11
+
12
+ export function rejectEmptyMergeIds(ids: readonly string[]): RejectEmptyMergeIdsResult {
13
+ if (ids.length === 0) return { ok: false, error: "empty_set" };
14
+ for (const id of ids) {
15
+ if (id === "") return { ok: false, error: "empty_id" };
16
+ }
17
+ return { ok: true, ids };
18
+ }