@remnic/core 9.3.613 → 9.3.614

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 (376) hide show
  1. package/dist/access-cli.js +58 -57
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +4 -2
  4. package/dist/access-http.js +22 -22
  5. package/dist/access-mcp.d.ts +9 -2
  6. package/dist/access-mcp.js +19 -19
  7. package/dist/access-schema.d.ts +12 -12
  8. package/dist/access-schema.js +3 -3
  9. package/dist/{access-service-D2J9dh_9.d.ts → access-service-DGG_2xPK.d.ts} +1 -1
  10. package/dist/access-service.d.ts +2 -2
  11. package/dist/access-service.js +16 -16
  12. package/dist/active-recall.js +20 -3
  13. package/dist/active-recall.js.map +1 -1
  14. package/dist/adapters/index.js +4 -4
  15. package/dist/adapters/registry.js +2 -2
  16. package/dist/behavior-learner.js +2 -3
  17. package/dist/behavior-learner.js.map +1 -1
  18. package/dist/bootstrap.d.ts +1 -1
  19. package/dist/briefing.js +3 -3
  20. package/dist/buffer.d.ts +1 -1
  21. package/dist/buffer.js +1 -1
  22. package/dist/calibration.d.ts +5 -2
  23. package/dist/calibration.js +7 -5
  24. package/dist/calibration.js.map +1 -1
  25. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
  26. package/dist/causal-consolidation.d.ts +8 -2
  27. package/dist/causal-consolidation.js +13 -11
  28. package/dist/causal-consolidation.js.map +1 -1
  29. package/dist/{chunk-3BP57I6J.js → chunk-2F6NP3NT.js} +2 -1
  30. package/dist/{chunk-3BP57I6J.js.map → chunk-2F6NP3NT.js.map} +1 -1
  31. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  32. package/dist/{chunk-HSVJGWYS.js → chunk-2ROPI5OE.js} +2 -2
  33. package/dist/{chunk-C4SQJZAF.js → chunk-2SGJY2UY.js} +6 -3
  34. package/dist/chunk-2SGJY2UY.js.map +1 -0
  35. package/dist/{chunk-ZDTVJXIP.js → chunk-3MAONBX3.js} +13 -5
  36. package/dist/chunk-3MAONBX3.js.map +1 -0
  37. package/dist/{chunk-G3Z3QEF5.js → chunk-3PY7VHV7.js} +2 -2
  38. package/dist/chunk-3PY7VHV7.js.map +1 -0
  39. package/dist/{chunk-CF3ZF2YU.js → chunk-3QSU4NFF.js} +3 -3
  40. package/dist/{chunk-AJA46VX5.js → chunk-3T74IZB3.js} +11 -2
  41. package/dist/chunk-3T74IZB3.js.map +1 -0
  42. package/dist/{chunk-KVEVLBKC.js → chunk-4HFJQCJZ.js} +13 -8
  43. package/dist/chunk-4HFJQCJZ.js.map +1 -0
  44. package/dist/{chunk-KGK2QKWL.js → chunk-4R4KTDIE.js} +1 -1
  45. package/dist/chunk-4R4KTDIE.js.map +1 -0
  46. package/dist/{chunk-OI27U2HT.js → chunk-5BTCT236.js} +2 -2
  47. package/dist/{chunk-CO7ZO4TU.js → chunk-5VDJMYTF.js} +2 -2
  48. package/dist/{chunk-BFBF3XEF.js → chunk-6BDVBBBY.js} +33 -25
  49. package/dist/{chunk-BFBF3XEF.js.map → chunk-6BDVBBBY.js.map} +1 -1
  50. package/dist/{chunk-EAZGEEG2.js → chunk-6L46YAEZ.js} +45 -9
  51. package/dist/chunk-6L46YAEZ.js.map +1 -0
  52. package/dist/{chunk-YFS5OEKO.js → chunk-7MLB4NCL.js} +2 -2
  53. package/dist/{chunk-IOTENEVL.js → chunk-7YQFWOF7.js} +57 -50
  54. package/dist/chunk-7YQFWOF7.js.map +1 -0
  55. package/dist/{chunk-2QANQKSQ.js → chunk-ADNZVFXG.js} +15 -15
  56. package/dist/{chunk-LZ3VEOU5.js → chunk-AL4RAJL5.js} +22 -5
  57. package/dist/chunk-AL4RAJL5.js.map +1 -0
  58. package/dist/{chunk-557IAFPD.js → chunk-APRRL26Q.js} +2 -2
  59. package/dist/{chunk-QDDHYAKV.js → chunk-AZDOWD2L.js} +2 -2
  60. package/dist/{chunk-TH67Q46T.js → chunk-B6FDZPCF.js} +17 -9
  61. package/dist/chunk-B6FDZPCF.js.map +1 -0
  62. package/dist/{chunk-MLT75J5S.js → chunk-B6SU7YSE.js} +3 -3
  63. package/dist/{chunk-FXKPZ3H6.js → chunk-BPSGLMQ4.js} +2 -2
  64. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  65. package/dist/{chunk-NOMEVTUD.js → chunk-C6C7XVKG.js} +5 -4
  66. package/dist/chunk-C6C7XVKG.js.map +1 -0
  67. package/dist/{chunk-XKIQZXUB.js → chunk-CI7RKSRE.js} +7 -1
  68. package/dist/chunk-CI7RKSRE.js.map +1 -0
  69. package/dist/{chunk-IK34DVAC.js → chunk-CIOMS6DI.js} +2 -2
  70. package/dist/{chunk-2I5JGH3M.js → chunk-CYEPCZN5.js} +2 -2
  71. package/dist/{chunk-2I5JGH3M.js.map → chunk-CYEPCZN5.js.map} +1 -1
  72. package/dist/{chunk-JHMFYY7L.js → chunk-DCGT4FPP.js} +13 -5
  73. package/dist/chunk-DCGT4FPP.js.map +1 -0
  74. package/dist/{chunk-7DZRO2DC.js → chunk-DEPRLVLK.js} +2 -2
  75. package/dist/{chunk-CSKLPDN6.js → chunk-DEVUWMME.js} +52 -19
  76. package/dist/chunk-DEVUWMME.js.map +1 -0
  77. package/dist/{chunk-DHGSZ3UD.js → chunk-DGNQRNLL.js} +2 -2
  78. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  79. package/dist/chunk-FAV25DUZ.js +12 -0
  80. package/dist/chunk-FAV25DUZ.js.map +1 -0
  81. package/dist/{chunk-ETUPBUHB.js → chunk-GDASG7NC.js} +2 -2
  82. package/dist/{chunk-L227SKTB.js → chunk-GDB4J2H3.js} +17 -1
  83. package/dist/chunk-GDB4J2H3.js.map +1 -0
  84. package/dist/{chunk-IP73YCZP.js → chunk-GLPBYIXN.js} +4 -2
  85. package/dist/chunk-GLPBYIXN.js.map +1 -0
  86. package/dist/{chunk-4HP7HIE3.js → chunk-HP5FMB6L.js} +2 -2
  87. package/dist/{chunk-EVZFIAPG.js → chunk-IBTZEBUD.js} +23 -10
  88. package/dist/chunk-IBTZEBUD.js.map +1 -0
  89. package/dist/{chunk-DOX2CG6Y.js → chunk-IEUU7O4F.js} +2 -2
  90. package/dist/{chunk-JNANKJLN.js → chunk-JOASJWQR.js} +2 -2
  91. package/dist/chunk-JOASJWQR.js.map +1 -0
  92. package/dist/{chunk-WSGF57U2.js → chunk-JQDZQ4TB.js} +2 -2
  93. package/dist/{chunk-HINSGUA7.js → chunk-KBL3JJR6.js} +9 -13
  94. package/dist/chunk-KBL3JJR6.js.map +1 -0
  95. package/dist/{chunk-W7L6HXUC.js → chunk-LXOM6IQU.js} +2 -2
  96. package/dist/{chunk-G6R5UD3Q.js → chunk-MGN7VHWQ.js} +42 -1
  97. package/dist/{chunk-G6R5UD3Q.js.map → chunk-MGN7VHWQ.js.map} +1 -1
  98. package/dist/{chunk-DLJ4IR6M.js → chunk-MHQC2WU2.js} +2 -2
  99. package/dist/chunk-MHQC2WU2.js.map +1 -0
  100. package/dist/{chunk-6JGNHWCI.js → chunk-OBIRVF36.js} +3 -3
  101. package/dist/{chunk-CHCA44C3.js → chunk-ODPLEWB6.js} +3 -3
  102. package/dist/chunk-ODPLEWB6.js.map +1 -0
  103. package/dist/{chunk-HENLZHIT.js → chunk-OIF36KGD.js} +7 -4
  104. package/dist/chunk-OIF36KGD.js.map +1 -0
  105. package/dist/{chunk-GUPISBV2.js → chunk-PP2JH3GP.js} +2 -2
  106. package/dist/{chunk-OXJBNGBK.js → chunk-PSUB67YB.js} +2 -2
  107. package/dist/{chunk-UWY7GIVS.js → chunk-PYIFUBRK.js} +45 -13
  108. package/dist/chunk-PYIFUBRK.js.map +1 -0
  109. package/dist/{chunk-KIB7SDIJ.js → chunk-Q6YIJGXJ.js} +2 -2
  110. package/dist/{chunk-PPPZY2EU.js → chunk-QEMCQFDW.js} +2 -2
  111. package/dist/{chunk-ZT3EGNLR.js → chunk-QPD426WT.js} +2 -2
  112. package/dist/{chunk-RLV3PQGH.js → chunk-QVO4YOB7.js} +6 -6
  113. package/dist/{chunk-GMAG2HS4.js → chunk-RG3LBSGH.js} +46 -9
  114. package/dist/chunk-RG3LBSGH.js.map +1 -0
  115. package/dist/{chunk-XSWKORGM.js → chunk-S53OYO3F.js} +3 -1
  116. package/dist/chunk-S53OYO3F.js.map +1 -0
  117. package/dist/{chunk-YCN4BVDK.js → chunk-SCPFRKIT.js} +4 -2
  118. package/dist/chunk-SCPFRKIT.js.map +1 -0
  119. package/dist/{chunk-HJNQQICM.js → chunk-T5XWMMU2.js} +107 -50
  120. package/dist/chunk-T5XWMMU2.js.map +1 -0
  121. package/dist/{chunk-NZPF2SYV.js → chunk-T7N6KQGS.js} +138 -5
  122. package/dist/chunk-T7N6KQGS.js.map +1 -0
  123. package/dist/{chunk-VJXSUAO7.js → chunk-TNOWU6RP.js} +13 -10
  124. package/dist/chunk-TNOWU6RP.js.map +1 -0
  125. package/dist/{chunk-PCI747N2.js → chunk-TZVQQTG4.js} +48 -19
  126. package/dist/chunk-TZVQQTG4.js.map +1 -0
  127. package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
  128. package/dist/{chunk-IK7DCC5H.js → chunk-VMGLYN42.js} +2 -2
  129. package/dist/{chunk-5RPTH6AU.js → chunk-VPGUMLBA.js} +8 -7
  130. package/dist/chunk-VPGUMLBA.js.map +1 -0
  131. package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
  132. package/dist/chunk-WB3LYXC5.js.map +1 -0
  133. package/dist/{chunk-NSKYFGDL.js → chunk-X4QQB7O6.js} +2 -2
  134. package/dist/{chunk-HPWVAEET.js → chunk-X6IRLNOO.js} +3 -7
  135. package/dist/chunk-X6IRLNOO.js.map +1 -0
  136. package/dist/{chunk-46GJIW5M.js → chunk-XAZOWLW4.js} +5 -5
  137. package/dist/{chunk-46GJIW5M.js.map → chunk-XAZOWLW4.js.map} +1 -1
  138. package/dist/{chunk-XPSVGJYA.js → chunk-YRMKDTKF.js} +12 -9
  139. package/dist/chunk-YRMKDTKF.js.map +1 -0
  140. package/dist/{chunk-6ZZP4EJF.js → chunk-ZJR7VG5L.js} +3 -3
  141. package/dist/{chunk-6ZZP4EJF.js.map → chunk-ZJR7VG5L.js.map} +1 -1
  142. package/dist/{cli-OrfKXNU4.d.ts → cli-DWeu7eTY.d.ts} +6 -2
  143. package/dist/cli.d.ts +3 -3
  144. package/dist/cli.js +60 -59
  145. package/dist/compounding/engine.js +3 -3
  146. package/dist/compounding/preference-consolidator.js +39 -11
  147. package/dist/compounding/preference-consolidator.js.map +1 -1
  148. package/dist/config.js +1 -1
  149. package/dist/connectors/codex-materialize-runner.js +3 -3
  150. package/dist/connectors/index.js +3 -3
  151. package/dist/consolidation-provenance-check.js +1 -1
  152. package/dist/contradiction/index.js +4 -4
  153. package/dist/conversation-index/backend.js +2 -2
  154. package/dist/conversation-index/indexer.js +1 -1
  155. package/dist/cross-namespace-budget.js +1 -1
  156. package/dist/enrichment/index.js +1 -1
  157. package/dist/entity-retrieval.js +3 -3
  158. package/dist/evals.js +1 -1
  159. package/dist/explicit-capture.d.ts +1 -1
  160. package/dist/extraction-judge.js +8 -1
  161. package/dist/extraction.js +2 -2
  162. package/dist/fallback-llm.d.ts +23 -6
  163. package/dist/fallback-llm.js +5 -3
  164. package/dist/{first-start-migration-GYJWIH36.js → first-start-migration-FF7YFGRP.js} +6 -6
  165. package/dist/index.d.ts +3 -3
  166. package/dist/index.js +94 -93
  167. package/dist/index.js.map +1 -1
  168. package/dist/lcm/archive.js +2 -2
  169. package/dist/lcm/engine.js +5 -5
  170. package/dist/lcm/index.js +7 -7
  171. package/dist/lcm/summarizer.js +3 -3
  172. package/dist/maintenance/memory-governance-cron.d.ts +6 -4
  173. package/dist/maintenance/memory-governance-cron.js +1 -1
  174. package/dist/maintenance/memory-governance.js +3 -3
  175. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  176. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  177. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  178. package/dist/mcp-memory-inspector-app.js +1 -1
  179. package/dist/migrate/from-engram.js +1 -1
  180. package/dist/namespaces/migrate.js +16 -15
  181. package/dist/namespaces/search.js +12 -11
  182. package/dist/namespaces/storage.js +3 -3
  183. package/dist/network/webdav.d.ts +2 -0
  184. package/dist/network/webdav.js +1 -1
  185. package/dist/objective-state-writers.js +2 -2
  186. package/dist/operator-toolkit.d.ts +3 -1
  187. package/dist/operator-toolkit.js +21 -20
  188. package/dist/{orchestrator-DTRQG75J.d.ts → orchestrator-CqWOjfgl.d.ts} +46 -3
  189. package/dist/orchestrator.d.ts +1 -1
  190. package/dist/orchestrator.js +47 -44
  191. package/dist/patterns-cli.js +1 -1
  192. package/dist/qmd-recall-cache.d.ts +2 -0
  193. package/dist/qmd-recall-cache.js +1 -1
  194. package/dist/qmd.d.ts +37 -2
  195. package/dist/qmd.js +4 -1
  196. package/dist/recall-explain-renderer.js +3 -3
  197. package/dist/recall-planner-llm.d.ts +57 -0
  198. package/dist/recall-planner-llm.js +167 -0
  199. package/dist/recall-planner-llm.js.map +1 -0
  200. package/dist/recall-xray-cli.js +4 -4
  201. package/dist/recall-xray-renderer.js +3 -3
  202. package/dist/recall-xray.js +2 -2
  203. package/dist/resume-bundles.js +2 -2
  204. package/dist/retrieval-agents.js +2 -2
  205. package/dist/routing/store.js +1 -1
  206. package/dist/schemas.d.ts +22 -22
  207. package/dist/search/factory.js +11 -10
  208. package/dist/search/index.js +11 -10
  209. package/dist/search/lancedb-backend.d.ts +1 -1
  210. package/dist/search/lancedb-backend.js +3 -2
  211. package/dist/search/meilisearch-backend.d.ts +1 -1
  212. package/dist/search/meilisearch-backend.js +3 -2
  213. package/dist/search/noop-backend.d.ts +1 -1
  214. package/dist/search/noop-backend.js +1 -1
  215. package/dist/search/orama-backend.d.ts +1 -1
  216. package/dist/search/orama-backend.js +3 -2
  217. package/dist/search/port.d.ts +6 -1
  218. package/dist/search/port.js +7 -0
  219. package/dist/search/remote-backend.d.ts +1 -1
  220. package/dist/search/remote-backend.js +1 -1
  221. package/dist/semantic-consolidation.js +4 -4
  222. package/dist/semantic-rule-promotion.js +3 -3
  223. package/dist/semantic-rule-verifier.js +3 -3
  224. package/dist/session-observer-state.js +1 -1
  225. package/dist/storage.js +2 -2
  226. package/dist/summarizer.js +2 -2
  227. package/dist/temporal-index.js +1 -1
  228. package/dist/{tier-stats-SKML2OSF.js → tier-stats-3LYQ3VV5.js} +3 -3
  229. package/dist/transfer/backup.js +2 -2
  230. package/dist/transfer/capsule-export.js +2 -2
  231. package/dist/transfer/capsule-import.js +2 -2
  232. package/dist/transfer/export-sqlite.js +1 -1
  233. package/dist/transfer/types.d.ts +12 -12
  234. package/dist/types.d.ts +32 -0
  235. package/dist/types.js +1 -1
  236. package/dist/utility-learner.js +1 -1
  237. package/dist/utility-runtime.js +2 -2
  238. package/dist/verified-recall.js +3 -3
  239. package/dist/work/board.js +2 -2
  240. package/dist/work/storage.d.ts +2 -0
  241. package/dist/work/storage.js +1 -1
  242. package/package.json +1 -1
  243. package/src/access-http.ts +3 -0
  244. package/src/access-mcp.test.ts +51 -0
  245. package/src/access-mcp.ts +26 -5
  246. package/src/active-recall.test.ts +40 -0
  247. package/src/active-recall.ts +19 -2
  248. package/src/behavior-learner.ts +5 -3
  249. package/src/buffer-session.test.ts +58 -0
  250. package/src/buffer-surprise-trigger.test.ts +4 -18
  251. package/src/buffer.ts +39 -11
  252. package/src/calibration.ts +10 -4
  253. package/src/causal-consolidation.test.ts +47 -2
  254. package/src/causal-consolidation.ts +13 -9
  255. package/src/cli.ts +19 -4
  256. package/src/compounding/engine.ts +2 -0
  257. package/src/compounding/preference-consolidator.test.ts +292 -0
  258. package/src/compounding/preference-consolidator.ts +55 -19
  259. package/src/config.test.ts +213 -0
  260. package/src/config.ts +175 -4
  261. package/src/connectors/codex-materialize-runner.ts +7 -4
  262. package/src/consolidation-provenance-check.ts +24 -5
  263. package/src/conversation-index/indexer.test.ts +22 -0
  264. package/src/conversation-index/indexer.ts +7 -3
  265. package/src/cross-namespace-budget.test.ts +44 -21
  266. package/src/cross-namespace-budget.ts +2 -2
  267. package/src/enrichment/pipeline.ts +11 -16
  268. package/src/evals.ts +1 -1
  269. package/src/extraction-judge-chain.test.ts +55 -0
  270. package/src/extraction-judge.ts +7 -9
  271. package/src/extraction.ts +16 -5
  272. package/src/fallback-llm.test.ts +600 -1
  273. package/src/fallback-llm.ts +91 -22
  274. package/src/maintenance/memory-governance-cron.ts +39 -29
  275. package/src/mcp-memory-inspector-app.ts +54 -12
  276. package/src/message-parts/index.ts +6 -0
  277. package/src/message-parts/message-parts.test.ts +30 -0
  278. package/src/migrate/from-engram.ts +19 -5
  279. package/src/namespaces/search.test.ts +15 -2
  280. package/src/namespaces/search.ts +1 -1
  281. package/src/network/webdav.ts +61 -21
  282. package/src/operator-toolkit.ts +6 -2
  283. package/src/orchestrator.ts +173 -20
  284. package/src/qmd-client.test.ts +85 -0
  285. package/src/qmd-recall-cache.test.ts +16 -0
  286. package/src/qmd-recall-cache.ts +7 -0
  287. package/src/qmd.test.ts +54 -0
  288. package/src/qmd.ts +119 -19
  289. package/src/recall-planner-llm.test.ts +224 -0
  290. package/src/recall-planner-llm.ts +289 -0
  291. package/src/routing/store.ts +4 -8
  292. package/src/search/factory.ts +3 -0
  293. package/src/search/lancedb-backend.ts +15 -3
  294. package/src/search/meilisearch-backend.ts +70 -7
  295. package/src/search/noop-backend.ts +5 -1
  296. package/src/search/orama-backend.ts +15 -3
  297. package/src/search/port.ts +15 -0
  298. package/src/search/remote-backend.ts +5 -1
  299. package/src/session-observer-state.ts +1 -1
  300. package/src/summarizer.ts +3 -3
  301. package/src/temporal-index.test.ts +18 -0
  302. package/src/temporal-index.ts +45 -0
  303. package/src/training-export/cli-date-validation.test.ts +36 -0
  304. package/src/training-export/date-parse.ts +21 -2
  305. package/src/transfer/export-sqlite.ts +3 -0
  306. package/src/types.ts +35 -0
  307. package/src/utility-learner.ts +1 -0
  308. package/src/work/storage.ts +23 -0
  309. package/dist/chunk-5RPTH6AU.js.map +0 -1
  310. package/dist/chunk-AJA46VX5.js.map +0 -1
  311. package/dist/chunk-C4SQJZAF.js.map +0 -1
  312. package/dist/chunk-CHCA44C3.js.map +0 -1
  313. package/dist/chunk-CSKLPDN6.js.map +0 -1
  314. package/dist/chunk-DLJ4IR6M.js.map +0 -1
  315. package/dist/chunk-EAZGEEG2.js.map +0 -1
  316. package/dist/chunk-EVZFIAPG.js.map +0 -1
  317. package/dist/chunk-G3Z3QEF5.js.map +0 -1
  318. package/dist/chunk-GMAG2HS4.js.map +0 -1
  319. package/dist/chunk-HENLZHIT.js.map +0 -1
  320. package/dist/chunk-HINSGUA7.js.map +0 -1
  321. package/dist/chunk-HJNQQICM.js.map +0 -1
  322. package/dist/chunk-HPWVAEET.js.map +0 -1
  323. package/dist/chunk-IOTENEVL.js.map +0 -1
  324. package/dist/chunk-IP73YCZP.js.map +0 -1
  325. package/dist/chunk-JHMFYY7L.js.map +0 -1
  326. package/dist/chunk-JNANKJLN.js.map +0 -1
  327. package/dist/chunk-KGK2QKWL.js.map +0 -1
  328. package/dist/chunk-KM2A35EO.js.map +0 -1
  329. package/dist/chunk-KVEVLBKC.js.map +0 -1
  330. package/dist/chunk-L227SKTB.js.map +0 -1
  331. package/dist/chunk-LZ3VEOU5.js.map +0 -1
  332. package/dist/chunk-NOMEVTUD.js.map +0 -1
  333. package/dist/chunk-NZPF2SYV.js.map +0 -1
  334. package/dist/chunk-PCI747N2.js.map +0 -1
  335. package/dist/chunk-TH67Q46T.js.map +0 -1
  336. package/dist/chunk-UWY7GIVS.js.map +0 -1
  337. package/dist/chunk-VJXSUAO7.js.map +0 -1
  338. package/dist/chunk-XKIQZXUB.js.map +0 -1
  339. package/dist/chunk-XPSVGJYA.js.map +0 -1
  340. package/dist/chunk-XSWKORGM.js.map +0 -1
  341. package/dist/chunk-YCN4BVDK.js.map +0 -1
  342. package/dist/chunk-ZDTVJXIP.js.map +0 -1
  343. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
  344. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  345. /package/dist/{chunk-HSVJGWYS.js.map → chunk-2ROPI5OE.js.map} +0 -0
  346. /package/dist/{chunk-CF3ZF2YU.js.map → chunk-3QSU4NFF.js.map} +0 -0
  347. /package/dist/{chunk-OI27U2HT.js.map → chunk-5BTCT236.js.map} +0 -0
  348. /package/dist/{chunk-CO7ZO4TU.js.map → chunk-5VDJMYTF.js.map} +0 -0
  349. /package/dist/{chunk-YFS5OEKO.js.map → chunk-7MLB4NCL.js.map} +0 -0
  350. /package/dist/{chunk-2QANQKSQ.js.map → chunk-ADNZVFXG.js.map} +0 -0
  351. /package/dist/{chunk-557IAFPD.js.map → chunk-APRRL26Q.js.map} +0 -0
  352. /package/dist/{chunk-QDDHYAKV.js.map → chunk-AZDOWD2L.js.map} +0 -0
  353. /package/dist/{chunk-MLT75J5S.js.map → chunk-B6SU7YSE.js.map} +0 -0
  354. /package/dist/{chunk-FXKPZ3H6.js.map → chunk-BPSGLMQ4.js.map} +0 -0
  355. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  356. /package/dist/{chunk-IK34DVAC.js.map → chunk-CIOMS6DI.js.map} +0 -0
  357. /package/dist/{chunk-7DZRO2DC.js.map → chunk-DEPRLVLK.js.map} +0 -0
  358. /package/dist/{chunk-DHGSZ3UD.js.map → chunk-DGNQRNLL.js.map} +0 -0
  359. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  360. /package/dist/{chunk-ETUPBUHB.js.map → chunk-GDASG7NC.js.map} +0 -0
  361. /package/dist/{chunk-4HP7HIE3.js.map → chunk-HP5FMB6L.js.map} +0 -0
  362. /package/dist/{chunk-DOX2CG6Y.js.map → chunk-IEUU7O4F.js.map} +0 -0
  363. /package/dist/{chunk-WSGF57U2.js.map → chunk-JQDZQ4TB.js.map} +0 -0
  364. /package/dist/{chunk-W7L6HXUC.js.map → chunk-LXOM6IQU.js.map} +0 -0
  365. /package/dist/{chunk-6JGNHWCI.js.map → chunk-OBIRVF36.js.map} +0 -0
  366. /package/dist/{chunk-GUPISBV2.js.map → chunk-PP2JH3GP.js.map} +0 -0
  367. /package/dist/{chunk-OXJBNGBK.js.map → chunk-PSUB67YB.js.map} +0 -0
  368. /package/dist/{chunk-KIB7SDIJ.js.map → chunk-Q6YIJGXJ.js.map} +0 -0
  369. /package/dist/{chunk-PPPZY2EU.js.map → chunk-QEMCQFDW.js.map} +0 -0
  370. /package/dist/{chunk-ZT3EGNLR.js.map → chunk-QPD426WT.js.map} +0 -0
  371. /package/dist/{chunk-RLV3PQGH.js.map → chunk-QVO4YOB7.js.map} +0 -0
  372. /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
  373. /package/dist/{chunk-IK7DCC5H.js.map → chunk-VMGLYN42.js.map} +0 -0
  374. /package/dist/{chunk-NSKYFGDL.js.map → chunk-X4QQB7O6.js.map} +0 -0
  375. /package/dist/{first-start-migration-GYJWIH36.js.map → first-start-migration-FF7YFGRP.js.map} +0 -0
  376. /package/dist/{tier-stats-SKML2OSF.js.map → tier-stats-3LYQ3VV5.js.map} +0 -0
