@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,7 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export * from './fileTreeMemoryStore';
7
+ export * from './scopeEncoding';
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Result, fail, mapResults, succeed } from '@fgv/ts-utils';
7
+ import { MemoryScopeKey, assertPortableFilenameStem } from '../types';
8
+
9
+ /**
10
+ * Encode a {@link MemoryScopeKey} to its on-disk directory path. The scope may
11
+ * be multi-segment (`/`-separated) — each component is validated independently
12
+ * against the POSIX portable filename set (via
13
+ * {@link assertPortableFilenameStem}), then rejoined with `/`.
14
+ *
15
+ * @remarks
16
+ * This is the day-one multi-segment scope resolver design-lock §9.1 calls for:
17
+ * the knowledge scope is the single segment `knowledge`, while the Phase-C MTM
18
+ * scope `conversations/<conversationId>` is two segments. Validating each
19
+ * segment independently (rather than the whole path as one stem, which the
20
+ * `ts-prompt-assist` default encoding does) keeps the Phase-C codec additive —
21
+ * no scope-encoding change is needed when MTM ships.
22
+ * @public
23
+ */
24
+ export function defaultMemoryScopeEncoding(scope: MemoryScopeKey): Result<string> {
25
+ // `''.split('/')` yields `['']`, never `[]`, so a single empty-segment check
26
+ // covers the empty-scope case too.
27
+ const segments: string[] = scope.split('/');
28
+ if (segments.some((s) => s.length === 0)) {
29
+ return fail(`scope '${scope}': must not contain empty path segments`);
30
+ }
31
+ return mapResults(
32
+ segments.map((segment) =>
33
+ assertPortableFilenameStem(segment).withErrorFormat((msg) => `scope '${scope}': ${msg}`)
34
+ )
35
+ ).onSuccess((validated) => succeed(validated.join('/')));
36
+ }
@@ -0,0 +1,138 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { EntityId, Kind, LinkType, MemoryId, Tag } from './ids';
7
+
8
+ /**
9
+ * Origin of a provenance attribution. Open vocabulary: the three named
10
+ * sources are conventional, but the `(string & {})` arm admits any other
11
+ * source string without resignature.
12
+ * @public
13
+ */
14
+ export type ProvenanceSource = 'agent' | 'host-ingest' | 'human' | (string & {});
15
+
16
+ /**
17
+ * Structured provenance for a record or an edge. Never a flat enum — the
18
+ * `[key: string]: unknown` index signature lets a consumer attach an opaque
19
+ * domain payload (e.g. PersonAIlity's sentiment / epistemic blocks) without
20
+ * changing this interface, while still satisfying the no-`any` rule.
21
+ * @public
22
+ */
23
+ export interface IProvenance {
24
+ /** Where the attribution came from. */
25
+ readonly source: ProvenanceSource;
26
+ /** Optional human or agent identifier responsible for the write. */
27
+ readonly by?: string;
28
+ /** Optional model identifier, when a model produced the content. */
29
+ readonly model?: string;
30
+ /** Optional confidence in `[0, 1]`. */
31
+ readonly confidence?: number;
32
+ /** Back-link to the source experience record. Enables the cross-kind provenance spine. */
33
+ readonly derivedFrom?: MemoryId;
34
+ /** Opaque extension payload — consumer-owned, never interpreted by the store. */
35
+ readonly [key: string]: unknown;
36
+ }
37
+
38
+ /**
39
+ * An attributed link between two records. Carries the relation type, the
40
+ * target id, and optional confidence / provenance / world-truth validity.
41
+ * Replaces bare string references (e.g. PersonAIlity's `IMtmRef` becomes an
42
+ * `IEdge` with `type: LinkType('mtm-ref')`).
43
+ * @public
44
+ */
45
+ export interface IEdge {
46
+ /** Open-vocabulary relation type. */
47
+ readonly type: LinkType;
48
+ /** The linked-to record. */
49
+ readonly target: MemoryId;
50
+ /** Optional confidence in `[0, 1]`. */
51
+ readonly confidence?: number;
52
+ /** Optional structured provenance for the link itself. */
53
+ readonly provenance?: IProvenance;
54
+ /** World-truth validity start (epoch ms). Present only on temporal edges. */
55
+ readonly valid_at?: number;
56
+ /**
57
+ * World-truth validity end (epoch ms). `null` = still valid; absent = no
58
+ * temporal extent.
59
+ */
60
+ // eslint-disable-next-line @rushstack/no-new-null -- null is a meaningful value here (still-valid) distinct from absent (no temporal extent); design-lock §2.3
61
+ readonly invalid_at?: number | null;
62
+ }
63
+
64
+ /**
65
+ * Optional bi-temporal validity block on an envelope. Present only on
66
+ * temporal kinds; absent = zero cost for atemporal kinds.
67
+ * @public
68
+ */
69
+ export interface ITemporalBlock {
70
+ /** World-truth validity start (epoch ms). */
71
+ readonly valid_at?: number;
72
+ /** World-truth validity end (epoch ms). `null` = still valid. */
73
+ // eslint-disable-next-line @rushstack/no-new-null -- null is a meaningful value here (still-valid) distinct from absent; design-lock §2.4
74
+ readonly invalid_at?: number | null;
75
+ }
76
+
77
+ /**
78
+ * The invariant identity + transaction-time envelope carried by every memory
79
+ * record, independent of the per-kind body.
80
+ * @public
81
+ */
82
+ export interface IMemoryEnvelope {
83
+ // --- Core identity ---
84
+ /** Stable file-stem identifier. MUST equal the on-disk filename stem. */
85
+ readonly id: MemoryId;
86
+ /** Consumer-supplied domain key. Equals {@link IMemoryEnvelope.id | id} for non-temporal kinds. */
87
+ readonly entityId: EntityId;
88
+ /** Consumer-registered kind; dispatches the body Converter. */
89
+ readonly kind: Kind;
90
+ /** Open-vocabulary tags. */
91
+ readonly tags: ReadonlyArray<Tag>;
92
+ /** Attributed outbound edges. */
93
+ readonly links: ReadonlyArray<IEdge>;
94
+
95
+ // --- Transaction-time metadata (always present) ---
96
+ /** Epoch ms of the first write. Immutable after creation. */
97
+ readonly created: number;
98
+ /** Epoch ms of the most recent write. */
99
+ readonly updated: number;
100
+ /**
101
+ * Monotonic write counter within the store instance, assigned by the store
102
+ * on every successful put. Enables stable cursor paging over observation
103
+ * records without a full walk.
104
+ */
105
+ readonly seq: number;
106
+ /**
107
+ * Content hash over the canonical `{ kind, body, links }`. The dedup key:
108
+ * an exact match is a no-op upsert that returns the existing record.
109
+ */
110
+ readonly contentHash: string;
111
+ /** Structured provenance (never a flat enum). */
112
+ readonly provenance: IProvenance;
113
+
114
+ // --- Optional temporal block ---
115
+ /** Bi-temporal validity. Present only on temporal kinds. */
116
+ readonly temporal?: ITemporalBlock;
117
+
118
+ /**
119
+ * Vector-index entry reference, set by the vector index on write. `null` =
120
+ * not embedded; absent = same as `null` (backwards-compat seam).
121
+ */
122
+ // eslint-disable-next-line @rushstack/no-new-null -- null is the explicit "not embedded" sentinel distinct from absent (backwards-compat seam); design-lock §2.5
123
+ readonly embeddingRef?: string | null;
124
+ }
125
+
126
+ /**
127
+ * A complete memory record: the invariant {@link IMemoryEnvelope} plus the
128
+ * typed, per-kind body. The store's public surface uses
129
+ * `IMemoryRecord<unknown>`; consumers narrow `TBody` by checking
130
+ * `envelope.kind` and validating through the registered Converter.
131
+ * @public
132
+ */
133
+ export interface IMemoryRecord<TBody = unknown> {
134
+ /** The invariant identity + transaction-time envelope. */
135
+ readonly envelope: IMemoryEnvelope;
136
+ /** The per-kind, Converter-validated body. */
137
+ readonly body: TBody;
138
+ }
@@ -0,0 +1,57 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Result, fail, succeed } from '@fgv/ts-utils';
7
+
8
+ /**
9
+ * Reserved Windows device basenames. Includes `COM0..9` and `LPT0..9` — the
10
+ * 0-suffixed variants were added in Windows 11 / Server 2022. Matched
11
+ * case-insensitively against the basename (text before the first `.`).
12
+ */
13
+ const RESERVED_WIN_DEVICES: ReadonlySet<string> = new Set<string>([
14
+ 'CON',
15
+ 'PRN',
16
+ 'AUX',
17
+ 'NUL',
18
+ ...Array.from({ length: 10 }, (__v, i) => `COM${i}`),
19
+ ...Array.from({ length: 10 }, (__v, i) => `LPT${i}`)
20
+ ]);
21
+
22
+ const PORTABLE_FILENAME_RE: RegExp = /^[A-Za-z0-9._-]+$/;
23
+
24
+ /**
25
+ * Validates a single filename stem against the POSIX portable filename set
26
+ * (`[A-Za-z0-9._-]`), rejecting a leading or trailing `.` and reserved Windows
27
+ * device names. This is the cross-platform filename-stem contract for the
28
+ * package: {@link MemoryId} values, the concrete {@link IIdentityCodec}
29
+ * implementations, and the store's `verifyFilenameId` all gate on it.
30
+ *
31
+ * @remarks
32
+ * A trailing `.` is rejected because Windows silently strips trailing dots from
33
+ * filenames, so such a stem would not round-trip verbatim through the file
34
+ * layer even though it is otherwise portable-set-valid.
35
+ * @public
36
+ */
37
+ export function assertPortableFilenameStem(stem: string): Result<string> {
38
+ if (stem.length === 0) {
39
+ return fail('idStem: must be a non-empty string');
40
+ }
41
+ if (stem.startsWith('.')) {
42
+ return fail(`idStem '${stem}': may not begin with '.'`);
43
+ }
44
+ if (stem.endsWith('.')) {
45
+ return fail(`idStem '${stem}': may not end with '.' (Windows strips trailing dots)`);
46
+ }
47
+ if (!PORTABLE_FILENAME_RE.test(stem)) {
48
+ return fail(
49
+ `idStem '${stem}': contains characters outside the POSIX portable filename set ([A-Za-z0-9._-])`
50
+ );
51
+ }
52
+ const basename: string = stem.split('.')[0];
53
+ if (RESERVED_WIN_DEVICES.has(basename.toUpperCase())) {
54
+ return fail(`idStem '${stem}': basename '${basename}' matches a reserved Windows device name`);
55
+ }
56
+ return succeed(stem);
57
+ }
@@ -0,0 +1,263 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Result, fail, succeed } from '@fgv/ts-utils';
7
+ import { Convert, EntityId, MemoryScopeKey } from './ids';
8
+ import { assertPortableFilenameStem } from './filenameSafety';
9
+
10
+ /**
11
+ * The FileTree storage address an {@link IIdentityCodec} maps a domain key to.
12
+ * @public
13
+ */
14
+ export interface IIdentityCodecResult {
15
+ /** Scope path segment (may be multi-level, e.g. `conversations/<id>`). */
16
+ readonly scope: MemoryScopeKey;
17
+ /** Filename stem (the part before `.md`). Filename-safe after encoding. */
18
+ readonly idStem: string;
19
+ /**
20
+ * Whether this kind uses a versioned layout (temporal: multiple files per
21
+ * entity) vs. a flat layout (one file per entity). Non-temporal = always
22
+ * `false`.
23
+ */
24
+ readonly isVersioned: boolean;
25
+ }
26
+
27
+ /**
28
+ * Maps a consumer-supplied domain key ⇄ a FileTree storage address. Injected
29
+ * per kind so the store never touches raw domain keys: the codec owns all
30
+ * filename escaping and the flat-vs-versioned layout dispatch.
31
+ * @public
32
+ */
33
+ export interface IIdentityCodec {
34
+ /**
35
+ * Encode a consumer-supplied entity id to a FileTree address. Deterministic
36
+ * and pure — no I/O.
37
+ */
38
+ encode(entityId: EntityId): Result<IIdentityCodecResult>;
39
+
40
+ /**
41
+ * Decode a FileTree address back to the original {@link EntityId}. The exact
42
+ * inverse of {@link IIdentityCodec.encode | encode} for non-versioned kinds.
43
+ */
44
+ decode(scope: MemoryScopeKey, encodedStem: string): Result<EntityId>;
45
+
46
+ /**
47
+ * Assert that `encode(decode(scope, stem)).idStem === stem`. Used by the
48
+ * store's `verifyFilenameId` check on load.
49
+ */
50
+ verifyRoundTrip(scope: MemoryScopeKey, stem: string): Result<true>;
51
+ }
52
+
53
+ /**
54
+ /**
55
+ * Identity codec for the knowledge kind family. A knowledge entity is keyed
56
+ * by its consumer-supplied `docId`, which is used verbatim as the filename
57
+ * stem under the flat `knowledge` scope.
58
+ *
59
+ * @remarks
60
+ * - `encode`: scope = `knowledge`, idStem = `docId`, `isVersioned = false`.
61
+ * - `decode`: brands the stem back to an {@link EntityId}.
62
+ * - Escaping: the `docId` must match the POSIX portable filename set.
63
+ * - Layout: `vault/knowledge/<docId>.md`.
64
+ * @public
65
+ */
66
+ export class KnowledgeIdentityCodec implements IIdentityCodec {
67
+ /** The fixed scope for every knowledge entity. */
68
+ public static readonly scope: MemoryScopeKey = 'knowledge' as MemoryScopeKey;
69
+
70
+ /** {@inheritDoc IIdentityCodec.encode} */
71
+ public encode(entityId: EntityId): Result<IIdentityCodecResult> {
72
+ return assertPortableFilenameStem(entityId).onSuccess((idStem) =>
73
+ succeed({ scope: KnowledgeIdentityCodec.scope, idStem, isVersioned: false })
74
+ );
75
+ }
76
+
77
+ /** {@inheritDoc IIdentityCodec.decode} */
78
+ public decode(scope: MemoryScopeKey, encodedStem: string): Result<EntityId> {
79
+ if (scope !== KnowledgeIdentityCodec.scope) {
80
+ return fail(
81
+ `knowledge codec: scope '${scope}' does not match expected scope '${KnowledgeIdentityCodec.scope}'`
82
+ );
83
+ }
84
+ return assertPortableFilenameStem(encodedStem).onSuccess((stem) => Convert.entityId.convert(stem));
85
+ }
86
+
87
+ /** {@inheritDoc IIdentityCodec.verifyRoundTrip} */
88
+ public verifyRoundTrip(scope: MemoryScopeKey, stem: string): Result<true> {
89
+ return this.decode(scope, stem)
90
+ .onSuccess((entityId) => this.encode(entityId))
91
+ .onSuccess((encoded) => {
92
+ /* c8 ignore start -- defensive: for the identity (knowledge) codec, encode(decode(stem)).idStem always equals stem when both succeed; the guard exists for the non-identity codecs (LTM/MTM, Phase C) that reuse this contract */
93
+ if (encoded.idStem !== stem) {
94
+ return fail(
95
+ `knowledge codec: round-trip mismatch for stem '${stem}' (re-encoded to '${encoded.idStem}')`
96
+ );
97
+ }
98
+ /* c8 ignore stop */
99
+ return succeed(true);
100
+ });
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Identity codec for the long-term-memory (LTM) kind family. An LTM entity is
106
+ * keyed by its `conversationId`, used verbatim as the filename stem under the
107
+ * flat `conversations` scope.
108
+ *
109
+ * @remarks
110
+ * - `encode`: scope = `conversations`, idStem = `conversationId`, `isVersioned = false`.
111
+ * - `decode`: brands the stem back to an {@link EntityId}.
112
+ * - Escaping: the `conversationId` must match the POSIX portable filename set.
113
+ * - Layout: `vault/conversations/<conversationId>.md`.
114
+ *
115
+ * An LTM file (`conversations/<id>.md`) and the MTM subtree
116
+ * (`conversations/<id>/turn-N.md`) coexist — a file and a same-named directory
117
+ * are independent on every supported FileTree backend.
118
+ * @public
119
+ */
120
+ export class LtmIdentityCodec implements IIdentityCodec {
121
+ /** The fixed scope for every LTM entity. */
122
+ public static readonly scope: MemoryScopeKey = 'conversations' as MemoryScopeKey;
123
+
124
+ /** {@inheritDoc IIdentityCodec.encode} */
125
+ public encode(entityId: EntityId): Result<IIdentityCodecResult> {
126
+ return assertPortableFilenameStem(entityId).onSuccess((idStem) =>
127
+ succeed({ scope: LtmIdentityCodec.scope, idStem, isVersioned: false })
128
+ );
129
+ }
130
+
131
+ /** {@inheritDoc IIdentityCodec.decode} */
132
+ public decode(scope: MemoryScopeKey, encodedStem: string): Result<EntityId> {
133
+ if (scope !== LtmIdentityCodec.scope) {
134
+ return fail(`LTM codec: scope '${scope}' does not match expected scope '${LtmIdentityCodec.scope}'`);
135
+ }
136
+ return assertPortableFilenameStem(encodedStem).onSuccess((stem) => Convert.entityId.convert(stem));
137
+ }
138
+
139
+ /** {@inheritDoc IIdentityCodec.verifyRoundTrip} */
140
+ public verifyRoundTrip(scope: MemoryScopeKey, stem: string): Result<true> {
141
+ return this.decode(scope, stem)
142
+ .onSuccess((entityId) => this.encode(entityId))
143
+ .onSuccess((encoded) => {
144
+ /* c8 ignore start -- defensive: LTM encode(decode(stem)).idStem always equals stem when both succeed (identity mapping); guard preserves the contract for direct callers */
145
+ if (encoded.idStem !== stem) {
146
+ return fail(
147
+ `LTM codec: round-trip mismatch for stem '${stem}' (re-encoded to '${encoded.idStem}')`
148
+ );
149
+ }
150
+ /* c8 ignore stop */
151
+ return succeed(true);
152
+ });
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Identity codec for the medium-term-memory (MTM) kind family. An MTM entity is
158
+ * keyed by the colon-composite `<conversationId>:<turnIndex>` and maps into a
159
+ * per-conversation subtree.
160
+ *
161
+ * @remarks
162
+ * - `encode`: splits the entity id on `:`; scope =
163
+ * `conversations/<conversationId>` (multi-segment), idStem = `turn-<turnIndex>`,
164
+ * `isVersioned = false`.
165
+ * - `decode`: reverses scope `conversations/<id>` + stem `turn-<N>` to the
166
+ * composite `<id>:<N>`.
167
+ * - Escaping: `conversationId` must match the POSIX portable filename set (so it
168
+ * contains no `/` or `:`); `turnIndex` must be a non-negative integer string,
169
+ * preserved verbatim so the round-trip is exact.
170
+ * - Layout: `vault/conversations/<conversationId>/turn-<N>.md`. The `/` in the
171
+ * scope is handled by the store's multi-segment scope resolver.
172
+ *
173
+ * **Verbatim turn index (caller-canonicalization note).** The turn index is
174
+ * preserved exactly — `conv-1:7` and `conv-1:007` are DISTINCT entities mapping
175
+ * to distinct files (`turn-7.md` / `turn-007.md`). Verbatim preservation is what
176
+ * makes the round-trip exact, but it means a caller that formats turn indices
177
+ * inconsistently (some zero-padded, some not) will silently mint separate
178
+ * entities. Callers should canonicalize to one form (e.g. no leading zeros).
179
+ * @public
180
+ */
181
+ export class MtmIdentityCodec implements IIdentityCodec {
182
+ /** The top-level scope segment under which every MTM subtree lives. */
183
+ public static readonly rootScopeSegment: string = 'conversations';
184
+ /** The fixed filename-stem prefix for a turn record. */
185
+ public static readonly turnStemPrefix: string = 'turn-';
186
+
187
+ /** A non-negative integer string (the turn index), preserved verbatim. */
188
+ private static readonly _turnIndexRe: RegExp = /^\d+$/;
189
+
190
+ /** {@inheritDoc IIdentityCodec.encode} */
191
+ public encode(entityId: EntityId): Result<IIdentityCodecResult> {
192
+ const parts: string[] = entityId.split(':');
193
+ if (parts.length !== 2) {
194
+ return fail(`MTM codec: entity id '${entityId}' must be a '<conversationId>:<turnIndex>' composite`);
195
+ }
196
+ const [conversationId, turnIndex] = parts;
197
+ return assertPortableFilenameStem(conversationId)
198
+ .withErrorFormat((msg) => `MTM codec: conversationId '${conversationId}': ${msg}`)
199
+ .onSuccess(() => MtmIdentityCodec._assertTurnIndex(turnIndex))
200
+ .onSuccess(() =>
201
+ succeed({
202
+ scope: `${MtmIdentityCodec.rootScopeSegment}/${conversationId}` as MemoryScopeKey,
203
+ idStem: `${MtmIdentityCodec.turnStemPrefix}${turnIndex}`,
204
+ isVersioned: false
205
+ })
206
+ );
207
+ }
208
+
209
+ /** {@inheritDoc IIdentityCodec.decode} */
210
+ public decode(scope: MemoryScopeKey, encodedStem: string): Result<EntityId> {
211
+ return MtmIdentityCodec._conversationIdFromScope(scope).onSuccess((conversationId) =>
212
+ MtmIdentityCodec._turnIndexFromStem(encodedStem).onSuccess((turnIndex) =>
213
+ Convert.entityId.convert(`${conversationId}:${turnIndex}`)
214
+ )
215
+ );
216
+ }
217
+
218
+ /** {@inheritDoc IIdentityCodec.verifyRoundTrip} */
219
+ public verifyRoundTrip(scope: MemoryScopeKey, stem: string): Result<true> {
220
+ return this.decode(scope, stem)
221
+ .onSuccess((entityId) => this.encode(entityId))
222
+ .onSuccess((encoded) => {
223
+ /* c8 ignore start -- defensive: MTM encode(decode(scope, stem)) reproduces the same scope+stem whenever both succeed (no normalization); guard preserves the contract for direct callers */
224
+ if (encoded.idStem !== stem || encoded.scope !== scope) {
225
+ return fail(
226
+ `MTM codec: round-trip mismatch for scope '${scope}' stem '${stem}' (re-encoded to scope '${encoded.scope}' stem '${encoded.idStem}')`
227
+ );
228
+ }
229
+ /* c8 ignore stop */
230
+ return succeed(true);
231
+ });
232
+ }
233
+
234
+ /** Validate and extract the `conversationId` from a `conversations/<id>` scope. */
235
+ private static _conversationIdFromScope(scope: MemoryScopeKey): Result<string> {
236
+ const segments: string[] = scope.split('/');
237
+ if (segments.length !== 2 || segments[0] !== MtmIdentityCodec.rootScopeSegment) {
238
+ return fail(
239
+ `MTM codec: scope '${scope}' must be '${MtmIdentityCodec.rootScopeSegment}/<conversationId>'`
240
+ );
241
+ }
242
+ const conversationId: string = segments[1];
243
+ return assertPortableFilenameStem(conversationId)
244
+ .withErrorFormat((msg) => `MTM codec: conversationId '${conversationId}': ${msg}`)
245
+ .onSuccess(() => succeed(conversationId));
246
+ }
247
+
248
+ /** Validate and extract the turn-index digits from a `turn-<N>` stem. */
249
+ private static _turnIndexFromStem(stem: string): Result<string> {
250
+ if (!stem.startsWith(MtmIdentityCodec.turnStemPrefix)) {
251
+ return fail(`MTM codec: stem '${stem}' must begin with '${MtmIdentityCodec.turnStemPrefix}'`);
252
+ }
253
+ return MtmIdentityCodec._assertTurnIndex(stem.slice(MtmIdentityCodec.turnStemPrefix.length));
254
+ }
255
+
256
+ /** Assert a string is a non-negative integer (the turn index). */
257
+ private static _assertTurnIndex(turnIndex: string): Result<string> {
258
+ if (!MtmIdentityCodec._turnIndexRe.test(turnIndex)) {
259
+ return fail(`MTM codec: turnIndex '${turnIndex}' must be a non-negative integer`);
260
+ }
261
+ return succeed(turnIndex);
262
+ }
263
+ }
@@ -0,0 +1,124 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Brand, Converter, Converters, Result, fail, succeed } from '@fgv/ts-utils';
7
+ import { assertPortableFilenameStem } from './filenameSafety';
8
+
9
+ /**
10
+ * Stable file-stem identifier for a memory record. Equals the codec-produced
11
+ * `idStem` and the on-disk filename stem (`verifyFilenameId` enforces the
12
+ * round-trip).
13
+ * @public
14
+ */
15
+ export type MemoryId = Brand<string, 'MemoryId'>;
16
+
17
+ /**
18
+ * Consumer-supplied domain key. The stable entity identity across versions;
19
+ * the package never mints identity. Equals {@link MemoryId} for non-temporal
20
+ * kinds.
21
+ * @public
22
+ */
23
+ export type EntityId = Brand<string, 'EntityId'>;
24
+
25
+ /**
26
+ * Open-vocabulary record classifier. Consumers register one body
27
+ * {@link https://www.npmjs.com/package/@fgv/ts-utils | Converter} per kind.
28
+ * @public
29
+ */
30
+ export type Kind = Brand<string, 'Kind'>;
31
+
32
+ /**
33
+ * Open-vocabulary tag label.
34
+ * @public
35
+ */
36
+ export type Tag = Brand<string, 'Tag'>;
37
+
38
+ /**
39
+ * Scope path segment. May be multi-segment (`/`-separated) for kinds whose
40
+ * codec maps an entity into a sub-tree (e.g. MTM: `conversations/<id>`). The
41
+ * codec — not this brand — owns filename-safe escaping, so the converter
42
+ * validates only the non-empty/length/whitespace hygiene shared by every
43
+ * brand.
44
+ * @public
45
+ */
46
+ export type MemoryScopeKey = Brand<string, 'MemoryScopeKey'>;
47
+
48
+ /**
49
+ * Open-vocabulary link-relation type for an attributed {@link IEdge}.
50
+ * @public
51
+ */
52
+ export type LinkType = Brand<string, 'LinkType'>;
53
+
54
+ /**
55
+ * Maximum length of any branded identifier in the library. Picked to be
56
+ * larger than any reasonable id a consumer would supply while still bounded
57
+ * to defuse adversarial inputs (e.g. multi-megabyte ids used as map keys).
58
+ */
59
+ const MAX_BRAND_LENGTH: number = 256;
60
+
61
+ /**
62
+ * Builds a `Converter` for one of the branded id scalars in this library.
63
+ * All brands share the same hygiene: non-empty, length-capped, and free of
64
+ * leading/trailing whitespace. Filename-safety and single-segment
65
+ * constraints are intentionally NOT enforced here — the per-kind
66
+ * {@link IIdentityCodec} owns escaping so that multi-segment scope keys
67
+ * (`conversations/<id>`) and composite entity ids (`<id>:<turn>`) validate
68
+ * cleanly.
69
+ *
70
+ * Not exported — the per-brand `Convert.<brand>` constants are the public
71
+ * surface.
72
+ */
73
+ function brandedIdConverter<T extends string>(brand: string): Converter<T> {
74
+ return Converters.string.map((from: string): Result<T> => {
75
+ if (from.length === 0) {
76
+ return fail(`${brand}: must be a non-empty string`);
77
+ }
78
+ if (from.length > MAX_BRAND_LENGTH) {
79
+ return fail(`${brand}: exceeds maximum length ${MAX_BRAND_LENGTH} (got ${from.length})`);
80
+ }
81
+ if (from !== from.trim()) {
82
+ return fail(`${brand}: must not have leading or trailing whitespace`);
83
+ }
84
+ return succeed(from as unknown as T);
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Converters for the branded identifier scalars. Each validates an `unknown`
90
+ * value into the corresponding brand, enforcing the shared hygiene
91
+ * (non-empty, length-capped, trimmed).
92
+ * @public
93
+ */
94
+ export const Convert: {
95
+ readonly memoryId: Converter<MemoryId>;
96
+ readonly entityId: Converter<EntityId>;
97
+ readonly kind: Converter<Kind>;
98
+ readonly tag: Converter<Tag>;
99
+ readonly scopeKey: Converter<MemoryScopeKey>;
100
+ readonly linkType: Converter<LinkType>;
101
+ } = {
102
+ /**
103
+ * Validates an `unknown` value as a {@link MemoryId}. Unlike the other
104
+ * brands, `MemoryId` IS the on-disk filename stem (`id === idStem`, enforced
105
+ * by the store's `verifyFilenameId`), so it additionally enforces the
106
+ * portable-filename-stem contract — rejecting path-unsafe ids (`a/b`,
107
+ * `conv-1:7`) at convert time rather than letting them surface as a file-path
108
+ * failure later. (`EntityId` stays relaxed: a domain key may legitimately be
109
+ * a composite like `conv-1:7`; the codec maps it to a safe stem.)
110
+ */
111
+ memoryId: brandedIdConverter<MemoryId>('MemoryId').map((id) =>
112
+ assertPortableFilenameStem(id).onSuccess(() => succeed(id))
113
+ ),
114
+ /** Validates an `unknown` value as an {@link EntityId}. */
115
+ entityId: brandedIdConverter<EntityId>('EntityId'),
116
+ /** Validates an `unknown` value as a {@link Kind}. */
117
+ kind: brandedIdConverter<Kind>('Kind'),
118
+ /** Validates an `unknown` value as a {@link Tag}. */
119
+ tag: brandedIdConverter<Tag>('Tag'),
120
+ /** Validates an `unknown` value as a {@link MemoryScopeKey}. */
121
+ scopeKey: brandedIdConverter<MemoryScopeKey>('MemoryScopeKey'),
122
+ /** Validates an `unknown` value as a {@link LinkType}. */
123
+ linkType: brandedIdConverter<LinkType>('LinkType')
124
+ } as const;
@@ -0,0 +1,10 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export * from './ids';
7
+ export * from './envelope';
8
+ export * from './filenameSafety';
9
+ export * from './identityCodec';
10
+ export * from './writePolicy';