@fgv/ts-agent-memory 5.1.0-36

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 (331) hide show
  1. package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +9 -0
  2. package/.rush/temp/f6a88bfdd66517ccb98c2c7ae1be6e6fe9e15d38.tar.log +282 -0
  3. package/.rush/temp/operation/build/all.log +9 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +688 -0
  7. package/LICENSE +21 -0
  8. package/README.md +45 -0
  9. package/config/api-extractor.json +343 -0
  10. package/config/jest.config.json +14 -0
  11. package/config/rig.json +4 -0
  12. package/dist/index.js +12 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/packlets/converters/bodyConverterRegistry.js +51 -0
  15. package/dist/packlets/converters/bodyConverterRegistry.js.map +1 -0
  16. package/dist/packlets/converters/envelopeConverter.js +159 -0
  17. package/dist/packlets/converters/envelopeConverter.js.map +1 -0
  18. package/dist/packlets/converters/index.js +7 -0
  19. package/dist/packlets/converters/index.js.map +1 -0
  20. package/dist/packlets/index/index.js +6 -0
  21. package/dist/packlets/index/index.js.map +1 -0
  22. package/dist/packlets/index/memoryIndex.js +170 -0
  23. package/dist/packlets/index/memoryIndex.js.map +1 -0
  24. package/dist/packlets/observe/index.js +7 -0
  25. package/dist/packlets/observe/index.js.map +1 -0
  26. package/dist/packlets/observe/memoryObservationStore.js +119 -0
  27. package/dist/packlets/observe/memoryObservationStore.js.map +1 -0
  28. package/dist/packlets/observe/observer.js +6 -0
  29. package/dist/packlets/observe/observer.js.map +1 -0
  30. package/dist/packlets/retrieve/hybridRetriever.js +135 -0
  31. package/dist/packlets/retrieve/hybridRetriever.js.map +1 -0
  32. package/dist/packlets/retrieve/index.js +12 -0
  33. package/dist/packlets/retrieve/index.js.map +1 -0
  34. package/dist/packlets/retrieve/linkTraversalRetriever.js +143 -0
  35. package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -0
  36. package/dist/packlets/retrieve/recencyRetriever.js +33 -0
  37. package/dist/packlets/retrieve/recencyRetriever.js.map +1 -0
  38. package/dist/packlets/retrieve/retriever.js +110 -0
  39. package/dist/packlets/retrieve/retriever.js.map +1 -0
  40. package/dist/packlets/retrieve/semanticRetriever.js +86 -0
  41. package/dist/packlets/retrieve/semanticRetriever.js.map +1 -0
  42. package/dist/packlets/retrieve/structuredFilterRetriever.js +37 -0
  43. package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -0
  44. package/dist/packlets/retrieve/tagRetriever.js +37 -0
  45. package/dist/packlets/retrieve/tagRetriever.js.map +1 -0
  46. package/dist/packlets/store/fileTreeMemoryStore.js +698 -0
  47. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -0
  48. package/dist/packlets/store/index.js +7 -0
  49. package/dist/packlets/store/index.js.map +1 -0
  50. package/dist/packlets/store/scopeEncoding.js +31 -0
  51. package/dist/packlets/store/scopeEncoding.js.map +1 -0
  52. package/dist/packlets/types/envelope.js +6 -0
  53. package/dist/packlets/types/envelope.js.map +1 -0
  54. package/dist/packlets/types/filenameSafety.js +52 -0
  55. package/dist/packlets/types/filenameSafety.js.map +1 -0
  56. package/dist/packlets/types/identityCodec.js +184 -0
  57. package/dist/packlets/types/identityCodec.js.map +1 -0
  58. package/dist/packlets/types/ids.js +67 -0
  59. package/dist/packlets/types/ids.js.map +1 -0
  60. package/dist/packlets/types/index.js +10 -0
  61. package/dist/packlets/types/index.js.map +1 -0
  62. package/dist/packlets/types/writePolicy.js +263 -0
  63. package/dist/packlets/types/writePolicy.js.map +1 -0
  64. package/dist/packlets/vector/inMemoryCosineIndex.js +150 -0
  65. package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -0
  66. package/dist/packlets/vector/index.js +7 -0
  67. package/dist/packlets/vector/index.js.map +1 -0
  68. package/dist/packlets/vector/vectorIndex.js +6 -0
  69. package/dist/packlets/vector/vectorIndex.js.map +1 -0
  70. package/dist/test/unit/converters/bodyConverterRegistry.test.js +72 -0
  71. package/dist/test/unit/converters/bodyConverterRegistry.test.js.map +1 -0
  72. package/dist/test/unit/converters/envelopeConverter.test.js +196 -0
  73. package/dist/test/unit/converters/envelopeConverter.test.js.map +1 -0
  74. package/dist/test/unit/index/memoryIndex.test.js +152 -0
  75. package/dist/test/unit/index/memoryIndex.test.js.map +1 -0
  76. package/dist/test/unit/observe/memoryObservationStore.test.js +118 -0
  77. package/dist/test/unit/observe/memoryObservationStore.test.js.map +1 -0
  78. package/dist/test/unit/retrieve/linkTraversalRetriever.test.js +182 -0
  79. package/dist/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -0
  80. package/dist/test/unit/retrieve/retrievers.test.js +506 -0
  81. package/dist/test/unit/retrieve/retrievers.test.js.map +1 -0
  82. package/dist/test/unit/store/embedOnWrite.test.js +260 -0
  83. package/dist/test/unit/store/embedOnWrite.test.js.map +1 -0
  84. package/dist/test/unit/store/fileTreeMemoryStore.test.js +647 -0
  85. package/dist/test/unit/store/fileTreeMemoryStore.test.js.map +1 -0
  86. package/dist/test/unit/store/observations.test.js +239 -0
  87. package/dist/test/unit/store/observations.test.js.map +1 -0
  88. package/dist/test/unit/store/scopeEncoding.test.js +24 -0
  89. package/dist/test/unit/store/scopeEncoding.test.js.map +1 -0
  90. package/dist/test/unit/types/identityCodec.test.js +187 -0
  91. package/dist/test/unit/types/identityCodec.test.js.map +1 -0
  92. package/dist/test/unit/types/ids.test.js +84 -0
  93. package/dist/test/unit/types/ids.test.js.map +1 -0
  94. package/dist/test/unit/types/writePolicy.test.js +241 -0
  95. package/dist/test/unit/types/writePolicy.test.js.map +1 -0
  96. package/dist/test/unit/vector/inMemoryCosineIndex.test.js +192 -0
  97. package/dist/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -0
  98. package/dist/test/unit/vector/vectorIndex.test.js +42 -0
  99. package/dist/test/unit/vector/vectorIndex.test.js.map +1 -0
  100. package/dist/ts-agent-memory.d.ts +1901 -0
  101. package/dist/tsdoc-metadata.json +11 -0
  102. package/eslint.config.js +15 -0
  103. package/etc/ts-agent-memory.api.md +525 -0
  104. package/lib/index.d.ts +8 -0
  105. package/lib/index.d.ts.map +1 -0
  106. package/lib/index.js +28 -0
  107. package/lib/index.js.map +1 -0
  108. package/lib/packlets/converters/bodyConverterRegistry.d.ts +64 -0
  109. package/lib/packlets/converters/bodyConverterRegistry.d.ts.map +1 -0
  110. package/lib/packlets/converters/bodyConverterRegistry.js +55 -0
  111. package/lib/packlets/converters/bodyConverterRegistry.js.map +1 -0
  112. package/lib/packlets/converters/envelopeConverter.d.ts +70 -0
  113. package/lib/packlets/converters/envelopeConverter.d.ts.map +1 -0
  114. package/lib/packlets/converters/envelopeConverter.js +166 -0
  115. package/lib/packlets/converters/envelopeConverter.js.map +1 -0
  116. package/lib/packlets/converters/index.d.ts +3 -0
  117. package/lib/packlets/converters/index.d.ts.map +1 -0
  118. package/lib/packlets/converters/index.js +23 -0
  119. package/lib/packlets/converters/index.js.map +1 -0
  120. package/lib/packlets/index/index.d.ts +2 -0
  121. package/lib/packlets/index/index.d.ts.map +1 -0
  122. package/lib/packlets/index/index.js +22 -0
  123. package/lib/packlets/index/index.js.map +1 -0
  124. package/lib/packlets/index/memoryIndex.d.ts +127 -0
  125. package/lib/packlets/index/memoryIndex.d.ts.map +1 -0
  126. package/lib/packlets/index/memoryIndex.js +174 -0
  127. package/lib/packlets/index/memoryIndex.js.map +1 -0
  128. package/lib/packlets/observe/index.d.ts +3 -0
  129. package/lib/packlets/observe/index.d.ts.map +1 -0
  130. package/lib/packlets/observe/index.js +23 -0
  131. package/lib/packlets/observe/index.js.map +1 -0
  132. package/lib/packlets/observe/memoryObservationStore.d.ts +91 -0
  133. package/lib/packlets/observe/memoryObservationStore.d.ts.map +1 -0
  134. package/lib/packlets/observe/memoryObservationStore.js +123 -0
  135. package/lib/packlets/observe/memoryObservationStore.js.map +1 -0
  136. package/lib/packlets/observe/observer.d.ts +110 -0
  137. package/lib/packlets/observe/observer.d.ts.map +1 -0
  138. package/lib/packlets/observe/observer.js +7 -0
  139. package/lib/packlets/observe/observer.js.map +1 -0
  140. package/lib/packlets/retrieve/hybridRetriever.d.ts +79 -0
  141. package/lib/packlets/retrieve/hybridRetriever.d.ts.map +1 -0
  142. package/lib/packlets/retrieve/hybridRetriever.js +140 -0
  143. package/lib/packlets/retrieve/hybridRetriever.js.map +1 -0
  144. package/lib/packlets/retrieve/index.d.ts +8 -0
  145. package/lib/packlets/retrieve/index.d.ts.map +1 -0
  146. package/lib/packlets/retrieve/index.js +28 -0
  147. package/lib/packlets/retrieve/index.js.map +1 -0
  148. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +61 -0
  149. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -0
  150. package/lib/packlets/retrieve/linkTraversalRetriever.js +147 -0
  151. package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -0
  152. package/lib/packlets/retrieve/recencyRetriever.d.ts +21 -0
  153. package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -0
  154. package/lib/packlets/retrieve/recencyRetriever.js +37 -0
  155. package/lib/packlets/retrieve/recencyRetriever.js.map +1 -0
  156. package/lib/packlets/retrieve/retriever.d.ts +133 -0
  157. package/lib/packlets/retrieve/retriever.d.ts.map +1 -0
  158. package/lib/packlets/retrieve/retriever.js +119 -0
  159. package/lib/packlets/retrieve/retriever.js.map +1 -0
  160. package/lib/packlets/retrieve/semanticRetriever.d.ts +69 -0
  161. package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -0
  162. package/lib/packlets/retrieve/semanticRetriever.js +90 -0
  163. package/lib/packlets/retrieve/semanticRetriever.js.map +1 -0
  164. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +22 -0
  165. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -0
  166. package/lib/packlets/retrieve/structuredFilterRetriever.js +41 -0
  167. package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -0
  168. package/lib/packlets/retrieve/tagRetriever.d.ts +22 -0
  169. package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -0
  170. package/lib/packlets/retrieve/tagRetriever.js +41 -0
  171. package/lib/packlets/retrieve/tagRetriever.js.map +1 -0
  172. package/lib/packlets/store/fileTreeMemoryStore.d.ts +327 -0
  173. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -0
  174. package/lib/packlets/store/fileTreeMemoryStore.js +702 -0
  175. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -0
  176. package/lib/packlets/store/index.d.ts +3 -0
  177. package/lib/packlets/store/index.d.ts.map +1 -0
  178. package/lib/packlets/store/index.js +23 -0
  179. package/lib/packlets/store/index.js.map +1 -0
  180. package/lib/packlets/store/scopeEncoding.d.ts +19 -0
  181. package/lib/packlets/store/scopeEncoding.d.ts.map +1 -0
  182. package/lib/packlets/store/scopeEncoding.js +34 -0
  183. package/lib/packlets/store/scopeEncoding.js.map +1 -0
  184. package/lib/packlets/types/envelope.d.ts +119 -0
  185. package/lib/packlets/types/envelope.d.ts.map +1 -0
  186. package/lib/packlets/types/envelope.js +7 -0
  187. package/lib/packlets/types/envelope.js.map +1 -0
  188. package/lib/packlets/types/filenameSafety.d.ts +16 -0
  189. package/lib/packlets/types/filenameSafety.d.ts.map +1 -0
  190. package/lib/packlets/types/filenameSafety.js +55 -0
  191. package/lib/packlets/types/filenameSafety.js.map +1 -0
  192. package/lib/packlets/types/identityCodec.d.ts +136 -0
  193. package/lib/packlets/types/identityCodec.d.ts.map +1 -0
  194. package/lib/packlets/types/identityCodec.js +190 -0
  195. package/lib/packlets/types/identityCodec.js.map +1 -0
  196. package/lib/packlets/types/ids.d.ts +55 -0
  197. package/lib/packlets/types/ids.d.ts.map +1 -0
  198. package/lib/packlets/types/ids.js +70 -0
  199. package/lib/packlets/types/ids.js.map +1 -0
  200. package/lib/packlets/types/index.d.ts +6 -0
  201. package/lib/packlets/types/index.d.ts.map +1 -0
  202. package/lib/packlets/types/index.js +26 -0
  203. package/lib/packlets/types/index.js.map +1 -0
  204. package/lib/packlets/types/writePolicy.d.ts +213 -0
  205. package/lib/packlets/types/writePolicy.d.ts.map +1 -0
  206. package/lib/packlets/types/writePolicy.js +268 -0
  207. package/lib/packlets/types/writePolicy.js.map +1 -0
  208. package/lib/packlets/vector/inMemoryCosineIndex.d.ts +69 -0
  209. package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -0
  210. package/lib/packlets/vector/inMemoryCosineIndex.js +154 -0
  211. package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -0
  212. package/lib/packlets/vector/index.d.ts +3 -0
  213. package/lib/packlets/vector/index.d.ts.map +1 -0
  214. package/lib/packlets/vector/index.js +23 -0
  215. package/lib/packlets/vector/index.js.map +1 -0
  216. package/lib/packlets/vector/vectorIndex.d.ts +68 -0
  217. package/lib/packlets/vector/vectorIndex.d.ts.map +1 -0
  218. package/lib/packlets/vector/vectorIndex.js +7 -0
  219. package/lib/packlets/vector/vectorIndex.js.map +1 -0
  220. package/lib/test/unit/converters/bodyConverterRegistry.test.d.ts +2 -0
  221. package/lib/test/unit/converters/bodyConverterRegistry.test.d.ts.map +1 -0
  222. package/lib/test/unit/converters/bodyConverterRegistry.test.js +74 -0
  223. package/lib/test/unit/converters/bodyConverterRegistry.test.js.map +1 -0
  224. package/lib/test/unit/converters/envelopeConverter.test.d.ts +2 -0
  225. package/lib/test/unit/converters/envelopeConverter.test.d.ts.map +1 -0
  226. package/lib/test/unit/converters/envelopeConverter.test.js +198 -0
  227. package/lib/test/unit/converters/envelopeConverter.test.js.map +1 -0
  228. package/lib/test/unit/index/memoryIndex.test.d.ts +2 -0
  229. package/lib/test/unit/index/memoryIndex.test.d.ts.map +1 -0
  230. package/lib/test/unit/index/memoryIndex.test.js +154 -0
  231. package/lib/test/unit/index/memoryIndex.test.js.map +1 -0
  232. package/lib/test/unit/observe/memoryObservationStore.test.d.ts +2 -0
  233. package/lib/test/unit/observe/memoryObservationStore.test.d.ts.map +1 -0
  234. package/lib/test/unit/observe/memoryObservationStore.test.js +120 -0
  235. package/lib/test/unit/observe/memoryObservationStore.test.js.map +1 -0
  236. package/lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts +2 -0
  237. package/lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts.map +1 -0
  238. package/lib/test/unit/retrieve/linkTraversalRetriever.test.js +184 -0
  239. package/lib/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -0
  240. package/lib/test/unit/retrieve/retrievers.test.d.ts +2 -0
  241. package/lib/test/unit/retrieve/retrievers.test.d.ts.map +1 -0
  242. package/lib/test/unit/retrieve/retrievers.test.js +508 -0
  243. package/lib/test/unit/retrieve/retrievers.test.js.map +1 -0
  244. package/lib/test/unit/store/embedOnWrite.test.d.ts +2 -0
  245. package/lib/test/unit/store/embedOnWrite.test.d.ts.map +1 -0
  246. package/lib/test/unit/store/embedOnWrite.test.js +262 -0
  247. package/lib/test/unit/store/embedOnWrite.test.js.map +1 -0
  248. package/lib/test/unit/store/fileTreeMemoryStore.test.d.ts +2 -0
  249. package/lib/test/unit/store/fileTreeMemoryStore.test.d.ts.map +1 -0
  250. package/lib/test/unit/store/fileTreeMemoryStore.test.js +649 -0
  251. package/lib/test/unit/store/fileTreeMemoryStore.test.js.map +1 -0
  252. package/lib/test/unit/store/observations.test.d.ts +2 -0
  253. package/lib/test/unit/store/observations.test.d.ts.map +1 -0
  254. package/lib/test/unit/store/observations.test.js +241 -0
  255. package/lib/test/unit/store/observations.test.js.map +1 -0
  256. package/lib/test/unit/store/scopeEncoding.test.d.ts +2 -0
  257. package/lib/test/unit/store/scopeEncoding.test.d.ts.map +1 -0
  258. package/lib/test/unit/store/scopeEncoding.test.js +26 -0
  259. package/lib/test/unit/store/scopeEncoding.test.js.map +1 -0
  260. package/lib/test/unit/types/identityCodec.test.d.ts +2 -0
  261. package/lib/test/unit/types/identityCodec.test.d.ts.map +1 -0
  262. package/lib/test/unit/types/identityCodec.test.js +189 -0
  263. package/lib/test/unit/types/identityCodec.test.js.map +1 -0
  264. package/lib/test/unit/types/ids.test.d.ts +2 -0
  265. package/lib/test/unit/types/ids.test.d.ts.map +1 -0
  266. package/lib/test/unit/types/ids.test.js +86 -0
  267. package/lib/test/unit/types/ids.test.js.map +1 -0
  268. package/lib/test/unit/types/writePolicy.test.d.ts +2 -0
  269. package/lib/test/unit/types/writePolicy.test.d.ts.map +1 -0
  270. package/lib/test/unit/types/writePolicy.test.js +243 -0
  271. package/lib/test/unit/types/writePolicy.test.js.map +1 -0
  272. package/lib/test/unit/vector/inMemoryCosineIndex.test.d.ts +2 -0
  273. package/lib/test/unit/vector/inMemoryCosineIndex.test.d.ts.map +1 -0
  274. package/lib/test/unit/vector/inMemoryCosineIndex.test.js +194 -0
  275. package/lib/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -0
  276. package/lib/test/unit/vector/vectorIndex.test.d.ts +2 -0
  277. package/lib/test/unit/vector/vectorIndex.test.d.ts.map +1 -0
  278. package/lib/test/unit/vector/vectorIndex.test.js +44 -0
  279. package/lib/test/unit/vector/vectorIndex.test.js.map +1 -0
  280. package/package.json +81 -0
  281. package/rush-logs/ts-agent-memory.build.cache.log +3 -0
  282. package/rush-logs/ts-agent-memory.build.log +9 -0
  283. package/src/index.ts +12 -0
  284. package/src/packlets/converters/bodyConverterRegistry.ts +105 -0
  285. package/src/packlets/converters/envelopeConverter.ts +210 -0
  286. package/src/packlets/converters/index.ts +7 -0
  287. package/src/packlets/index/index.ts +6 -0
  288. package/src/packlets/index/memoryIndex.ts +268 -0
  289. package/src/packlets/observe/index.ts +7 -0
  290. package/src/packlets/observe/memoryObservationStore.ts +153 -0
  291. package/src/packlets/observe/observer.ts +119 -0
  292. package/src/packlets/retrieve/hybridRetriever.ts +181 -0
  293. package/src/packlets/retrieve/index.ts +12 -0
  294. package/src/packlets/retrieve/linkTraversalRetriever.ts +169 -0
  295. package/src/packlets/retrieve/recencyRetriever.ts +54 -0
  296. package/src/packlets/retrieve/retriever.ts +207 -0
  297. package/src/packlets/retrieve/semanticRetriever.ts +147 -0
  298. package/src/packlets/retrieve/structuredFilterRetriever.ts +58 -0
  299. package/src/packlets/retrieve/tagRetriever.ts +58 -0
  300. package/src/packlets/store/fileTreeMemoryStore.ts +1073 -0
  301. package/src/packlets/store/index.ts +7 -0
  302. package/src/packlets/store/scopeEncoding.ts +36 -0
  303. package/src/packlets/types/envelope.ts +138 -0
  304. package/src/packlets/types/filenameSafety.ts +57 -0
  305. package/src/packlets/types/identityCodec.ts +263 -0
  306. package/src/packlets/types/ids.ts +124 -0
  307. package/src/packlets/types/index.ts +10 -0
  308. package/src/packlets/types/writePolicy.ts +447 -0
  309. package/src/packlets/vector/inMemoryCosineIndex.ts +173 -0
  310. package/src/packlets/vector/index.ts +7 -0
  311. package/src/packlets/vector/vectorIndex.ts +78 -0
  312. package/src/test/unit/converters/bodyConverterRegistry.test.ts +89 -0
  313. package/src/test/unit/converters/envelopeConverter.test.ts +261 -0
  314. package/src/test/unit/index/memoryIndex.test.ts +187 -0
  315. package/src/test/unit/observe/memoryObservationStore.test.ts +158 -0
  316. package/src/test/unit/retrieve/linkTraversalRetriever.test.ts +230 -0
  317. package/src/test/unit/retrieve/retrievers.test.ts +662 -0
  318. package/src/test/unit/store/embedOnWrite.test.ts +346 -0
  319. package/src/test/unit/store/fileTreeMemoryStore.test.ts +875 -0
  320. package/src/test/unit/store/observations.test.ts +290 -0
  321. package/src/test/unit/store/scopeEncoding.test.ts +37 -0
  322. package/src/test/unit/types/identityCodec.test.ts +266 -0
  323. package/src/test/unit/types/ids.test.ts +94 -0
  324. package/src/test/unit/types/writePolicy.test.ts +325 -0
  325. package/src/test/unit/vector/inMemoryCosineIndex.test.ts +242 -0
  326. package/src/test/unit/vector/vectorIndex.test.ts +48 -0
  327. package/temp/build/lint/_eslint-5eVG3S6w.json +182 -0
  328. package/temp/build/typescript/ts_8nwakTlr.json +1 -0
  329. package/temp/ts-agent-memory.api.json +12438 -0
  330. package/temp/ts-agent-memory.api.md +525 -0
  331. package/tsconfig.json +8 -0