@@ -30,6 +30,298 @@ test("consolidatePreferences preserves never-use facts as negative preferences",
30
30
  );
31
31
  });
32
32
 
33
+ test("consolidatePreferences preserves like facts as positive preferences", () => {
34
+ const result = consolidatePreferences([
35
+ memory("The user likes React for dashboards."),
36
+ ]);
37
+
38
+ assert.equal(result.preferences.length, 1);
39
+ assert.equal(
40
+ result.preferences[0]?.statement,
41
+ "The user prefers React for dashboards",
42
+ );
43
+ });
44
+
45
+ test("consolidatePreferences preserves like-to facts as infinitive preferences", () => {
46
+ const result = consolidatePreferences([
47
+ memory("The user would like to use React for dashboards."),
48
+ ]);
49
+
50
+ assert.equal(result.preferences.length, 1);
51
+ assert.equal(
52
+ result.preferences[0]?.statement,
53
+ "The user prefers to use React for dashboards",
54
+ );
55
+ });
56
+
57
+ test("consolidatePreferences does not join separate like-to follow-up sentences", () => {
58
+ const result = consolidatePreferences([
59
+ memory("The user would like to use React. They moved to Svelte."),
60
+ ]);
61
+
62
+ assert.equal(result.preferences.length, 1);
63
+ assert.equal(result.preferences[0]?.statement, "The user prefers to use React");
64
+ });
65
+
66
+ test("consolidatePreferences preserves love facts as positive preferences", () => {
67
+ const result = consolidatePreferences([
68
+ memory("The user loves Svelte for prototypes."),
69
+ ]);
70
+
71
+ assert.equal(result.preferences.length, 1);
72
+ assert.equal(
73
+ result.preferences[0]?.statement,
74
+ "The user prefers Svelte for prototypes",
75
+ );
76
+ });
77
+
78
+ test("consolidatePreferences preserves does-not-love facts as negative preferences", () => {
79
+ const result = consolidatePreferences([
80
+ memory("The user does not love React for dashboards."),
81
+ ]);
82
+
83
+ assert.equal(result.preferences.length, 1);
84
+ assert.equal(
85
+ result.preferences[0]?.statement,
86
+ "The user would not prefer React for dashboards",
87
+ );
88
+ });
89
+
90
+ test("consolidatePreferences preserves did-not-love facts as negative preferences", () => {
91
+ const result = consolidatePreferences([
92
+ memory("The user did not love React for dashboards."),
93
+ ]);
94
+
95
+ assert.equal(result.preferences.length, 1);
96
+ assert.equal(
97
+ result.preferences[0]?.statement,
98
+ "The user would not prefer React for dashboards",
99
+ );
100
+ });
101
+
102
+ test("consolidatePreferences preserves would-not-love facts as negative preferences", () => {
103
+ const result = consolidatePreferences([
104
+ memory("The user would not love React for dashboards."),
105
+ ]);
106
+
107
+ assert.equal(result.preferences.length, 1);
108
+ assert.equal(
109
+ result.preferences[0]?.statement,
110
+ "The user would not prefer React for dashboards",
111
+ );
112
+ });
113
+
114
+ test("consolidatePreferences preserves would-not-like-to facts as negative preferences", () => {
115
+ const result = consolidatePreferences([
116
+ memory("The user would not like to use React for dashboards."),
117
+ ]);
118
+
119
+ assert.equal(result.preferences.length, 1);
120
+ assert.equal(
121
+ result.preferences[0]?.statement,
122
+ "The user would not prefer to use React for dashboards",
123
+ );
124
+ });
125
+
126
+ test("consolidatePreferences preserves no-longer-loves facts as negative preferences", () => {
127
+ const result = consolidatePreferences([
128
+ memory("The user no longer loves React."),
129
+ ]);
130
+
131
+ assert.equal(result.preferences.length, 1);
132
+ assert.equal(result.preferences[0]?.statement, "The user would not prefer React");
133
+ });
134
+
135
+ test("consolidatePreferences preserves disliked facts as negative preferences", () => {
136
+ const result = consolidatePreferences([
137
+ memory("The user disliked React for dashboards."),
138
+ ]);
139
+
140
+ assert.equal(result.preferences.length, 1);
141
+ assert.equal(
142
+ result.preferences[0]?.statement,
143
+ "The user would not prefer React for dashboards",
144
+ );
145
+ });
146
+
147
+ test("consolidatePreferences ignores double-negated dislike facts", () => {
148
+ const result = consolidatePreferences([
149
+ memory("The user does not dislike React."),
150
+ ]);
151
+
152
+ assert.equal(result.preferences.length, 0);
153
+ });
154
+
155
+ test("consolidatePreferences ignores repeated double-negated dislike facts", () => {
156
+ const result = consolidatePreferences([
157
+ memory("The user does not dislike React and does not dislike Svelte."),
158
+ ]);
159
+
160
+ assert.equal(result.preferences.length, 0);
161
+ });
162
+
163
+ test("consolidatePreferences preserves explicit preferences after double-negated aversion", () => {
164
+ const result = consolidatePreferences([
165
+ memory("The user does not dislike React, but prefers Svelte for dashboards."),
166
+ ]);
167
+
168
+ assert.equal(result.preferences.length, 1);
169
+ assert.equal(
170
+ result.preferences[0]?.statement,
171
+ "The user prefers Svelte for dashboards",
172
+ );
173
+ });
174
+
175
+ test("consolidatePreferences preserves replacement love clauses after withdrawn preferences", () => {
176
+ const result = consolidatePreferences([
177
+ memory("The user did not love React but now loves Svelte for dashboards."),
178
+ ]);
179
+
180
+ assert.equal(result.preferences.length, 1);
181
+ assert.equal(
182
+ result.preferences[0]?.statement,
183
+ "The user prefers Svelte for dashboards",
184
+ );
185
+ });
186
+
187
+ test("consolidatePreferences preserves short replacement love clauses after withdrawn preferences", () => {
188
+ const result = consolidatePreferences([
189
+ memory("The user did not love React but now loves Go."),
190
+ ]);
191
+
192
+ assert.equal(result.preferences.length, 1);
193
+ assert.equal(result.preferences[0]?.statement, "The user prefers Go");
194
+ });
195
+
196
+ test("consolidatePreferences preserves and-now replacement clauses after withdrawn preferences", () => {
197
+ const result = consolidatePreferences([
198
+ memory("The user did not love React, and now loves Svelte for dashboards."),
199
+ ]);
200
+
201
+ assert.equal(result.preferences.length, 1);
202
+ assert.equal(
203
+ result.preferences[0]?.statement,
204
+ "The user prefers Svelte for dashboards",
205
+ );
206
+ });
207
+
208
+ test("consolidatePreferences preserves plain-and replacement clauses after withdrawn preferences", () => {
209
+ const result = consolidatePreferences([
210
+ memory("The user did not love React and loves Svelte for dashboards."),
211
+ ]);
212
+
213
+ assert.equal(result.preferences.length, 1);
214
+ assert.equal(
215
+ result.preferences[0]?.statement,
216
+ "The user prefers Svelte for dashboards",
217
+ );
218
+ });
219
+
220
+ test("consolidatePreferences keeps compound negated-use clauses negative", () => {
221
+ const result = consolidatePreferences([
222
+ memory("The user does not use React and uses Svelte for dashboards."),
223
+ ]);
224
+
225
+ assert.equal(result.preferences.length, 1);
226
+ assert.equal(result.preferences[0]?.statement, "The user would not prefer React");
227
+ });
228
+
229
+ test("consolidatePreferences preserves replacement like-to clauses after withdrawn preferences", () => {
230
+ const result = consolidatePreferences([
231
+ memory("The user did not love React but like to use Svelte for dashboards."),
232
+ ]);
233
+
234
+ assert.equal(result.preferences.length, 1);
235
+ assert.equal(
236
+ result.preferences[0]?.statement,
237
+ "The user prefers to use Svelte for dashboards",
238
+ );
239
+ });
240
+
241
+ test("consolidatePreferences preserves replacement enjoying clauses after withdrawn preferences", () => {
242
+ const result = consolidatePreferences([
243
+ memory("The user did not love React but enjoying Svelte dashboard work."),
244
+ ]);
245
+
246
+ assert.equal(result.preferences.length, 1);
247
+ assert.equal(
248
+ result.preferences[0]?.statement,
249
+ "The user prefers: enjoying Svelte dashboard work",
250
+ );
251
+ });
252
+
253
+ test("consolidatePreferences preserves replacement use clauses after withdrawn preferences", () => {
254
+ const result = consolidatePreferences([
255
+ memory("The user did not love React but now uses Svelte for dashboards."),
256
+ ]);
257
+
258
+ assert.equal(result.preferences.length, 1);
259
+ assert.equal(
260
+ result.preferences[0]?.statement,
261
+ "The user prefers to use Svelte for dashboards",
262
+ );
263
+ });
264
+
265
+ test("consolidatePreferences still admits preference stem variants in facts", () => {
266
+ const result = consolidatePreferences([
267
+ memory("The user is enjoying Svelte specialization work."),
268
+ ]);
269
+
270
+ assert.equal(result.preferences.length, 1);
271
+ assert.equal(
272
+ result.preferences[0]?.statement,
273
+ "The user is enjoying Svelte specialization work",
274
+ );
275
+ });
276
+
277
+ test("consolidatePreferences ignores like analogies in facts", () => {
278
+ const result = consolidatePreferences([
279
+ memory("The user said Next.js is like React."),
280
+ ]);
281
+
282
+ assert.equal(result.preferences.length, 0);
283
+ });
284
+
285
+ test("consolidatePreferences ignores stale past-tense liked facts", () => {
286
+ const result = consolidatePreferences([
287
+ memory("The user liked Angular before switching to React."),
288
+ ]);
289
+
290
+ assert.equal(result.preferences.length, 0);
291
+ });
292
+
293
+ test("consolidatePreferences preserves multiline use-for facts", () => {
294
+ const result = consolidatePreferences([
295
+ memory("The user uses React\nfor dashboards."),
296
+ ]);
297
+
298
+ assert.equal(result.preferences.length, 1);
299
+ assert.equal(
300
+ result.preferences[0]?.statement,
301
+ "The user prefers to use React for dashboards",
302
+ );
303
+ });
304
+
305
+ test("consolidatePreferences does not join separate use and movement sentences", () => {
306
+ const result = consolidatePreferences([
307
+ memory("The user uses React.\nThey moved to Svelte."),
308
+ ]);
309
+
310
+ assert.equal(result.preferences.length, 0);
311
+ });
312
+
313
+ test("consolidatePreferences preserves a first use clause before later sentences", () => {
314
+ const result = consolidatePreferences([
315
+ memory("The user uses React for dashboards.\nThey moved to Svelte."),
316
+ ]);
317
+
318
+ assert.equal(result.preferences.length, 1);
319
+ assert.equal(
320
+ result.preferences[0]?.statement,
321
+ "The user prefers to use React for dashboards",
322
+ );
323
+ });
324
+
33
325
  test("consolidatePreferences preserves does-not-use facts as negative preferences", () => {
34
326
  const result = consolidatePreferences([
35
327
  memory("The user does not use React for dashboards."),
@@ -48,15 +48,24 @@ const PREFERENCE_EXTRACTORS: Array<{
48
48
  }> = [
49
49
  // "Avoids X" / negated use/work/code statements → negative preference
50
50
  {
51
- pattern: /(?:avoids?|dislikes?|hates?|does\s+not\s+like|doesn'?t\s+like|never\s+uses?|does\s+not\s+uses?|doesn'?t\s+uses?|does\s+not\s+use|doesn'?t\s+use|never\s+works?\s+(?:with|in)|does\s+not\s+works?\s+(?:with|in)|doesn'?t\s+works?\s+(?:with|in)|never\s+codes?\s+(?:in|with)|does\s+not\s+codes?\s+(?:in|with)|doesn'?t\s+codes?\s+(?:in|with)|refuses\s+to\s+(?:use|work\s+(?:with|in)|code\s+(?:in|with)))\s+(.+?)$/im,
51
+ pattern: /(?:avoids?|avoided|dislikes?|disliked|hates?|hated|does\s+not\s+(?:prefer|like|love|enjoy|favou?r)s?|doesn'?t\s+(?:prefer|like|love|enjoy|favou?r)s?|did\s+not\s+(?:prefer|like|love|enjoy|favou?r)s?|didn'?t\s+(?:prefer|like|love|enjoy|favou?r)s?|would\s+not\s+(?:prefer|like|love|enjoy|favou?r)s?|wouldn'?t\s+(?:prefer|like|love|enjoy|favou?r)s?|won'?t\s+(?:prefer|like|love|enjoy|favou?r)s?|never\s+(?:prefers?|likes?|loves?|enjoys?|favou?rs?)|no\s+longer\s+(?:prefers?|likes?|loves?|enjoys?|favou?rs?)|never\s+uses?|does\s+not\s+uses?|doesn'?t\s+uses?|does\s+not\s+use|doesn'?t\s+use|would\s+not\s+use|wouldn'?t\s+use|won'?t\s+use|never\s+works?\s+(?:with|in)|does\s+not\s+works?\s+(?:with|in)|doesn'?t\s+works?\s+(?:with|in)|would\s+not\s+works?\s+(?:with|in)|wouldn'?t\s+works?\s+(?:with|in)|won'?t\s+works?\s+(?:with|in)|never\s+codes?\s+(?:in|with)|does\s+not\s+codes?\s+(?:in|with)|doesn'?t\s+codes?\s+(?:in|with)|would\s+not\s+codes?\s+(?:in|with)|wouldn'?t\s+codes?\s+(?:in|with)|won'?t\s+codes?\s+(?:in|with)|refuses\s+to\s+(?:use|work\s+(?:with|in)|code\s+(?:in|with)))\s+([^.!?]+?)(?=\s+(?:(?:but|however|though|although|except|instead|rather)\b|and\s+(?:(?:now|currently|instead|rather)\b|(?=(?:prefer\w*|enjoy\w*|likes|like\s+to|loves?|loving|favou?r\w*|uses?|works?\s+(?:with|in)|codes?\s+(?:in|with)|interested\s+in|passionate\s+about|focused\s+on|specializ\w*)\b)))|[.!?]|$)/im,
52
52
  transform: (match) => {
53
53
  const subject = match[1].replace(/\.$/, "").trim();
54
54
  return `The user would not prefer ${subject}`;
55
55
  },
56
56
  },
57
+ // "Would like to X" / "like to X" → preference for doing X
58
+ {
59
+ pattern: /\blikes?\s+to\s+([^.!?]+?)(?:\s+(?:for|when|in|over)\s+([^.!?]+?))?(?=[.!?]|$)/im,
60
+ transform: (match) => {
61
+ const action = match[1].replace(/\.$/, "").trim();
62
+ const context = match[2] ? ` for ${match[2].replace(/\.$/, "").trim()}` : "";
63
+ return `The user prefers to ${action}${context}`;
64
+ },
65
+ },
57
66
  // Direct preference statements
58
67
  {
59
- pattern: /(?:prefers?|enjoys?|likes?|loves?|favou?rs?)\s+(.+?)(?:\s+(?:for|when|in|over)\s+(.+?))?$/im,
68
+ pattern: /\b(?:prefers?|enjoys?|likes|loves?|loving|favou?rs?)\s+(.+?)(?:\s+(?:for|when|in|over)\s+(.+?))?$/im,
60
69
  transform: (match) => {
61
70
  const subject = match[1].replace(/\.$/, "").trim();
62
71
  const context = match[2] ? ` for ${match[2].replace(/\.$/, "").trim()}` : "";
@@ -65,10 +74,12 @@ const PREFERENCE_EXTRACTORS: Array<{
65
74
  },
66
75
  // "Uses X for Y" → preference for X in Y context
67
76
  {
68
- pattern: /(?:uses?|works?\s+(?:with|in)|codes?\s+(?:in|with))\s+(.+?)(?:\s+(?:for|to|when)\s+(.+?))?$/im,
77
+ pattern: /(?:uses?|works?\s+(?:with|in)|codes?\s+(?:in|with))\s+([^.!?]+?)(?:\s+(?:for|to|when)\s+([^.!?]+?))?(?=[.!?]|$)/i,
69
78
  transform: (match) => {
70
- const tool = match[1].replace(/\.$/, "").trim();
71
- const context = match[2] ? ` for ${match[2].replace(/\.$/, "").trim()}` : "";
79
+ const tool = match[1].replace(/\s+/g, " ").replace(/\.$/, "").trim();
80
+ const context = match[2]
81
+ ? ` for ${match[2].replace(/\s+/g, " ").replace(/\.$/, "").trim()}`
82
+ : "";
72
83
  return `The user prefers to use ${tool}${context}`;
73
84
  },
74
85
  },
@@ -91,6 +102,26 @@ const PREFERENCE_EXTRACTORS: Array<{
91
102
  },
92
103
  ];
93
104
 
105
+ const FACT_PREFERENCE_LANGUAGE_PATTERN =
106
+ /\b(?:prefer\w*|enjoy\w*|likes|like\s+to|loves?|loving|favou?r\w*|avoid\w*|dislik\w*|hat(?:e|es|ed|ing)|interested\s+in|passionate\s+about|specializ\w*|go-to)\b/i;
107
+ const FACT_USE_CONTEXT_PATTERN =
108
+ /\b(?:uses?|works?\s+(?:with|in)|codes?\s+(?:in|with))\b[^.!?]+\b(?:for|to|when)\b/i;
109
+ const DOUBLE_NEGATED_AVERSION_PATTERN =
110
+ /\b(?:does\s+not|doesn'?t|did\s+not|didn'?t|never|no\s+longer)\s+(?:avoids?|avoided|dislikes?|disliked|hates?|hated)\b/i;
111
+ const DOUBLE_NEGATED_AVERSION_GLOBAL_PATTERN =
112
+ /\b(?:does\s+not|doesn'?t|did\s+not|didn'?t|never|no\s+longer)\s+(?:avoids?|avoided|dislikes?|disliked|hates?|hated)\b/gi;
113
+ const WITHDRAWN_PREFERENCE_PATTERN =
114
+ /\b(?:avoids?|avoided|dislikes?|disliked|hates?|hated|does\s+not\s+(?:prefer|like|love|enjoy|favou?r)s?|doesn'?t\s+(?:prefer|like|love|enjoy|favou?r)s?|did\s+not\s+(?:prefer|like|love|enjoy|favou?r)s?|didn'?t\s+(?:prefer|like|love|enjoy|favou?r)s?|would\s+not\s+(?:prefer|like|love|enjoy|favou?r)s?|wouldn'?t\s+(?:prefer|like|love|enjoy|favou?r)s?|won'?t\s+(?:prefer|like|love|enjoy|favou?r)s?|never\s+(?:prefers?|likes?|loves?|enjoys?|favou?rs?)|no\s+longer\s+(?:prefers?|likes?|loves?|enjoys?|favou?rs?))\b/i;
115
+ const REPLACEMENT_PREFERENCE_CLAUSE_PATTERN =
116
+ /\b(?:(?:but|however|though|although)\s+(?:now\s+|currently\s+|instead\s+|rather\s+)?|and\s+(?:now\s+|currently\s+|instead\s+|rather\s+)?)(?=(?:prefer\w*|enjoy\w*|likes|like\s+to|loves?|loving|favou?r\w*|uses?|works?\s+(?:with|in)|codes?\s+(?:in|with)|interested\s+in|passionate\s+about|focused\s+on|specializ\w*)\b)/i;
117
+
118
+ function replacementPreferenceClause(content: string): string | null {
119
+ const match = REPLACEMENT_PREFERENCE_CLAUSE_PATTERN.exec(content);
120
+ if (!match) return null;
121
+ const clause = content.slice(match.index + match[0].length).trim();
122
+ return clause.replace(/[.!?]+$/, "").trim().length > 0 ? clause : null;
123
+ }
124
+
94
125
  /**
95
126
  * Fallback: convert any preference/correction memory content into a
96
127
  * "The user prefers..." statement by prepending a suitable prefix.
@@ -184,17 +215,9 @@ export function consolidatePreferences(
184
215
  if (m.frontmatter.category !== "fact") return false;
185
216
  if (m.frontmatter.status && m.frontmatter.status !== "active") return false;
186
217
  if ((m.frontmatter.confidence ?? 0) < minConfidence) return false;
187
- const lower = m.content.toLowerCase();
188
218
  return (
189
- lower.includes("prefer") ||
190
- lower.includes("enjoy") ||
191
- lower.includes("like to") ||
192
- lower.includes("interested in") ||
193
- lower.includes("passionate about") ||
194
- lower.includes("specializ") ||
195
- lower.includes("favourite") ||
196
- lower.includes("favorite") ||
197
- (lower.includes("use") && lower.includes("for"))
219
+ FACT_PREFERENCE_LANGUAGE_PATTERN.test(m.content) ||
220
+ FACT_USE_CONTEXT_PATTERN.test(m.content)
198
221
  );
199
222
  });
200
223
 
@@ -225,24 +248,37 @@ export function consolidatePreferences(
225
248
 
226
249
  for (const mem of deduped.slice(0, maxPreferences * 2)) {
227
250
  const content = mem.content.trim();
251
+ const hasDoubleNegatedAversion = DOUBLE_NEGATED_AVERSION_PATTERN.test(content);
252
+ const hasWithdrawnPreference = WITHDRAWN_PREFERENCE_PATTERN.test(content);
253
+ const strippedContent = hasDoubleNegatedAversion
254
+ ? content.replace(DOUBLE_NEGATED_AVERSION_GLOBAL_PATTERN, " ").trim()
255
+ : content;
256
+ const replacementContent = hasWithdrawnPreference
257
+ ? replacementPreferenceClause(strippedContent)
258
+ : null;
259
+ const extractionContent = replacementContent ?? strippedContent;
228
260
  let statement: string | null = null;
229
261
 
230
262
  // Try pattern-based extraction first
231
263
  for (const extractor of PREFERENCE_EXTRACTORS) {
232
- const match = content.match(extractor.pattern);
264
+ const match = extractionContent.match(extractor.pattern);
233
265
  if (match) {
234
- statement = extractor.transform(match, content);
266
+ statement = extractor.transform(match, extractionContent);
235
267
  break;
236
268
  }
237
269
  }
238
270
 
271
+ if (!statement && hasDoubleNegatedAversion) {
272
+ continue;
273
+ }
274
+
239
275
  // Fallback: generic prefix
240
276
  if (!statement) {
241
- statement = fallbackPreferenceStatement(content, mem.frontmatter.category);
277
+ statement = fallbackPreferenceStatement(replacementContent ?? content, mem.frontmatter.category);
242
278
  }
243
279
 
244
280
  // Skip if statement is too short or too generic
245
- if (statement.length < 20) continue;
281
+ if (statement.length < 20 && !replacementContent) continue;
246
282
 
247
283
  const keywords = extractKeywords(statement);
248
284
 
@@ -3,6 +3,63 @@ import test from "node:test";
3
3
 
4
4
  import { parseConfig } from "./config.js";
5
5
 
6
+ test("parseConfig emitLegacyTools defaults to true and coerces config/env (issue #1427)", () => {
7
+ // Default: legacy aliases on, for backward compatibility.
8
+ assert.equal(parseConfig({}).emitLegacyTools, true);
9
+ // `null` means "unset → use default", consistent with the repo convention for
10
+ // optional fields (e.g. taskModelChain: null → undefined). Not a hard error.
11
+ assert.equal(parseConfig({ emitLegacyTools: null }).emitLegacyTools, true);
12
+ // Boolean + boolean-like string config values.
13
+ assert.equal(parseConfig({ emitLegacyTools: false }).emitLegacyTools, false);
14
+ assert.equal(parseConfig({ emitLegacyTools: "false" }).emitLegacyTools, false);
15
+ assert.equal(parseConfig({ emitLegacyTools: "0" }).emitLegacyTools, false);
16
+ assert.equal(parseConfig({ emitLegacyTools: "true" }).emitLegacyTools, true);
17
+
18
+ // Env var fallback (REMNIC_ preferred, ENGRAM_ legacy) when config field absent.
19
+ const prevRemnic = process.env.REMNIC_EMIT_LEGACY_TOOLS;
20
+ const prevEngram = process.env.ENGRAM_EMIT_LEGACY_TOOLS;
21
+ try {
22
+ process.env.REMNIC_EMIT_LEGACY_TOOLS = "false";
23
+ assert.equal(parseConfig({}).emitLegacyTools, false, "REMNIC_ env disables");
24
+ // Explicit config field wins over env.
25
+ assert.equal(parseConfig({ emitLegacyTools: true }).emitLegacyTools, true, "config field wins over env");
26
+ delete process.env.REMNIC_EMIT_LEGACY_TOOLS;
27
+ process.env.ENGRAM_EMIT_LEGACY_TOOLS = "false";
28
+ assert.equal(parseConfig({}).emitLegacyTools, false, "ENGRAM_ env fallback disables");
29
+ } finally {
30
+ if (prevRemnic === undefined) delete process.env.REMNIC_EMIT_LEGACY_TOOLS;
31
+ else process.env.REMNIC_EMIT_LEGACY_TOOLS = prevRemnic;
32
+ if (prevEngram === undefined) delete process.env.ENGRAM_EMIT_LEGACY_TOOLS;
33
+ else process.env.ENGRAM_EMIT_LEGACY_TOOLS = prevEngram;
34
+ }
35
+ });
36
+
37
+ test("parseConfig rejects a present-but-malformed emitLegacyTools (gotcha #51, #1427)", () => {
38
+ // A typo must fail fast, not silently fall through to the default (true) and
39
+ // re-enable legacy tool advertising.
40
+ for (const bad of ["fales", "maybe", 2, "2", "enabled"]) {
41
+ assert.throws(
42
+ () => parseConfig({ emitLegacyTools: bad }),
43
+ /emitLegacyTools must be a boolean-like value/,
44
+ `emitLegacyTools=${JSON.stringify(bad)} should throw`,
45
+ );
46
+ }
47
+ // Malformed env var also fails fast (only when the config field is absent).
48
+ const prev = process.env.REMNIC_EMIT_LEGACY_TOOLS;
49
+ try {
50
+ process.env.REMNIC_EMIT_LEGACY_TOOLS = "maybe";
51
+ assert.throws(
52
+ () => parseConfig({}),
53
+ /REMNIC_EMIT_LEGACY_TOOLS must be a boolean-like value/,
54
+ );
55
+ // An explicit valid config field overrides a malformed env (field wins first).
56
+ assert.equal(parseConfig({ emitLegacyTools: false }).emitLegacyTools, false);
57
+ } finally {
58
+ if (prev === undefined) delete process.env.REMNIC_EMIT_LEGACY_TOOLS;
59
+ else process.env.REMNIC_EMIT_LEGACY_TOOLS = prev;
60
+ }
61
+ });
62
+
6
63
  test("parseConfig expands tilde paths for core storage directories", () => {
7
64
  const previousHome = process.env.HOME;
8
65
  process.env.HOME = "/Users/remnic-test";
@@ -72,6 +129,33 @@ test("parseConfig codex missing entirely → installExtension defaults to true",
72
129
  assert.equal(result.codex.installExtension, true);
73
130
  });
74
131
 
132
+ test("parseConfig recallPlannerLlmEnabled defaults to false and coerces boolean-like strings (opt-in, issue #1367)", () => {
133
+ assert.equal(parseConfig({}).recallPlannerLlmEnabled, false);
134
+ assert.equal(parseConfig({ recallPlannerLlmEnabled: true }).recallPlannerLlmEnabled, true);
135
+ // CLI/env surfaces pass strings — these must enable the gate (gotcha #36).
136
+ assert.equal(parseConfig({ recallPlannerLlmEnabled: "true" }).recallPlannerLlmEnabled, true);
137
+ assert.equal(parseConfig({ recallPlannerLlmEnabled: "1" }).recallPlannerLlmEnabled, true);
138
+ assert.equal(parseConfig({ recallPlannerLlmEnabled: "on" }).recallPlannerLlmEnabled, true);
139
+ // Boolean-like falses and junk stay off.
140
+ assert.equal(parseConfig({ recallPlannerLlmEnabled: "false" }).recallPlannerLlmEnabled, false);
141
+ assert.equal(parseConfig({ recallPlannerLlmEnabled: "0" }).recallPlannerLlmEnabled, false);
142
+ });
143
+
144
+ test("parseConfig coerces boolean-like strings for all recallPlanner gates (issue #1367, gotcha #36)", () => {
145
+ // Rollout switches must honor string config from CLI/env surfaces.
146
+ assert.equal(parseConfig({ recallPlannerShadowMode: "true" }).recallPlannerShadowMode, true);
147
+ assert.equal(parseConfig({ recallPlannerShadowMode: "off" }).recallPlannerShadowMode, false);
148
+ assert.equal(parseConfig({}).recallPlannerShadowMode, false);
149
+
150
+ assert.equal(parseConfig({ recallPlannerTelemetryEnabled: "false" }).recallPlannerTelemetryEnabled, false);
151
+ assert.equal(parseConfig({}).recallPlannerTelemetryEnabled, true);
152
+
153
+ // The enable gate must be disableable via string "false" (the old `!== false`
154
+ // check treated "false" as truthy → could not disable).
155
+ assert.equal(parseConfig({ recallPlannerEnabled: "false" }).recallPlannerEnabled, false);
156
+ assert.equal(parseConfig({}).recallPlannerEnabled, true);
157
+ });
158
+
75
159
  test("parseConfig dreaming.maxEntries=0 preserves the runtime disable switch", () => {
76
160
  const result = parseConfig({ dreaming: { maxEntries: 0 } });
77
161
  assert.equal(result.dreaming.maxEntries, 0);
@@ -116,6 +200,59 @@ test("parseConfig initGateTimeoutMs defaults to OpenClaw cold-start budget", ()
116
200
  assert.equal(result.initGateTimeoutMs, 30_000);
117
201
  });
118
202
 
203
+ test("parseConfig qmdSearchStrategy defaults to hybrid and validates the enum", () => {
204
+ // Default must equal the historical lex+vec+hyde behavior. Issue #1335.
205
+ assert.equal(parseConfig({}).qmdSearchStrategy, "hybrid");
206
+ assert.equal(parseConfig({ qmdSearchStrategy: "hybrid" }).qmdSearchStrategy, "hybrid");
207
+ assert.equal(parseConfig({ qmdSearchStrategy: "lex-vec" }).qmdSearchStrategy, "lex-vec");
208
+ assert.equal(parseConfig({ qmdSearchStrategy: "lex" }).qmdSearchStrategy, "lex");
209
+ assert.equal(parseConfig({ qmdSearchStrategy: "LEX" }).qmdSearchStrategy, "lex");
210
+
211
+ for (const value of ["hyde", "vec", "bm25", "", 42]) {
212
+ assert.throws(
213
+ () => parseConfig({ qmdSearchStrategy: value }),
214
+ /qmdSearchStrategy must be one of/,
215
+ `invalid qmdSearchStrategy ${String(value)} should throw`,
216
+ );
217
+ }
218
+ });
219
+
220
+ test("parseConfig qmdSubprocessStrategy defaults to query (honors QMD query intent)", () => {
221
+ // Default must remain `qmd query` (LLM expansion + rerank) per CLAUDE.md gotcha #7.
222
+ assert.equal(parseConfig({}).qmdSubprocessStrategy, "query");
223
+ assert.equal(parseConfig({ qmdSubprocessStrategy: "query" }).qmdSubprocessStrategy, "query");
224
+ assert.equal(parseConfig({ qmdSubprocessStrategy: "search" }).qmdSubprocessStrategy, "search");
225
+ assert.equal(parseConfig({ qmdSubprocessStrategy: "SEARCH" }).qmdSubprocessStrategy, "search");
226
+
227
+ for (const value of ["bm25", "vsearch", "", 7]) {
228
+ assert.throws(
229
+ () => parseConfig({ qmdSubprocessStrategy: value }),
230
+ /qmdSubprocessStrategy must be one of/,
231
+ `invalid qmdSubprocessStrategy ${String(value)} should throw`,
232
+ );
233
+ }
234
+ });
235
+
236
+ test("parseConfig qmdDaemonTimeoutMs defaults to 8000 and clamps valid integers", () => {
237
+ assert.equal(parseConfig({}).qmdDaemonTimeoutMs, 8_000);
238
+ assert.equal(parseConfig({ qmdDaemonTimeoutMs: 20_000 }).qmdDaemonTimeoutMs, 20_000);
239
+ assert.equal(parseConfig({ qmdDaemonTimeoutMs: "20000" }).qmdDaemonTimeoutMs, 20_000);
240
+ // Below floor clamps up; above ceiling clamps down.
241
+ assert.equal(parseConfig({ qmdDaemonTimeoutMs: 100 }).qmdDaemonTimeoutMs, 1_000);
242
+ assert.equal(parseConfig({ qmdDaemonTimeoutMs: 999_999 }).qmdDaemonTimeoutMs, 120_000);
243
+ });
244
+
245
+ test("parseConfig qmdDaemonTimeoutMs rejects non-numeric and non-integer input", () => {
246
+ // gotcha #51 + codex review on #1422: silent coercion hides config mistakes.
247
+ for (const value of ["abc", "", 2500.9, "2500.9", Number.NaN, Infinity, true, {}]) {
248
+ assert.throws(
249
+ () => parseConfig({ qmdDaemonTimeoutMs: value }),
250
+ /qmdDaemonTimeoutMs must be an integer/,
251
+ `invalid qmdDaemonTimeoutMs ${String(value)} should throw`,
252
+ );
253
+ }
254
+ });
255
+
119
256
  test("parseConfig initGateTimeoutMs accepts CLI-style numeric strings", () => {
120
257
  const result = parseConfig({ initGateTimeoutMs: "45000" });
121
258
  assert.equal(result.initGateTimeoutMs, 45_000);
@@ -140,6 +277,82 @@ test("parseConfig modelSource=gateway does not inherit OPENAI_API_KEY from the p
140
277
  }
141
278
  });
142
279
 
280
+ test("parseConfig normalizes taskModelChain", () => {
281
+ const cfg = parseConfig({
282
+ taskModelChain: {
283
+ primary: " openai/cheap-primary ",
284
+ fallbacks: ["openai/cheap-primary", " fireworks/accounts/fireworks/models/glm-5p1 ", ""],
285
+ },
286
+ });
287
+
288
+ assert.deepEqual(cfg.taskModelChain, {
289
+ primary: "openai/cheap-primary",
290
+ fallbacks: ["fireworks/accounts/fireworks/models/glm-5p1"],
291
+ });
292
+ });
293
+
294
+ test("parseConfig treats an absent taskModelChain as not configured", () => {
295
+ assert.equal(parseConfig({}).taskModelChain, undefined);
296
+ assert.equal(parseConfig({ taskModelChain: null }).taskModelChain, undefined);
297
+ assert.equal(parseConfig({ taskModelChain: undefined }).taskModelChain, undefined);
298
+ });
299
+
300
+ test("parseConfig rejects a present-but-malformed taskModelChain (gotcha #51)", () => {
301
+ // A typo'd chain must surface loudly instead of silently reverting to defaults.
302
+ assert.throws(() => parseConfig({ taskModelChain: [] }), /taskModelChain must be an object/);
303
+ assert.throws(() => parseConfig({ taskModelChain: "openai/x" }), /taskModelChain must be an object/);
304
+ assert.throws(() => parseConfig({ taskModelChain: { primary: " " } }), /taskModelChain\.primary is required/);
305
+ assert.throws(() => parseConfig({ taskModelChain: { fallbacks: ["openai/fallback-only"] } }), /taskModelChain\.primary is required/);
306
+ assert.throws(
307
+ () => parseConfig({ taskModelChain: { primary: "openai/p", fallbacks: "not-an-array" } }),
308
+ /taskModelChain\.fallbacks must be an array/,
309
+ );
310
+ assert.throws(
311
+ () => parseConfig({ taskModelChain: { primary: "openai/p", fallbacks: [123] } }),
312
+ /taskModelChain\.fallbacks must contain only strings/,
313
+ );
314
+ });
315
+
316
+ test("parseConfig rejects unqualified taskModelChain model strings (codex review #1425)", () => {
317
+ // A slash-less id like "gpt-4.1" parses here but FallbackLlmClient.parseModelString
318
+ // drops it, leaving the chain silently using a different model — reject at parse.
319
+ assert.throws(
320
+ () => parseConfig({ taskModelChain: { primary: "gpt-4.1" } }),
321
+ /taskModelChain\.primary must be in "provider\/model" form/,
322
+ );
323
+ assert.throws(
324
+ () => parseConfig({ taskModelChain: { primary: "openai/" } }),
325
+ /taskModelChain\.primary must be in "provider\/model" form/,
326
+ );
327
+ assert.throws(
328
+ () => parseConfig({ taskModelChain: { primary: "/gpt-4.1" } }),
329
+ /taskModelChain\.primary must be in "provider\/model" form/,
330
+ );
331
+ assert.throws(
332
+ () => parseConfig({ taskModelChain: { primary: "openai/gpt", fallbacks: ["bare-model"] } }),
333
+ /taskModelChain\.fallbacks entries must be in "provider\/model" form/,
334
+ );
335
+ // Multi-slash provider/model paths remain valid.
336
+ assert.deepEqual(
337
+ parseConfig({
338
+ taskModelChain: { primary: "fireworks/accounts/fireworks/models/glm-5p1" },
339
+ }).taskModelChain,
340
+ { primary: "fireworks/accounts/fireworks/models/glm-5p1" },
341
+ );
342
+ });
343
+
344
+ test("parseConfig rejects unknown taskModelChain keys (codex review #1425)", () => {
345
+ // A misspelled "fallback" must not silently drop the fallback chain.
346
+ assert.throws(
347
+ () => parseConfig({ taskModelChain: { primary: "openai/p", fallback: ["openai/q"] } }),
348
+ /taskModelChain has unknown property: fallback/,
349
+ );
350
+ assert.throws(
351
+ () => parseConfig({ taskModelChain: { primary: "openai/p", fallbackModels: ["openai/q"], extra: 1 } }),
352
+ /taskModelChain has unknown properties:/,
353
+ );
354
+ });
355
+
143
356
  test("parseConfig modelSource=gateway still honors an explicit openaiApiKey override", () => {
144
357
  const original = process.env.OPENAI_API_KEY;
145
358
  process.env.OPENAI_API_KEY = "sk-env-should-not-be-used";