@@ -0,0 +1,78 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Result } from '@fgv/ts-utils';
7
+ import { IMemoryRecord, MemoryId } from '../types';
8
+
9
+ /**
10
+ * A single hit returned by {@link IVectorIndex.query}: the matched record id and
11
+ * the backend's similarity score (higher = more similar; the exact scale is
12
+ * backend-defined). Hits are returned in descending score order.
13
+ * @public
14
+ */
15
+ export interface IVectorQueryHit {
16
+ /** The id of the matched record. */
17
+ readonly id: MemoryId;
18
+ /** Backend similarity score; higher is more similar. */
19
+ readonly score: number;
20
+ }
21
+
22
+ /**
23
+ * The vector-index seam an embedding backend implements to make
24
+ * {@link SemanticRetriever | semantic recall} operational.
25
+ *
26
+ * @remarks
27
+ * Vectors cross this seam as `Float32Array` (the in-memory representation an
28
+ * embedding model produces); `number[]` is reserved for the JSON-wire edges
29
+ * (e.g. a provider's embedding response). The in-package brute-force cosine
30
+ * implementation is {@link InMemoryCosineIndex}; a consumer can swap an external
31
+ * ANN backend behind the same seam once N grows beyond the in-memory regime.
32
+ *
33
+ * Every operation returns a `Result` (async, since a real backend does I/O) so
34
+ * failure is explicit and never throws across the seam.
35
+ * @public
36
+ */
37
+ export interface IVectorIndex {
38
+ /**
39
+ * Add (or replace) the embedding for `id`. Returns the opaque
40
+ * {@link IMemoryEnvelope.embeddingRef | embeddingRef} the store stamps onto
41
+ * the envelope so a later read knows the record is embedded.
42
+ */
43
+ add(id: MemoryId, vector: Float32Array): Promise<Result<string>>;
44
+
45
+ /**
46
+ * Remove the embedding for `id`. Returns the removed id. Idempotent — removing
47
+ * an id with no embedding still succeeds (returns the id).
48
+ */
49
+ remove(id: MemoryId): Promise<Result<MemoryId>>;
50
+
51
+ /**
52
+ * Return the `topK` nearest records to `vector`, in descending score order.
53
+ */
54
+ query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;
55
+ }
56
+
57
+ /**
58
+ * Embeds a complete record into a vector for the store's embed-on-write hook.
59
+ * Async and `Result`-returning, since a real embedder does a network call (cloud
60
+ * provider) or in-process model inference. The consumer wires this — the core
61
+ * package never calls an embedding provider directly, staying embedder-agnostic.
62
+ * @public
63
+ */
64
+ export type MemoryEmbedder = (record: IMemoryRecord<unknown>) => Promise<Result<Float32Array>>;
65
+
66
+ /**
67
+ * The minimal record-source surface {@link InMemoryCosineIndex.rebuild} reads to
68
+ * re-embed an entire vault. {@link IMemoryStore} satisfies it structurally (its
69
+ * `list` accepts an optional filter, which is assignable to this no-argument
70
+ * shape), so a consumer passes the store directly — without the vector packlet
71
+ * taking a dependency on the store packlet (which depends on the vector packlet
72
+ * for {@link IVectorIndex}, so the reverse import would be a cycle).
73
+ * @public
74
+ */
75
+ export interface IMemoryRecordSource {
76
+ /** List every record in the vault. */
77
+ list(): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;
78
+ }
@@ -0,0 +1,89 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import '@fgv/ts-utils-jest';
7
+ import { Converters } from '@fgv/ts-utils';
8
+ import { JsonSchema } from '@fgv/ts-json-base';
9
+ import { BodyConverterRegistry } from '../../../packlets/converters';
10
+ import { Kind } from '../../../packlets/types';
11
+
12
+ const knowledgeKind: Kind = 'knowledge' as Kind;
13
+ const profileKind: Kind = 'profile' as Kind;
14
+
15
+ describe('BodyConverterRegistry', () => {
16
+ let registry: BodyConverterRegistry;
17
+
18
+ beforeEach(() => {
19
+ registry = BodyConverterRegistry.create().orThrow();
20
+ });
21
+
22
+ test('create succeeds', () => {
23
+ expect(BodyConverterRegistry.create()).toSucceed();
24
+ });
25
+
26
+ describe('register / has / convert', () => {
27
+ test('registers a Converter and converts a matching body', () => {
28
+ registry.register(knowledgeKind, Converters.string);
29
+ expect(registry.has(knowledgeKind)).toBe(true);
30
+ expect(registry.convert(knowledgeKind, 'a knowledge doc')).toSucceedWith('a knowledge doc');
31
+ });
32
+
33
+ test('a registered Converter rejects an invalid body', () => {
34
+ registry.register(knowledgeKind, Converters.string);
35
+ expect(registry.convert(knowledgeKind, 42)).toFail();
36
+ });
37
+
38
+ test('has returns false for an unregistered kind', () => {
39
+ expect(registry.has(knowledgeKind)).toBe(false);
40
+ });
41
+
42
+ test('convert fails for an unregistered kind', () => {
43
+ expect(registry.convert(knowledgeKind, 'x')).toFailWith(
44
+ /no converter registered for kind 'knowledge'/i
45
+ );
46
+ });
47
+
48
+ test('register replaces a prior registration for the same kind', () => {
49
+ registry.register(knowledgeKind, Converters.string);
50
+ registry.register(knowledgeKind, Converters.number);
51
+ expect(registry.convert(knowledgeKind, 7)).toSucceedWith(7);
52
+ expect(registry.convert(knowledgeKind, 'x')).toFail();
53
+ });
54
+ });
55
+
56
+ describe('getConverter', () => {
57
+ test('returns the registered converter', () => {
58
+ registry.register(knowledgeKind, Converters.string);
59
+ expect(registry.getConverter(knowledgeKind)).toSucceedAndSatisfy((converter) => {
60
+ expect(converter.convert('hello')).toSucceedWith('hello');
61
+ });
62
+ });
63
+
64
+ test('fails for an unregistered kind', () => {
65
+ expect(registry.getConverter(knowledgeKind)).toFailWith(/no converter registered for kind/i);
66
+ });
67
+ });
68
+
69
+ describe('registerSchema', () => {
70
+ const profileSchema = JsonSchema.object({
71
+ name: JsonSchema.string(),
72
+ age: JsonSchema.integer()
73
+ });
74
+
75
+ test('registers a JsonSchema validator and validates a matching body', () => {
76
+ registry.registerSchema(profileKind, profileSchema);
77
+ expect(registry.has(profileKind)).toBe(true);
78
+ expect(registry.convert(profileKind, { name: 'Ada', age: 36 })).toSucceedWith({
79
+ name: 'Ada',
80
+ age: 36
81
+ });
82
+ });
83
+
84
+ test('a registered schema rejects an invalid body', () => {
85
+ registry.registerSchema(profileKind, profileSchema);
86
+ expect(registry.convert(profileKind, { name: 'Ada' })).toFail();
87
+ });
88
+ });
89
+ });
@@ -0,0 +1,261 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import '@fgv/ts-utils-jest';
7
+ import { Converters } from '@fgv/ts-utils';
8
+ import {
9
+ BodyConverterRegistry,
10
+ edgeConverter,
11
+ envelopeConverter,
12
+ envelopeYamlConverter,
13
+ joinFrontmatter,
14
+ parseMemoryFile,
15
+ provenanceConverter,
16
+ serializeMemoryFile,
17
+ splitFrontmatter,
18
+ temporalConverter
19
+ } from '../../../packlets/converters';
20
+ import { IMemoryEnvelope, Kind } from '../../../packlets/types';
21
+
22
+ const validEnvelopeObject: Record<string, unknown> = {
23
+ id: 'intro-to-rust',
24
+ entityId: 'intro-to-rust',
25
+ kind: 'knowledge',
26
+ tags: ['rust', 'systems'],
27
+ links: [{ type: 'related', target: 'ownership' }],
28
+ created: 1000,
29
+ updated: 2000,
30
+ seq: 5,
31
+ contentHash: 'crc-abc',
32
+ provenance: { source: 'agent', by: 'curator' }
33
+ };
34
+
35
+ describe('provenanceConverter', () => {
36
+ test('validates the known fields', () => {
37
+ expect(
38
+ provenanceConverter.convert({ source: 'human', by: 'erik', model: 'gpt', confidence: 0.9 })
39
+ ).toSucceedWith({ source: 'human', by: 'erik', model: 'gpt', confidence: 0.9 });
40
+ });
41
+
42
+ test('preserves opaque extension keys verbatim', () => {
43
+ expect(
44
+ provenanceConverter.convert({ source: 'agent', sentiment: { score: 0.5 }, epistemic: 'belief' })
45
+ ).toSucceedAndSatisfy((p) => {
46
+ expect(p.source).toBe('agent');
47
+ expect(p.sentiment).toEqual({ score: 0.5 });
48
+ expect(p.epistemic).toBe('belief');
49
+ });
50
+ });
51
+
52
+ test('accepts an arbitrary source string (open vocabulary)', () => {
53
+ expect(provenanceConverter.convert({ source: 'host-ingest' })).toSucceedWith({ source: 'host-ingest' });
54
+ });
55
+
56
+ test('fails when source is missing', () => {
57
+ expect(provenanceConverter.convert({ by: 'erik' })).toFail();
58
+ });
59
+
60
+ test('fails on a non-object', () => {
61
+ expect(provenanceConverter.convert('nope')).toFail();
62
+ });
63
+ });
64
+
65
+ describe('edgeConverter', () => {
66
+ test('validates a full attributed edge', () => {
67
+ expect(
68
+ edgeConverter.convert({
69
+ type: 'mtm-ref',
70
+ target: 'turn-3',
71
+ confidence: 0.8,
72
+ provenance: { source: 'agent' },
73
+ valid_at: 100,
74
+ invalid_at: 200
75
+ })
76
+ ).toSucceedAndSatisfy((edge) => {
77
+ expect(edge.type).toBe('mtm-ref');
78
+ expect(edge.target).toBe('turn-3');
79
+ expect(edge.confidence).toBe(0.8);
80
+ expect(edge.provenance).toEqual({ source: 'agent' });
81
+ expect(edge.valid_at).toBe(100);
82
+ expect(edge.invalid_at).toBe(200);
83
+ });
84
+ });
85
+
86
+ test('validates a minimal edge', () => {
87
+ expect(edgeConverter.convert({ type: 'related', target: 'ownership' })).toSucceedAndSatisfy((edge) => {
88
+ expect(edge.type).toBe('related');
89
+ expect(edge.target).toBe('ownership');
90
+ expect(edge.confidence).toBeUndefined();
91
+ });
92
+ });
93
+
94
+ test('accepts a null invalid_at (still valid)', () => {
95
+ expect(edgeConverter.convert({ type: 'related', target: 'x', invalid_at: null })).toSucceedAndSatisfy(
96
+ (edge) => {
97
+ expect(edge.invalid_at).toBeNull();
98
+ }
99
+ );
100
+ });
101
+
102
+ test('fails when invalid_at is neither a number nor null', () => {
103
+ expect(edgeConverter.convert({ type: 'related', target: 'x', invalid_at: 'soon' })).toFail();
104
+ });
105
+
106
+ test('fails when target is missing', () => {
107
+ expect(edgeConverter.convert({ type: 'related' })).toFail();
108
+ });
109
+ });
110
+
111
+ describe('temporalConverter', () => {
112
+ test('validates a temporal block', () => {
113
+ expect(temporalConverter.convert({ valid_at: 1, invalid_at: null })).toSucceedWith({
114
+ valid_at: 1,
115
+ invalid_at: null
116
+ });
117
+ });
118
+
119
+ test('validates an empty temporal block', () => {
120
+ expect(temporalConverter.convert({})).toSucceedWith({});
121
+ });
122
+ });
123
+
124
+ describe('envelopeConverter', () => {
125
+ test('validates a full envelope without optional blocks', () => {
126
+ expect(envelopeConverter.convert(validEnvelopeObject)).toSucceedAndSatisfy((envelope) => {
127
+ expect(envelope.id).toBe('intro-to-rust');
128
+ expect(envelope.tags).toEqual(['rust', 'systems']);
129
+ expect(envelope.links).toHaveLength(1);
130
+ expect(envelope.temporal).toBeUndefined();
131
+ expect(envelope.embeddingRef).toBeUndefined();
132
+ });
133
+ });
134
+
135
+ test('validates a temporal envelope with a string embeddingRef', () => {
136
+ expect(
137
+ envelopeConverter.convert({
138
+ ...validEnvelopeObject,
139
+ temporal: { valid_at: 10 },
140
+ embeddingRef: 'vec-1'
141
+ })
142
+ ).toSucceedAndSatisfy((envelope) => {
143
+ expect(envelope.temporal).toEqual({ valid_at: 10 });
144
+ expect(envelope.embeddingRef).toBe('vec-1');
145
+ });
146
+ });
147
+
148
+ test('accepts a null embeddingRef (not embedded)', () => {
149
+ expect(envelopeConverter.convert({ ...validEnvelopeObject, embeddingRef: null })).toSucceedAndSatisfy(
150
+ (envelope) => {
151
+ expect(envelope.embeddingRef).toBeNull();
152
+ }
153
+ );
154
+ });
155
+
156
+ test('fails when embeddingRef is neither a string nor null', () => {
157
+ expect(envelopeConverter.convert({ ...validEnvelopeObject, embeddingRef: 42 })).toFail();
158
+ });
159
+
160
+ test('fails when a required field is missing', () => {
161
+ const missing: Record<string, unknown> = { ...validEnvelopeObject };
162
+ delete missing.contentHash;
163
+ expect(envelopeConverter.convert(missing)).toFail();
164
+ });
165
+ });
166
+
167
+ describe('envelopeYamlConverter', () => {
168
+ test('parses an envelope from a YAML string', () => {
169
+ const yaml = serializeMemoryFile(
170
+ envelopeConverter.convert(validEnvelopeObject).orThrow(),
171
+ 'body text'
172
+ ).orThrow();
173
+ const frontmatter = splitFrontmatter(yaml).orThrow().frontmatter;
174
+ expect(envelopeYamlConverter.convert(frontmatter)).toSucceedAndSatisfy((envelope) => {
175
+ expect(envelope.id).toBe('intro-to-rust');
176
+ });
177
+ });
178
+
179
+ test('fails on a YAML string that is not an object', () => {
180
+ expect(envelopeYamlConverter.convert('just a scalar')).toFail();
181
+ });
182
+ });
183
+
184
+ describe('splitFrontmatter / joinFrontmatter', () => {
185
+ test('splits a well-formed memory file', () => {
186
+ const raw = '---\nid: x\nkind: knowledge\n---\nThe body.\nSecond line.';
187
+ expect(splitFrontmatter(raw)).toSucceedWith({
188
+ frontmatter: 'id: x\nkind: knowledge',
189
+ body: 'The body.\nSecond line.'
190
+ });
191
+ });
192
+
193
+ test('strips trailing CR so CRLF-authored files parse identically to LF', () => {
194
+ const raw = '---\r\nid: x\r\nkind: knowledge\r\n---\r\nThe body.\r\nSecond line.';
195
+ expect(splitFrontmatter(raw)).toSucceedWith({
196
+ frontmatter: 'id: x\nkind: knowledge',
197
+ body: 'The body.\nSecond line.'
198
+ });
199
+ });
200
+
201
+ test('fails when the opening delimiter is missing', () => {
202
+ expect(splitFrontmatter('no frontmatter here')).toFailWith(/missing opening frontmatter delimiter/i);
203
+ });
204
+
205
+ test('fails when the closing delimiter is missing', () => {
206
+ expect(splitFrontmatter('---\nid: x\nstill going')).toFailWith(/missing closing frontmatter delimiter/i);
207
+ });
208
+
209
+ test('join adds a trailing newline to the frontmatter when absent', () => {
210
+ expect(joinFrontmatter('id: x', 'body')).toBe('---\nid: x\n---\nbody');
211
+ });
212
+
213
+ test('join does not double the trailing newline when present', () => {
214
+ expect(joinFrontmatter('id: x\n', 'body')).toBe('---\nid: x\n---\nbody');
215
+ });
216
+
217
+ test('split and join round-trip', () => {
218
+ const raw = '---\nid: x\n---\nbody content';
219
+ const parts = splitFrontmatter(raw).orThrow();
220
+ expect(joinFrontmatter(parts.frontmatter, parts.body)).toBe(raw);
221
+ });
222
+ });
223
+
224
+ describe('parseMemoryFile / serializeMemoryFile', () => {
225
+ let registry: BodyConverterRegistry;
226
+ const knowledgeKind: Kind = 'knowledge' as Kind;
227
+
228
+ beforeEach(() => {
229
+ registry = BodyConverterRegistry.create().orThrow();
230
+ registry.register(knowledgeKind, Converters.string);
231
+ });
232
+
233
+ test('serializes then parses a record round-trip', () => {
234
+ const envelope = envelopeConverter.convert(validEnvelopeObject).orThrow();
235
+ const file = serializeMemoryFile(envelope, 'The knowledge body.').orThrow();
236
+ expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {
237
+ expect(record.envelope.id).toBe('intro-to-rust');
238
+ expect(record.body).toBe('The knowledge body.');
239
+ });
240
+ });
241
+
242
+ test('fails when the frontmatter cannot be split', () => {
243
+ expect(parseMemoryFile('no delimiters', registry)).toFailWith(/missing opening frontmatter delimiter/i);
244
+ });
245
+
246
+ test('fails with envelope context when the envelope is invalid', () => {
247
+ const file = '---\nid: x\n---\nbody';
248
+ expect(parseMemoryFile(file, registry)).toFailWith(/invalid envelope/i);
249
+ });
250
+
251
+ test('fails with body context when the body fails the registered converter', () => {
252
+ const envelope = envelopeConverter.convert({ ...validEnvelopeObject, kind: 'unregistered' }).orThrow();
253
+ const file = serializeMemoryFile(envelope, 'body').orThrow();
254
+ expect(parseMemoryFile(file, registry)).toFailWith(/invalid body.*no converter registered/i);
255
+ });
256
+
257
+ test('serialize fails (with id context) when the envelope cannot be dumped to YAML', () => {
258
+ const unserializable = { ...validEnvelopeObject, weird: BigInt(10) } as unknown as IMemoryEnvelope;
259
+ expect(serializeMemoryFile(unserializable, 'body')).toFailWith(/failed to serialize envelope/i);
260
+ });
261
+ });
@@ -0,0 +1,187 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import '@fgv/ts-utils-jest';
7
+ import {
8
+ IIndexedMemoryRecord,
9
+ IMemoryRecord,
10
+ Kind,
11
+ MemoryId,
12
+ MemoryIndex,
13
+ MemoryScopeKey,
14
+ Tag,
15
+ envelopeConverter
16
+ } from '../../../index';
17
+
18
+ interface IRecordSpec {
19
+ readonly id: string;
20
+ readonly scope?: string;
21
+ readonly kind?: string;
22
+ readonly tags?: ReadonlyArray<string>;
23
+ readonly links?: ReadonlyArray<string>;
24
+ readonly updated?: number;
25
+ readonly seq?: number;
26
+ }
27
+
28
+ function makeEntry(spec: IRecordSpec): IIndexedMemoryRecord {
29
+ const record: IMemoryRecord<unknown> = {
30
+ envelope: envelopeConverter
31
+ .convert({
32
+ id: spec.id,
33
+ entityId: spec.id,
34
+ kind: spec.kind ?? 'knowledge',
35
+ tags: spec.tags ?? [],
36
+ links: (spec.links ?? []).map((target) => ({ type: 'rel', target })),
37
+ created: 0,
38
+ updated: spec.updated ?? 0,
39
+ seq: spec.seq ?? 0,
40
+ contentHash: 'h',
41
+ provenance: { source: 'agent' }
42
+ })
43
+ .orThrow(),
44
+ body: `body-${spec.id}`
45
+ };
46
+ return { scope: (spec.scope ?? 'knowledge') as MemoryScopeKey, record };
47
+ }
48
+
49
+ function ids(records: ReadonlyArray<IMemoryRecord<unknown>>): ReadonlyArray<string> {
50
+ return records.map((r) => r.envelope.id as string);
51
+ }
52
+
53
+ describe('MemoryIndex', () => {
54
+ let index: MemoryIndex;
55
+
56
+ beforeEach(() => {
57
+ index = MemoryIndex.create().orThrow();
58
+ });
59
+
60
+ describe('create', () => {
61
+ test('starts empty', () => {
62
+ expect(index.entries()).toHaveLength(0);
63
+ expect(index.byKind('knowledge' as Kind)).toHaveLength(0);
64
+ expect(index.byTag('any' as Tag)).toHaveLength(0);
65
+ expect(index.byRecency()).toHaveLength(0);
66
+ expect(index.backlinks('none' as MemoryId)).toHaveLength(0);
67
+ });
68
+ });
69
+
70
+ describe('rebuild', () => {
71
+ test('indexes every entry and reports the count', () => {
72
+ const entries = [
73
+ makeEntry({ id: 'a', tags: ['x'], links: ['b'] }),
74
+ makeEntry({ id: 'b', tags: ['x', 'y'] })
75
+ ];
76
+ expect(index.rebuild(entries)).toSucceedWith(2);
77
+ expect(index.entries()).toHaveLength(2);
78
+ expect([...ids(index.byKind('knowledge' as Kind))].sort()).toEqual(['a', 'b']);
79
+ expect([...ids(index.byTag('x' as Tag))].sort()).toEqual(['a', 'b']);
80
+ expect(ids(index.byTag('y' as Tag))).toEqual(['b']);
81
+ expect(index.backlinks('b' as MemoryId)).toEqual(['a']);
82
+ });
83
+
84
+ test('clears prior state', () => {
85
+ index.rebuild([makeEntry({ id: 'a', tags: ['x'] })]).orThrow();
86
+ expect(index.rebuild([makeEntry({ id: 'c', tags: ['z'] })])).toSucceedWith(1);
87
+ expect(ids(index.entries().map((e) => e.record))).toEqual(['c']);
88
+ expect(index.byTag('x' as Tag)).toHaveLength(0);
89
+ });
90
+ });
91
+
92
+ describe('patch', () => {
93
+ test('put inserts a new entry and returns it', () => {
94
+ const entry = makeEntry({ id: 'a', tags: ['x'], links: ['b'] });
95
+ expect(index.patch('put', entry)).toSucceedWith(entry);
96
+ expect(ids(index.byKind('knowledge' as Kind))).toEqual(['a']);
97
+ expect(index.backlinks('b' as MemoryId)).toEqual(['a']);
98
+ });
99
+
100
+ test('put on an existing key replaces stale associations', () => {
101
+ index.patch('put', makeEntry({ id: 'a', kind: 'knowledge', tags: ['old'], links: ['b'] })).orThrow();
102
+ index.patch('put', makeEntry({ id: 'a', kind: 'note', tags: ['new'], links: ['c'] })).orThrow();
103
+ expect(index.entries()).toHaveLength(1);
104
+ expect(index.byTag('old' as Tag)).toHaveLength(0);
105
+ expect(ids(index.byTag('new' as Tag))).toEqual(['a']);
106
+ expect(index.byKind('knowledge' as Kind)).toHaveLength(0);
107
+ expect(ids(index.byKind('note' as Kind))).toEqual(['a']);
108
+ expect(index.backlinks('b' as MemoryId)).toHaveLength(0);
109
+ expect(index.backlinks('c' as MemoryId)).toEqual(['a']);
110
+ });
111
+
112
+ test('delete removes the entry and its associations', () => {
113
+ const entry = makeEntry({ id: 'a', tags: ['x'], links: ['b'] });
114
+ index.patch('put', entry).orThrow();
115
+ expect(index.patch('delete', entry)).toSucceedWith(entry);
116
+ expect(index.entries()).toHaveLength(0);
117
+ expect(index.byTag('x' as Tag)).toHaveLength(0);
118
+ expect(index.backlinks('b' as MemoryId)).toHaveLength(0);
119
+ });
120
+
121
+ test('delete of an absent key is a no-op that still succeeds', () => {
122
+ const entry = makeEntry({ id: 'ghost' });
123
+ expect(index.patch('delete', entry)).toSucceedWith(entry);
124
+ expect(index.entries()).toHaveLength(0);
125
+ });
126
+
127
+ test('distinguishes entries with the same id across scopes', () => {
128
+ index.patch('put', makeEntry({ id: 'turn-0', scope: 'conversations/c1' })).orThrow();
129
+ index.patch('put', makeEntry({ id: 'turn-0', scope: 'conversations/c2' })).orThrow();
130
+ expect(index.entries()).toHaveLength(2);
131
+ });
132
+ });
133
+
134
+ describe('byRecency', () => {
135
+ test('orders most-recently-updated first, breaking ties by seq', () => {
136
+ index
137
+ .rebuild([
138
+ makeEntry({ id: 'old', updated: 100, seq: 1 }),
139
+ makeEntry({ id: 'newest', updated: 300, seq: 3 }),
140
+ makeEntry({ id: 'tie-lo', updated: 200, seq: 4 }),
141
+ makeEntry({ id: 'tie-hi', updated: 200, seq: 9 })
142
+ ])
143
+ .orThrow();
144
+ expect(ids(index.byRecency())).toEqual(['newest', 'tie-hi', 'tie-lo', 'old']);
145
+ });
146
+ });
147
+
148
+ describe('backlinks', () => {
149
+ test('aggregates multiple sources pointing at one target', () => {
150
+ index
151
+ .rebuild([makeEntry({ id: 'a', links: ['target'] }), makeEntry({ id: 'b', links: ['target'] })])
152
+ .orThrow();
153
+ expect([...index.backlinks('target' as MemoryId)].sort()).toEqual(['a', 'b']);
154
+ });
155
+
156
+ test('drops the target set once the last source is removed', () => {
157
+ const a = makeEntry({ id: 'a', links: ['target'] });
158
+ index.patch('put', a).orThrow();
159
+ index.patch('delete', a).orThrow();
160
+ expect(index.backlinks('target' as MemoryId)).toHaveLength(0);
161
+ });
162
+
163
+ test('tracks same-id sources across scopes independently', () => {
164
+ // Two distinct records that share the id 'a' under different scopes, both
165
+ // linking 'target'. Removing one must NOT drop the other's inbound edge.
166
+ const a1 = makeEntry({ id: 'a', scope: 'conversations/c1', links: ['target'] });
167
+ const a2 = makeEntry({ id: 'a', scope: 'conversations/c2', links: ['target'] });
168
+ index.patch('put', a1).orThrow();
169
+ index.patch('put', a2).orThrow();
170
+ expect(index.backlinks('target' as MemoryId)).toEqual(['a', 'a']);
171
+ index.patch('delete', a1).orThrow();
172
+ expect(index.backlinks('target' as MemoryId)).toEqual(['a']);
173
+ });
174
+ });
175
+
176
+ describe('duplicate associations', () => {
177
+ test('a record with duplicate tags and links is cleaned up exactly once on delete', () => {
178
+ const entry = makeEntry({ id: 'a', tags: ['dup', 'dup'], links: ['t', 't'] });
179
+ index.patch('put', entry).orThrow();
180
+ expect(ids(index.byTag('dup' as Tag))).toEqual(['a']);
181
+ expect(index.backlinks('t' as MemoryId)).toEqual(['a']);
182
+ index.patch('delete', entry).orThrow();
183
+ expect(index.byTag('dup' as Tag)).toHaveLength(0);
184
+ expect(index.backlinks('t' as MemoryId)).toHaveLength(0);
185
+ });
186
+ });
187
+ });