@remnic/core 1.1.11 → 1.1.13

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 (1462) hide show
  1. package/README.md +3 -3
  2. package/dist/access-cli.d.ts +2 -1
  3. package/dist/access-cli.js +293 -104
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +31 -62
  6. package/dist/access-http.js +53 -35
  7. package/dist/access-mcp.d.ts +31 -8
  8. package/dist/access-mcp.js +45 -34
  9. package/dist/access-schema.d.ts +197 -14
  10. package/dist/access-schema.js +16 -5
  11. package/dist/access-service-DcCDmNYC.d.ts +1542 -0
  12. package/dist/access-service.d.ts +30 -9
  13. package/dist/access-service.js +42 -32
  14. package/dist/action-confidence.d.ts +83 -0
  15. package/dist/action-confidence.js +22 -0
  16. package/dist/active-memory-bridge.d.ts +1 -1
  17. package/dist/active-memory-bridge.js +2 -2
  18. package/dist/active-recall.d.ts +1 -1
  19. package/dist/active-recall.js +11 -3
  20. package/dist/active-recall.js.map +1 -1
  21. package/dist/adapters/claude-code.d.ts +24 -0
  22. package/dist/adapters/claude-code.js +9 -0
  23. package/dist/adapters/codex.d.ts +25 -0
  24. package/dist/adapters/codex.js +9 -0
  25. package/dist/adapters/hermes.d.ts +35 -0
  26. package/dist/adapters/hermes.js +9 -0
  27. package/dist/adapters/index.d.ts +6 -0
  28. package/dist/adapters/index.js +26 -0
  29. package/dist/adapters/registry.d.ts +20 -0
  30. package/dist/adapters/registry.js +13 -0
  31. package/dist/adapters/replit.d.ts +28 -0
  32. package/dist/adapters/replit.js +9 -0
  33. package/dist/adapters/types.d.ts +43 -0
  34. package/dist/adapters/types.js +8 -0
  35. package/dist/behavior-learner.d.ts +1 -1
  36. package/dist/behavior-signals.d.ts +1 -1
  37. package/dist/bootstrap.d.ts +23 -6
  38. package/dist/boxes.d.ts +7 -0
  39. package/dist/boxes.js +1 -1
  40. package/dist/briefing.d.ts +5 -3
  41. package/dist/briefing.js +10 -7
  42. package/dist/buffer-surprise-report.d.ts +1 -1
  43. package/dist/buffer-surprise-report.js +1 -1
  44. package/dist/buffer.d.ts +18 -4
  45. package/dist/buffer.js +1 -1
  46. package/dist/calibration.d.ts +1 -1
  47. package/dist/calibration.js +6 -6
  48. package/dist/capsule-cli.d.ts +4 -4
  49. package/dist/capsule-cli.js +1 -1
  50. package/dist/capsule-crypto-5CYAGVC5.js +18 -0
  51. package/dist/capsule-merge-4MGKE7C5.js +189 -0
  52. package/dist/causal-behavior.d.ts +9 -29
  53. package/dist/causal-behavior.js +6 -3
  54. package/dist/causal-behavior.js.map +1 -1
  55. package/dist/causal-chain.js +3 -2
  56. package/dist/causal-consolidation.d.ts +2 -2
  57. package/dist/causal-consolidation.js +28 -17
  58. package/dist/causal-consolidation.js.map +1 -1
  59. package/dist/causal-retrieval.js +3 -3
  60. package/dist/causal-trajectory.js +1 -1
  61. package/dist/chunk-25MQ7IHJ.js +427 -0
  62. package/dist/chunk-25MQ7IHJ.js.map +1 -0
  63. package/dist/chunk-2F2W355T.js +256 -0
  64. package/dist/chunk-2F2W355T.js.map +1 -0
  65. package/dist/chunk-2KI4QFHU.js +228 -0
  66. package/dist/chunk-2KI4QFHU.js.map +1 -0
  67. package/dist/chunk-2PRQG7PV.js +86 -0
  68. package/dist/chunk-2PRQG7PV.js.map +1 -0
  69. package/dist/chunk-2QR3XXIC.js +2272 -0
  70. package/dist/chunk-2QR3XXIC.js.map +1 -0
  71. package/dist/chunk-2WWLHTZY.js +121 -0
  72. package/dist/chunk-326G7DJK.js +2185 -0
  73. package/dist/chunk-326G7DJK.js.map +1 -0
  74. package/dist/chunk-34DQE4KF.js +174 -0
  75. package/dist/chunk-34DQE4KF.js.map +1 -0
  76. package/dist/chunk-3APJ5EVB.js +601 -0
  77. package/dist/chunk-3APJ5EVB.js.map +1 -0
  78. package/dist/chunk-3HPAPHUK.js +51 -0
  79. package/dist/chunk-3HPAPHUK.js.map +1 -0
  80. package/dist/chunk-3JXBXXM2.js +69 -0
  81. package/dist/chunk-3JXBXXM2.js.map +1 -0
  82. package/dist/chunk-3KW65B36.js +681 -0
  83. package/dist/chunk-3KW65B36.js.map +1 -0
  84. package/dist/chunk-3UXOZBHV.js +20 -0
  85. package/dist/chunk-3UXOZBHV.js.map +1 -0
  86. package/dist/chunk-3VAL7ZL2.js +266 -0
  87. package/dist/chunk-3VAL7ZL2.js.map +1 -0
  88. package/dist/chunk-3Y4P7RXM.js +31 -0
  89. package/dist/chunk-3Y4P7RXM.js.map +1 -0
  90. package/dist/chunk-47VWKCAF.js +273 -0
  91. package/dist/chunk-47VWKCAF.js.map +1 -0
  92. package/dist/chunk-4CRG46BG.js +271 -0
  93. package/dist/chunk-4RA3C3EV.js +60 -0
  94. package/dist/chunk-4RA3C3EV.js.map +1 -0
  95. package/dist/chunk-5375UYTQ.js +914 -0
  96. package/dist/chunk-5375UYTQ.js.map +1 -0
  97. package/dist/chunk-56K5QLHX.js +506 -0
  98. package/dist/chunk-56K5QLHX.js.map +1 -0
  99. package/dist/chunk-5NXIJZFX.js +180 -0
  100. package/dist/chunk-5NXIJZFX.js.map +1 -0
  101. package/dist/chunk-5RGLBDQF.js +596 -0
  102. package/dist/chunk-5RGLBDQF.js.map +1 -0
  103. package/dist/chunk-5UZXUTVO.js +9 -0
  104. package/dist/chunk-5UZXUTVO.js.map +1 -0
  105. package/dist/chunk-65PG43EQ.js +105 -0
  106. package/dist/chunk-65PG43EQ.js.map +1 -0
  107. package/dist/chunk-66DHUKLO.js +57 -0
  108. package/dist/chunk-66DHUKLO.js.map +1 -0
  109. package/dist/chunk-6FC5EGNV.js +46 -0
  110. package/dist/chunk-6FC5EGNV.js.map +1 -0
  111. package/dist/chunk-6H2TESSP.js +62 -0
  112. package/dist/chunk-6H2TESSP.js.map +1 -0
  113. package/dist/chunk-6LVVDPJ4.js +32 -0
  114. package/dist/chunk-6LVVDPJ4.js.map +1 -0
  115. package/dist/chunk-6NKAQ74D.js +2237 -0
  116. package/dist/chunk-6NKAQ74D.js.map +1 -0
  117. package/dist/chunk-6RVI47ZR.js +159 -0
  118. package/dist/chunk-6RVI47ZR.js.map +1 -0
  119. package/dist/chunk-7AAT6G4Q.js +5117 -0
  120. package/dist/chunk-7AAT6G4Q.js.map +1 -0
  121. package/dist/chunk-7DTASS5T.js +29 -0
  122. package/dist/chunk-7DTASS5T.js.map +1 -0
  123. package/dist/chunk-7IASACLB.js +596 -0
  124. package/dist/chunk-7MNMYOFP.js +32 -0
  125. package/dist/chunk-7MNMYOFP.js.map +1 -0
  126. package/dist/chunk-7N4KAIGN.js +133 -0
  127. package/dist/chunk-7N4KAIGN.js.map +1 -0
  128. package/dist/chunk-7OZ53EXP.js +101 -0
  129. package/dist/chunk-7OZ53EXP.js.map +1 -0
  130. package/dist/chunk-7XYTQGCC.js +134 -0
  131. package/dist/chunk-7XYTQGCC.js.map +1 -0
  132. package/dist/chunk-A2XUIMJ3.js +341 -0
  133. package/dist/chunk-A2XUIMJ3.js.map +1 -0
  134. package/dist/chunk-AC5LO7IU.js +308 -0
  135. package/dist/chunk-AC5LO7IU.js.map +1 -0
  136. package/dist/chunk-AGZQD76C.js +201 -0
  137. package/dist/chunk-AGZQD76C.js.map +1 -0
  138. package/dist/chunk-AH2JUU6X.js +336 -0
  139. package/dist/chunk-AH2JUU6X.js.map +1 -0
  140. package/dist/chunk-APO3DCMU.js +361 -0
  141. package/dist/chunk-APO3DCMU.js.map +1 -0
  142. package/dist/chunk-BFBF3XEF.js +283 -0
  143. package/dist/chunk-BFBF3XEF.js.map +1 -0
  144. package/dist/chunk-BJ3KMYTB.js +1974 -0
  145. package/dist/chunk-BJ3KMYTB.js.map +1 -0
  146. package/dist/chunk-C5BCH4ZS.js +317 -0
  147. package/dist/chunk-C5BCH4ZS.js.map +1 -0
  148. package/dist/chunk-CHEL3SKB.js +6758 -0
  149. package/dist/chunk-CHEL3SKB.js.map +1 -0
  150. package/dist/chunk-CQZRLNMV.js +1491 -0
  151. package/dist/chunk-CQZRLNMV.js.map +1 -0
  152. package/dist/chunk-D46YSIYX.js +892 -0
  153. package/dist/chunk-D46YSIYX.js.map +1 -0
  154. package/dist/chunk-DB5A3NHS.js +906 -0
  155. package/dist/chunk-DB5A3NHS.js.map +1 -0
  156. package/dist/chunk-DINWEURR.js +648 -0
  157. package/dist/chunk-DINWEURR.js.map +1 -0
  158. package/dist/chunk-DK5LDEQM.js +530 -0
  159. package/dist/chunk-DK5LDEQM.js.map +1 -0
  160. package/dist/chunk-DOM4GKSW.js +34 -0
  161. package/dist/chunk-DOM4GKSW.js.map +1 -0
  162. package/dist/chunk-EDTHC6UD.js +1075 -0
  163. package/dist/chunk-EDTHC6UD.js.map +1 -0
  164. package/dist/chunk-EFJ3MQ4V.js +721 -0
  165. package/dist/chunk-EHRTFRWW.js +89 -0
  166. package/dist/chunk-EHRTFRWW.js.map +1 -0
  167. package/dist/chunk-FAJ7FZYM.js +11 -0
  168. package/dist/chunk-FAJ7FZYM.js.map +1 -0
  169. package/dist/chunk-FBYESMQ2.js +570 -0
  170. package/dist/chunk-FBYESMQ2.js.map +1 -0
  171. package/dist/chunk-FDU6HUUL.js +147 -0
  172. package/dist/chunk-FF4KLI5W.js +99 -0
  173. package/dist/chunk-FF4KLI5W.js.map +1 -0
  174. package/dist/chunk-FIT6DMX6.js +310 -0
  175. package/dist/chunk-FIT6DMX6.js.map +1 -0
  176. package/dist/chunk-FJ43PRLT.js +272 -0
  177. package/dist/chunk-FJ43PRLT.js.map +1 -0
  178. package/dist/chunk-FKFMOY3N.js +32 -0
  179. package/dist/chunk-FKFMOY3N.js.map +1 -0
  180. package/dist/chunk-FLTNHQK6.js +262 -0
  181. package/dist/chunk-FLTNHQK6.js.map +1 -0
  182. package/dist/chunk-GA454ALV.js +12436 -0
  183. package/dist/chunk-GA454ALV.js.map +1 -0
  184. package/dist/chunk-GGKRUQOO.js +228 -0
  185. package/dist/chunk-GIF42EW3.js +63 -0
  186. package/dist/chunk-GIF42EW3.js.map +1 -0
  187. package/dist/chunk-GL6I6MEQ.js +647 -0
  188. package/dist/chunk-H3ME6L6D.js +709 -0
  189. package/dist/chunk-H3ME6L6D.js.map +1 -0
  190. package/dist/chunk-HHLLAQGZ.js +1 -0
  191. package/dist/chunk-HXXBL2KD.js +2040 -0
  192. package/dist/chunk-I5V2VDIW.js +219 -0
  193. package/dist/chunk-I5V2VDIW.js.map +1 -0
  194. package/dist/chunk-I6K5FBRQ.js +35 -0
  195. package/dist/chunk-I6K5FBRQ.js.map +1 -0
  196. package/dist/chunk-ICRIXAP2.js +121 -0
  197. package/dist/chunk-ICRIXAP2.js.map +1 -0
  198. package/dist/chunk-J4EB7DNW.js +11 -0
  199. package/dist/chunk-J4EB7DNW.js.map +1 -0
  200. package/dist/chunk-JLFA7DQG.js +62 -0
  201. package/dist/chunk-JLFA7DQG.js.map +1 -0
  202. package/dist/chunk-KJTKLXTH.js +9 -0
  203. package/dist/chunk-KJTKLXTH.js.map +1 -0
  204. package/dist/chunk-KLAO5DGL.js +917 -0
  205. package/dist/chunk-KLAO5DGL.js.map +1 -0
  206. package/dist/chunk-KNKUID7G.js +183 -0
  207. package/dist/chunk-KOSORCJG.js +624 -0
  208. package/dist/chunk-KOSORCJG.js.map +1 -0
  209. package/dist/chunk-KUJVMMZQ.js +1262 -0
  210. package/dist/chunk-KUJVMMZQ.js.map +1 -0
  211. package/dist/chunk-LCR46JY5.js +123 -0
  212. package/dist/chunk-LCR46JY5.js.map +1 -0
  213. package/dist/chunk-LLQ2LLWF.js +148 -0
  214. package/dist/chunk-LLQ2LLWF.js.map +1 -0
  215. package/dist/chunk-LPMVBPA3.js +236 -0
  216. package/dist/chunk-LT3NLYSI.js +50 -0
  217. package/dist/chunk-LT3NLYSI.js.map +1 -0
  218. package/dist/chunk-LUDTDZLK.js +287 -0
  219. package/dist/chunk-LUDTDZLK.js.map +1 -0
  220. package/dist/chunk-M23FSH32.js +3963 -0
  221. package/dist/chunk-M23FSH32.js.map +1 -0
  222. package/dist/chunk-MC26UJIM.js +118 -0
  223. package/dist/chunk-ME6ESPZU.js +119 -0
  224. package/dist/chunk-ME6ESPZU.js.map +1 -0
  225. package/dist/chunk-MGKYQQYF.js +272 -0
  226. package/dist/chunk-MGKYQQYF.js.map +1 -0
  227. package/dist/chunk-MJFNCJXV.js +66 -0
  228. package/dist/chunk-MJFNCJXV.js.map +1 -0
  229. package/dist/chunk-MSWG7JI6.js +237 -0
  230. package/dist/chunk-MSWG7JI6.js.map +1 -0
  231. package/dist/chunk-MT25YHYH.js +141 -0
  232. package/dist/chunk-MT25YHYH.js.map +1 -0
  233. package/dist/chunk-MT4HVDUZ.js +53 -0
  234. package/dist/chunk-MY6TPVXW.js +219 -0
  235. package/dist/chunk-N2D6GXBM.js +267 -0
  236. package/dist/chunk-N2D6GXBM.js.map +1 -0
  237. package/dist/chunk-NJ3MJQZX.js +46 -0
  238. package/dist/chunk-NJ3MJQZX.js.map +1 -0
  239. package/dist/chunk-NMZY542O.js +335 -0
  240. package/dist/chunk-NMZY542O.js.map +1 -0
  241. package/dist/chunk-NNVTUXEB.js +23 -0
  242. package/dist/chunk-NZL6GGQE.js +375 -0
  243. package/dist/chunk-NZL6GGQE.js.map +1 -0
  244. package/dist/chunk-OAZ5MFUB.js +4124 -0
  245. package/dist/chunk-OAZ5MFUB.js.map +1 -0
  246. package/dist/chunk-OIGNEXKZ.js +237 -0
  247. package/dist/chunk-OIGNEXKZ.js.map +1 -0
  248. package/dist/chunk-OZKZ2TRP.js +3729 -0
  249. package/dist/chunk-OZKZ2TRP.js.map +1 -0
  250. package/dist/chunk-P4NEIHUT.js +108 -0
  251. package/dist/chunk-P7FMDTKL.js +103 -0
  252. package/dist/chunk-P7FMDTKL.js.map +1 -0
  253. package/dist/chunk-PD6O7AXF.js +110 -0
  254. package/dist/chunk-PD6O7AXF.js.map +1 -0
  255. package/dist/chunk-PHK3HARR.js +32 -0
  256. package/dist/chunk-PHK3HARR.js.map +1 -0
  257. package/dist/chunk-PIRJPV5T.js +98 -0
  258. package/dist/chunk-PIRJPV5T.js.map +1 -0
  259. package/dist/chunk-PK7H5L6Y.js +159 -0
  260. package/dist/chunk-PK7H5L6Y.js.map +1 -0
  261. package/dist/chunk-PR5FBTFU.js +233 -0
  262. package/dist/chunk-PR5FBTFU.js.map +1 -0
  263. package/dist/chunk-PU63GXWS.js +174 -0
  264. package/dist/chunk-PU63GXWS.js.map +1 -0
  265. package/dist/chunk-PYPOFEMK.js +294 -0
  266. package/dist/chunk-PYPOFEMK.js.map +1 -0
  267. package/dist/chunk-PZIAX57I.js +124 -0
  268. package/dist/chunk-PZIAX57I.js.map +1 -0
  269. package/dist/chunk-Q7P4WJDP.js +26 -0
  270. package/dist/chunk-Q7P4WJDP.js.map +1 -0
  271. package/dist/chunk-QDZ2RLEC.js +908 -0
  272. package/dist/chunk-QDZ2RLEC.js.map +1 -0
  273. package/dist/chunk-QQUAB63I.js +63 -0
  274. package/dist/chunk-QQUAB63I.js.map +1 -0
  275. package/dist/chunk-QRNI5JBH.js +18 -0
  276. package/dist/chunk-RHY3HH7P.js +601 -0
  277. package/dist/chunk-RHY3HH7P.js.map +1 -0
  278. package/dist/chunk-RK6F44Y6.js +84 -0
  279. package/dist/chunk-RK6F44Y6.js.map +1 -0
  280. package/dist/chunk-RRF5UOBJ.js +91 -0
  281. package/dist/chunk-RXDLTSWT.js +124 -0
  282. package/dist/chunk-RXDLTSWT.js.map +1 -0
  283. package/dist/chunk-RYED3SPJ.js +42 -0
  284. package/dist/chunk-RYED3SPJ.js.map +1 -0
  285. package/dist/chunk-S7KDBTWT.js +106 -0
  286. package/dist/chunk-S7KDBTWT.js.map +1 -0
  287. package/dist/chunk-SEDEKFYQ.js +1 -0
  288. package/dist/chunk-SOAU2OE2.js +125 -0
  289. package/dist/chunk-SOAU2OE2.js.map +1 -0
  290. package/dist/chunk-TECVW3JP.js +36 -0
  291. package/dist/chunk-TECVW3JP.js.map +1 -0
  292. package/dist/chunk-TFO23QT4.js +88 -0
  293. package/dist/chunk-TFO23QT4.js.map +1 -0
  294. package/dist/chunk-TK4UEOSK.js +76 -0
  295. package/dist/chunk-TK4UEOSK.js.map +1 -0
  296. package/dist/chunk-TKWGAOLV.js +122 -0
  297. package/dist/chunk-TKWGAOLV.js.map +1 -0
  298. package/dist/chunk-TMM4S4IJ.js +597 -0
  299. package/dist/chunk-TMM4S4IJ.js.map +1 -0
  300. package/dist/chunk-TMQLARTH.js +188 -0
  301. package/dist/chunk-TMQLARTH.js.map +1 -0
  302. package/dist/chunk-TPDBFYEG.js +130 -0
  303. package/dist/chunk-TPDBFYEG.js.map +1 -0
  304. package/dist/chunk-TPMQ3G6Z.js +145 -0
  305. package/dist/chunk-TPMQ3G6Z.js.map +1 -0
  306. package/dist/chunk-TZOLIGIG.js +61 -0
  307. package/dist/chunk-TZOLIGIG.js.map +1 -0
  308. package/dist/chunk-U3PN77QT.js +113 -0
  309. package/dist/chunk-U3WSW6PZ.js +277 -0
  310. package/dist/chunk-U4SCL7B7.js +640 -0
  311. package/dist/chunk-U4SCL7B7.js.map +1 -0
  312. package/dist/chunk-UWK5OXUJ.js +156 -0
  313. package/dist/chunk-UWK5OXUJ.js.map +1 -0
  314. package/dist/chunk-UWVJF25J.js +74 -0
  315. package/dist/chunk-UXHQAFNA.js +1317 -0
  316. package/dist/chunk-UXHQAFNA.js.map +1 -0
  317. package/dist/chunk-V5OCT34X.js +1 -0
  318. package/dist/chunk-V5OCT34X.js.map +1 -0
  319. package/dist/chunk-VLXA6PI2.js +304 -0
  320. package/dist/chunk-VLXA6PI2.js.map +1 -0
  321. package/dist/chunk-VNO6ZJ35.js +500 -0
  322. package/dist/chunk-VNO6ZJ35.js.map +1 -0
  323. package/dist/chunk-VW676BEI.js +827 -0
  324. package/dist/chunk-VW676BEI.js.map +1 -0
  325. package/dist/chunk-VWT3F4IV.js +2161 -0
  326. package/dist/chunk-VWT3F4IV.js.map +1 -0
  327. package/dist/chunk-W3LR522O.js +2296 -0
  328. package/dist/chunk-W3LR522O.js.map +1 -0
  329. package/dist/chunk-W4L6CZKA.js +96 -0
  330. package/dist/chunk-W4L6CZKA.js.map +1 -0
  331. package/dist/chunk-W4RVMTHR.js +372 -0
  332. package/dist/chunk-W4RVMTHR.js.map +1 -0
  333. package/dist/chunk-WEHSQBFR.js +188 -0
  334. package/dist/chunk-WEHSQBFR.js.map +1 -0
  335. package/dist/chunk-WELDCG6C.js +380 -0
  336. package/dist/chunk-WELDCG6C.js.map +1 -0
  337. package/dist/chunk-WZYKANL3.js +2800 -0
  338. package/dist/chunk-WZYKANL3.js.map +1 -0
  339. package/dist/chunk-XIG5PDM7.js +48 -0
  340. package/dist/chunk-XJNBEDFE.js +193 -0
  341. package/dist/chunk-XJNBEDFE.js.map +1 -0
  342. package/dist/chunk-XVVIG67A.js +291 -0
  343. package/dist/chunk-XVVIG67A.js.map +1 -0
  344. package/dist/chunk-XVZ7B3HG.js +135 -0
  345. package/dist/chunk-YBPYIAA5.js +73 -0
  346. package/dist/chunk-YBPYIAA5.js.map +1 -0
  347. package/dist/chunk-Z734BLO3.js +21 -0
  348. package/dist/chunk-Z734BLO3.js.map +1 -0
  349. package/dist/chunk-ZKSK55RC.js +269 -0
  350. package/dist/chunk-ZKSK55RC.js.map +1 -0
  351. package/dist/chunk-ZTFCYYEZ.js +69 -0
  352. package/dist/chunk-ZTFCYYEZ.js.map +1 -0
  353. package/dist/chunk-ZY2MNJR6.js +329 -0
  354. package/dist/chunk-ZY2MNJR6.js.map +1 -0
  355. package/dist/cli-D3VpkVwB.d.ts +1136 -0
  356. package/dist/cli.d.ts +42 -10
  357. package/dist/cli.js +121 -58
  358. package/dist/codex-cli-fallback.d.ts +1 -0
  359. package/dist/codex-cli-fallback.js +1 -1
  360. package/dist/commitment-ledger.js +1 -1
  361. package/dist/compat/checks.d.ts +5 -0
  362. package/dist/compat/checks.js +11 -0
  363. package/dist/compat/checks.js.map +1 -0
  364. package/dist/compat/types.d.ts +30 -0
  365. package/dist/compat/types.js +1 -0
  366. package/dist/compat/types.js.map +1 -0
  367. package/dist/compounding/engine.d.ts +221 -0
  368. package/dist/compounding/engine.js +32 -0
  369. package/dist/compounding/engine.js.map +1 -0
  370. package/dist/compounding/preference-consolidator.d.ts +92 -0
  371. package/dist/compounding/preference-consolidator.js +553 -0
  372. package/dist/compounding/preference-consolidator.js.map +1 -0
  373. package/dist/compression-optimizer.d.ts +1 -1
  374. package/dist/config.d.ts +5 -3
  375. package/dist/config.js +9 -4
  376. package/dist/conflict-policy-DyJ2wd-h.d.ts +4 -0
  377. package/dist/connectors/codex-materialize-runner.d.ts +64 -0
  378. package/dist/connectors/codex-materialize-runner.js +33 -0
  379. package/dist/connectors/codex-materialize-runner.js.map +1 -0
  380. package/dist/connectors/codex-materialize.d.ts +195 -0
  381. package/dist/connectors/codex-materialize.js +38 -0
  382. package/dist/connectors/codex-materialize.js.map +1 -0
  383. package/dist/connectors/index.d.ts +444 -0
  384. package/dist/connectors/index.js +115 -0
  385. package/dist/connectors/index.js.map +1 -0
  386. package/dist/connectors-cli-CwbyjGR7.d.ts +257 -0
  387. package/dist/connectors-cli.d.ts +1 -1
  388. package/dist/consolidation-provenance-check.d.ts +4 -2
  389. package/dist/consolidation-undo.d.ts +4 -2
  390. package/dist/contradiction/index.d.ts +258 -0
  391. package/dist/contradiction/index.js +43 -0
  392. package/dist/contradiction/index.js.map +1 -0
  393. package/dist/contradiction-review-ATP4S6IC.js +30 -0
  394. package/dist/contradiction-review-ATP4S6IC.js.map +1 -0
  395. package/dist/contradiction-scan-5A4IDZV5.js +13 -0
  396. package/dist/contradiction-scan-5A4IDZV5.js.map +1 -0
  397. package/dist/conversation-index/backend.d.ts +97 -0
  398. package/dist/conversation-index/backend.js +13 -0
  399. package/dist/conversation-index/backend.js.map +1 -0
  400. package/dist/conversation-index/chunker.d.ts +16 -0
  401. package/dist/conversation-index/chunker.js +8 -0
  402. package/dist/conversation-index/chunker.js.map +1 -0
  403. package/dist/conversation-index/cleanup.d.ts +11 -0
  404. package/dist/conversation-index/cleanup.js +9 -0
  405. package/dist/conversation-index/cleanup.js.map +1 -0
  406. package/dist/conversation-index/faiss-adapter.d.ts +6 -0
  407. package/dist/conversation-index/faiss-adapter.js +16 -0
  408. package/dist/conversation-index/faiss-adapter.js.map +1 -0
  409. package/dist/conversation-index/indexer.d.ts +23 -0
  410. package/dist/conversation-index/indexer.js +15 -0
  411. package/dist/conversation-index/indexer.js.map +1 -0
  412. package/dist/conversation-index/search.d.ts +6 -0
  413. package/dist/conversation-index/search.js +11 -0
  414. package/dist/conversation-index/search.js.map +1 -0
  415. package/dist/day-summary.d.ts +1 -1
  416. package/dist/delinearize.d.ts +1 -1
  417. package/dist/direct-answer-wiring.d.ts +1 -1
  418. package/dist/direct-answer-wiring.js +1 -1
  419. package/dist/direct-answer.d.ts +1 -1
  420. package/dist/embedding-fallback.d.ts +1 -1
  421. package/dist/embedding-fallback.js +2 -2
  422. package/dist/enrichment/index.d.ts +163 -0
  423. package/dist/enrichment/index.js +18 -0
  424. package/dist/enrichment/index.js.map +1 -0
  425. package/dist/entity-retrieval.d.ts +4 -2
  426. package/dist/entity-retrieval.js +9 -6
  427. package/dist/entity-schema.d.ts +1 -1
  428. package/dist/evals.js +1 -1
  429. package/dist/event-order-recall.d.ts +17 -0
  430. package/dist/event-order-recall.js +11 -0
  431. package/dist/event-order-recall.js.map +1 -0
  432. package/dist/evidence-pack.d.ts +3 -1
  433. package/dist/evidence-pack.js +5 -3
  434. package/dist/explicit-capture.d.ts +23 -6
  435. package/dist/explicit-capture.js +2 -2
  436. package/dist/explicit-cue-recall.d.ts +4 -1
  437. package/dist/explicit-cue-recall.js +4 -2
  438. package/dist/extraction-judge-telemetry.d.ts +1 -1
  439. package/dist/extraction-judge-training.d.ts +1 -1
  440. package/dist/extraction-judge-training.js +1 -1
  441. package/dist/extraction-judge.d.ts +1 -1
  442. package/dist/extraction.d.ts +1 -1
  443. package/dist/extraction.js +11 -10
  444. package/dist/faiss-adapter-CzPghc4C.d.ts +70 -0
  445. package/dist/fallback-llm.d.ts +4 -1
  446. package/dist/fallback-llm.js +6 -6
  447. package/dist/focused-list-recall.d.ts +17 -0
  448. package/dist/focused-list-recall.js +11 -0
  449. package/dist/focused-list-recall.js.map +1 -0
  450. package/dist/graph-edge-decay-5DI5GUNL.js +207 -0
  451. package/dist/identity-continuity.d.ts +1 -1
  452. package/dist/importance.d.ts +1 -1
  453. package/dist/index-DJ9QWMw-.d.ts +35 -0
  454. package/dist/index.d.ts +107 -715
  455. package/dist/index.js +657 -2611
  456. package/dist/index.js.map +1 -1
  457. package/dist/intent.d.ts +1 -1
  458. package/dist/intent.js +1 -1
  459. package/dist/lcm/archive.d.ts +89 -0
  460. package/dist/lcm/archive.js +12 -0
  461. package/dist/lcm/archive.js.map +1 -0
  462. package/dist/lcm/dag.d.ts +48 -0
  463. package/dist/lcm/dag.js +8 -0
  464. package/dist/lcm/dag.js.map +1 -0
  465. package/dist/lcm/engine.d.ts +116 -0
  466. package/dist/lcm/engine.js +20 -0
  467. package/dist/lcm/engine.js.map +1 -0
  468. package/dist/lcm/index.d.ts +12 -0
  469. package/dist/lcm/index.js +44 -0
  470. package/dist/lcm/index.js.map +1 -0
  471. package/dist/lcm/queue.d.ts +62 -0
  472. package/dist/lcm/queue.js +8 -0
  473. package/dist/lcm/queue.js.map +1 -0
  474. package/dist/lcm/recall.d.ts +20 -0
  475. package/dist/lcm/recall.js +8 -0
  476. package/dist/lcm/recall.js.map +1 -0
  477. package/dist/lcm/schema.d.ts +16 -0
  478. package/dist/lcm/schema.js +14 -0
  479. package/dist/lcm/schema.js.map +1 -0
  480. package/dist/lcm/summarizer.d.ts +38 -0
  481. package/dist/lcm/summarizer.js +12 -0
  482. package/dist/lcm/summarizer.js.map +1 -0
  483. package/dist/lcm/tools.d.ts +29 -0
  484. package/dist/lcm/tools.js +8 -0
  485. package/dist/lcm/tools.js.map +1 -0
  486. package/dist/lifecycle.d.ts +1 -1
  487. package/dist/live-connectors-runner.d.ts +1 -1
  488. package/dist/live-connectors-runner.js +5 -5
  489. package/dist/local-llm.d.ts +8 -4
  490. package/dist/local-llm.js +3 -3
  491. package/dist/maintenance/archive-observations.d.ts +18 -0
  492. package/dist/maintenance/archive-observations.js +8 -0
  493. package/dist/maintenance/archive-observations.js.map +1 -0
  494. package/dist/maintenance/backup-stamp.d.ts +3 -0
  495. package/dist/maintenance/backup-stamp.js +8 -0
  496. package/dist/maintenance/backup-stamp.js.map +1 -0
  497. package/dist/maintenance/memory-governance-cron.d.ts +85 -0
  498. package/dist/maintenance/memory-governance-cron.js +22 -0
  499. package/dist/maintenance/memory-governance-cron.js.map +1 -0
  500. package/dist/maintenance/memory-governance.d.ts +137 -0
  501. package/dist/maintenance/memory-governance.js +40 -0
  502. package/dist/maintenance/memory-governance.js.map +1 -0
  503. package/dist/maintenance/migrate-observations.d.ts +18 -0
  504. package/dist/maintenance/migrate-observations.js +9 -0
  505. package/dist/maintenance/migrate-observations.js.map +1 -0
  506. package/dist/maintenance/observation-ledger-utils.d.ts +10 -0
  507. package/dist/maintenance/observation-ledger-utils.js +10 -0
  508. package/dist/maintenance/observation-ledger-utils.js.map +1 -0
  509. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +15 -0
  510. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +28 -0
  511. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js.map +1 -0
  512. package/dist/maintenance/rebuild-memory-projection.d.ts +77 -0
  513. package/dist/maintenance/rebuild-memory-projection.js +35 -0
  514. package/dist/maintenance/rebuild-memory-projection.js.map +1 -0
  515. package/dist/maintenance/rebuild-observations.d.ts +17 -0
  516. package/dist/maintenance/rebuild-observations.js +9 -0
  517. package/dist/maintenance/rebuild-observations.js.map +1 -0
  518. package/dist/mcp-memory-inspector-app.d.ts +124 -0
  519. package/dist/mcp-memory-inspector-app.js +20 -0
  520. package/dist/mcp-memory-inspector-app.js.map +1 -0
  521. package/dist/memory-action-policy.d.ts +1 -1
  522. package/dist/memory-cache.d.ts +1 -1
  523. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  524. package/dist/memory-projection-store.d.ts +108 -3
  525. package/dist/memory-projection-store.js +2 -1
  526. package/dist/memory-provenance.d.ts +57 -0
  527. package/dist/memory-provenance.js +13 -0
  528. package/dist/memory-provenance.js.map +1 -0
  529. package/dist/memory-worth-outcomes.d.ts +4 -2
  530. package/dist/migrate/from-engram.d.ts +24 -0
  531. package/dist/migrate/from-engram.js +12 -0
  532. package/dist/migrate/from-engram.js.map +1 -0
  533. package/dist/models-json.d.ts +1 -1
  534. package/dist/namespaces/migrate.d.ts +50 -0
  535. package/dist/namespaces/migrate.js +50 -0
  536. package/dist/namespaces/migrate.js.map +1 -0
  537. package/dist/namespaces/principal.d.ts +17 -0
  538. package/dist/namespaces/principal.js +16 -0
  539. package/dist/namespaces/principal.js.map +1 -0
  540. package/dist/namespaces/search.d.ts +46 -0
  541. package/dist/namespaces/search.js +28 -0
  542. package/dist/namespaces/search.js.map +1 -0
  543. package/dist/namespaces/storage.d.ts +32 -0
  544. package/dist/namespaces/storage.js +28 -0
  545. package/dist/namespaces/storage.js.map +1 -0
  546. package/dist/native-knowledge.d.ts +1 -1
  547. package/dist/network/tailscale.d.ts +41 -0
  548. package/dist/network/tailscale.js +9 -0
  549. package/dist/network/tailscale.js.map +1 -0
  550. package/dist/network/webdav.d.ts +39 -0
  551. package/dist/network/webdav.js +10 -0
  552. package/dist/network/webdav.js.map +1 -0
  553. package/dist/objective-state-writers.d.ts +1 -1
  554. package/dist/objective-state-writers.js +2 -2
  555. package/dist/operator-toolkit.d.ts +4 -2
  556. package/dist/operator-toolkit.js +35 -17
  557. package/dist/opik-exporter.js +2 -2
  558. package/dist/opik-exporter.js.map +1 -1
  559. package/dist/orchestrator-DuWl9Hwx.d.ts +1244 -0
  560. package/dist/orchestrator.d.ts +24 -7
  561. package/dist/orchestrator.js +107 -65
  562. package/dist/path-MR5JPYOP.js +9 -0
  563. package/dist/path-MR5JPYOP.js.map +1 -0
  564. package/dist/patterns-cli.d.ts +1 -1
  565. package/dist/policy-runtime.d.ts +1 -1
  566. package/dist/qmd-recall-cache.d.ts +2 -2
  567. package/dist/qmd.d.ts +103 -4
  568. package/dist/qmd.js +23 -5
  569. package/dist/recall-disclosure-escalation.d.ts +1 -1
  570. package/dist/recall-explain-renderer.d.ts +3 -1
  571. package/dist/recall-explain-renderer.js +5 -3
  572. package/dist/recall-state.d.ts +1 -1
  573. package/dist/recall-tag-filter.d.ts +3 -1
  574. package/dist/recall-xray-cli.d.ts +3 -1
  575. package/dist/recall-xray-cli.js +6 -4
  576. package/dist/recall-xray-renderer.d.ts +3 -1
  577. package/dist/recall-xray-renderer.js +5 -3
  578. package/dist/recall-xray.d.ts +8 -1
  579. package/dist/recall-xray.js +4 -2
  580. package/dist/replay/normalizers/chatgpt.d.ts +6 -0
  581. package/dist/replay/normalizers/chatgpt.js +11 -0
  582. package/dist/replay/normalizers/chatgpt.js.map +1 -0
  583. package/dist/replay/normalizers/claude.d.ts +6 -0
  584. package/dist/replay/normalizers/claude.js +11 -0
  585. package/dist/replay/normalizers/claude.js.map +1 -0
  586. package/dist/replay/normalizers/openclaw.d.ts +6 -0
  587. package/dist/replay/normalizers/openclaw.js +11 -0
  588. package/dist/replay/normalizers/openclaw.js.map +1 -0
  589. package/dist/replay/normalizers/shared.d.ts +16 -0
  590. package/dist/replay/normalizers/shared.js +14 -0
  591. package/dist/replay/normalizers/shared.js.map +1 -0
  592. package/dist/replay/runner.d.ts +35 -0
  593. package/dist/replay/runner.js +16 -0
  594. package/dist/replay/runner.js.map +1 -0
  595. package/dist/replay/types.d.ts +57 -0
  596. package/dist/replay/types.js +19 -0
  597. package/dist/replay/types.js.map +1 -0
  598. package/dist/resolution-B7FNQSSP.js +12 -0
  599. package/dist/resolution-B7FNQSSP.js.map +1 -0
  600. package/dist/resolve-auth-token.d.ts +1 -1
  601. package/dist/resolve-provider-secret.js +2 -2
  602. package/dist/response-guidance-recall.d.ts +18 -0
  603. package/dist/response-guidance-recall.js +11 -0
  604. package/dist/response-guidance-recall.js.map +1 -0
  605. package/dist/resume-bundles.js +7 -5
  606. package/dist/retrieval-agents.d.ts +2 -2
  607. package/dist/retrieval-tiers.d.ts +1 -1
  608. package/dist/routing/engine.d.ts +35 -0
  609. package/dist/routing/engine.js +16 -0
  610. package/dist/routing/engine.js.map +1 -0
  611. package/dist/routing/store.d.ts +27 -0
  612. package/dist/routing/store.js +10 -0
  613. package/dist/routing/store.js.map +1 -0
  614. package/dist/runtime/better-sqlite.d.ts +8 -0
  615. package/dist/runtime/better-sqlite.js +10 -0
  616. package/dist/runtime/better-sqlite.js.map +1 -0
  617. package/dist/runtime/child-process.d.ts +32 -0
  618. package/dist/runtime/child-process.js +10 -0
  619. package/dist/runtime/child-process.js.map +1 -0
  620. package/dist/runtime/env.d.ts +5 -0
  621. package/dist/runtime/env.js +12 -0
  622. package/dist/runtime/env.js.map +1 -0
  623. package/dist/sdk-compat.js +1 -1
  624. package/dist/search/document-scanner.d.ts +22 -0
  625. package/dist/search/document-scanner.js +8 -0
  626. package/dist/search/document-scanner.js.map +1 -0
  627. package/dist/search/embed-helper.d.ts +35 -0
  628. package/dist/search/embed-helper.js +9 -0
  629. package/dist/search/embed-helper.js.map +1 -0
  630. package/dist/search/factory.d.ts +32 -0
  631. package/dist/search/factory.js +29 -0
  632. package/dist/search/factory.js.map +1 -0
  633. package/dist/search/index.d.ts +15 -0
  634. package/dist/search/index.js +50 -0
  635. package/dist/search/index.js.map +1 -0
  636. package/dist/search/lancedb-backend.d.ts +51 -0
  637. package/dist/search/lancedb-backend.js +10 -0
  638. package/dist/search/lancedb-backend.js.map +1 -0
  639. package/dist/search/meilisearch-backend.d.ts +48 -0
  640. package/dist/search/meilisearch-backend.js +10 -0
  641. package/dist/search/meilisearch-backend.js.map +1 -0
  642. package/dist/search/noop-backend.d.ts +26 -0
  643. package/dist/search/noop-backend.js +8 -0
  644. package/dist/search/noop-backend.js.map +1 -0
  645. package/dist/search/orama-backend.d.ts +53 -0
  646. package/dist/search/orama-backend.js +10 -0
  647. package/dist/search/orama-backend.js.map +1 -0
  648. package/dist/search/port.d.ts +61 -0
  649. package/dist/search/port.js +1 -0
  650. package/dist/search/port.js.map +1 -0
  651. package/dist/search/remote-backend.d.ts +39 -0
  652. package/dist/search/remote-backend.js +9 -0
  653. package/dist/search/remote-backend.js.map +1 -0
  654. package/dist/secure-store/index.d.ts +890 -0
  655. package/dist/secure-store/index.js +156 -0
  656. package/dist/secure-store/index.js.map +1 -0
  657. package/dist/semantic-VwGI14Ok.d.ts +69 -0
  658. package/dist/semantic-consolidation-4HkHWgeI.d.ts +180 -0
  659. package/dist/semantic-consolidation.d.ts +3 -3
  660. package/dist/semantic-consolidation.js +15 -8
  661. package/dist/semantic-rule-promotion.js +9 -6
  662. package/dist/semantic-rule-verifier.d.ts +1 -1
  663. package/dist/semantic-rule-verifier.js +10 -7
  664. package/dist/session-observer-bands.d.ts +1 -1
  665. package/dist/session-observer-state.d.ts +1 -1
  666. package/dist/shared-context/manager.d.ts +131 -0
  667. package/dist/shared-context/manager.js +15 -0
  668. package/dist/shared-context/manager.js.map +1 -0
  669. package/dist/signal.d.ts +1 -1
  670. package/dist/skills-registry.js +13 -1
  671. package/dist/skills-registry.js.map +1 -1
  672. package/dist/state-store-VZU2IA53.js +16 -0
  673. package/dist/state-store-VZU2IA53.js.map +1 -0
  674. package/dist/storage-paths.d.ts +9 -0
  675. package/dist/storage-paths.js +20 -0
  676. package/dist/storage-paths.js.map +1 -0
  677. package/dist/storage.d.ts +6 -2
  678. package/dist/storage.js +8 -5
  679. package/dist/summarizer.d.ts +6 -1
  680. package/dist/summarizer.js +11 -10
  681. package/dist/summary-snapshot.d.ts +1 -1
  682. package/dist/summary-snapshot.js +2 -1
  683. package/dist/surfaces/dreams.d.ts +16 -0
  684. package/dist/surfaces/dreams.js +282 -0
  685. package/dist/surfaces/dreams.js.map +1 -0
  686. package/dist/surfaces/heartbeat.d.ts +17 -0
  687. package/dist/surfaces/heartbeat.js +265 -0
  688. package/dist/surfaces/heartbeat.js.map +1 -0
  689. package/dist/targeted-fact-recall.d.ts +17 -0
  690. package/dist/targeted-fact-recall.js +11 -0
  691. package/dist/targeted-fact-recall.js.map +1 -0
  692. package/dist/telemetry-transcript.d.ts +7 -0
  693. package/dist/telemetry-transcript.js +16 -0
  694. package/dist/telemetry-transcript.js.map +1 -0
  695. package/dist/temporal-supersession.d.ts +4 -2
  696. package/dist/temporal-supersession.js +2 -1
  697. package/dist/temporal-validity.d.ts +1 -1
  698. package/dist/threading.d.ts +6 -1
  699. package/dist/threading.js +2 -1
  700. package/dist/tier-migration.d.ts +5 -3
  701. package/dist/tier-routing.d.ts +1 -1
  702. package/dist/tokens.js +2 -2
  703. package/dist/topics.d.ts +1 -1
  704. package/dist/transcript.d.ts +16 -2
  705. package/dist/transcript.js +2 -1
  706. package/dist/transfer/autodetect.d.ts +4 -0
  707. package/dist/transfer/autodetect.js +15 -0
  708. package/dist/transfer/autodetect.js.map +1 -0
  709. package/dist/transfer/backup.d.ts +21 -0
  710. package/dist/transfer/backup.js +17 -0
  711. package/dist/transfer/backup.js.map +1 -0
  712. package/dist/transfer/capsule-export.d.ts +113 -0
  713. package/dist/transfer/capsule-export.js +19 -0
  714. package/dist/transfer/capsule-export.js.map +1 -0
  715. package/dist/transfer/capsule-import.d.ts +124 -0
  716. package/dist/transfer/capsule-import.js +16 -0
  717. package/dist/transfer/capsule-import.js.map +1 -0
  718. package/dist/transfer/constants.d.ts +13 -0
  719. package/dist/transfer/constants.js +12 -0
  720. package/dist/transfer/constants.js.map +1 -0
  721. package/dist/transfer/export-json.d.ts +11 -0
  722. package/dist/transfer/export-json.js +11 -0
  723. package/dist/transfer/export-json.js.map +1 -0
  724. package/dist/transfer/export-md.d.ts +10 -0
  725. package/dist/transfer/export-md.js +13 -0
  726. package/dist/transfer/export-md.js.map +1 -0
  727. package/dist/transfer/export-sqlite.d.ts +9 -0
  728. package/dist/transfer/export-sqlite.js +12 -0
  729. package/dist/transfer/export-sqlite.js.map +1 -0
  730. package/dist/transfer/fs-utils.d.ts +61 -0
  731. package/dist/transfer/fs-utils.js +40 -0
  732. package/dist/transfer/fs-utils.js.map +1 -0
  733. package/dist/transfer/import-json.d.ts +16 -0
  734. package/dist/transfer/import-json.js +13 -0
  735. package/dist/transfer/import-json.js.map +1 -0
  736. package/dist/transfer/import-md.d.ts +14 -0
  737. package/dist/transfer/import-md.js +11 -0
  738. package/dist/transfer/import-md.js.map +1 -0
  739. package/dist/transfer/import-sqlite.d.ts +14 -0
  740. package/dist/transfer/import-sqlite.js +12 -0
  741. package/dist/transfer/import-sqlite.js.map +1 -0
  742. package/dist/transfer/sqlite-schema.d.ts +4 -0
  743. package/dist/transfer/sqlite-schema.js +10 -0
  744. package/dist/transfer/sqlite-schema.js.map +1 -0
  745. package/dist/transfer/types.d.ts +916 -0
  746. package/dist/transfer/types.js +30 -0
  747. package/dist/transfer/types.js.map +1 -0
  748. package/dist/trust-zones.d.ts +3 -2
  749. package/dist/trust-zones.js +1 -1
  750. package/dist/types.d.ts +88 -3
  751. package/dist/types.js +1 -1
  752. package/dist/user-model.d.ts +37 -0
  753. package/dist/user-model.js +28 -0
  754. package/dist/user-model.js.map +1 -0
  755. package/dist/utility-runtime.d.ts +1 -1
  756. package/dist/verified-recall.js +11 -8
  757. package/dist/work/board.d.ts +43 -0
  758. package/dist/work/board.js +14 -0
  759. package/dist/work/board.js.map +1 -0
  760. package/dist/work/boundary.d.ts +8 -0
  761. package/dist/work/boundary.js +14 -0
  762. package/dist/work/boundary.js.map +1 -0
  763. package/dist/work/storage.d.ts +39 -0
  764. package/dist/work/storage.js +11 -0
  765. package/dist/work/storage.js.map +1 -0
  766. package/dist/work/types.d.ts +75 -0
  767. package/dist/work/types.js +1 -0
  768. package/dist/work/types.js.map +1 -0
  769. package/package.json +2767 -6
  770. package/scripts/faiss_index.py +816 -0
  771. package/scripts/faiss_requirements.txt +3 -0
  772. package/skills/remnic-entities/SKILL.md +51 -0
  773. package/skills/remnic-memory-workflow/SKILL.md +61 -0
  774. package/skills/remnic-recall/SKILL.md +51 -0
  775. package/skills/remnic-remember/SKILL.md +56 -0
  776. package/skills/remnic-search/SKILL.md +51 -0
  777. package/skills/remnic-status/SKILL.md +51 -0
  778. package/src/abort-error.test.ts +49 -0
  779. package/src/abort-error.ts +46 -0
  780. package/src/abstraction-nodes.ts +162 -0
  781. package/src/access-audit.test.ts +178 -0
  782. package/src/access-audit.ts +125 -0
  783. package/src/access-cli.test.ts +439 -0
  784. package/src/access-cli.ts +438 -0
  785. package/src/access-http.test.ts +225 -0
  786. package/src/access-http.ts +1899 -0
  787. package/src/access-idempotency.ts +232 -0
  788. package/src/access-mcp.test.ts +568 -0
  789. package/src/access-mcp.ts +3056 -0
  790. package/src/access-schema-pi.test.ts +60 -0
  791. package/src/access-schema.ts +522 -0
  792. package/src/access-service-namespace.test.ts +123 -0
  793. package/src/access-service.ts +5629 -0
  794. package/src/action-confidence.test.ts +206 -0
  795. package/src/action-confidence.ts +466 -0
  796. package/src/active-memory-bridge.test.ts +285 -0
  797. package/src/active-memory-bridge.ts +217 -0
  798. package/src/active-recall.test.ts +484 -0
  799. package/src/active-recall.ts +459 -0
  800. package/src/adapters/claude-code.ts +56 -0
  801. package/src/adapters/codex.ts +57 -0
  802. package/src/adapters/hermes.ts +64 -0
  803. package/src/adapters/index.ts +6 -0
  804. package/src/adapters/registry.ts +41 -0
  805. package/src/adapters/replit.ts +55 -0
  806. package/src/adapters/types.ts +51 -0
  807. package/src/behavior-learner.ts +144 -0
  808. package/src/behavior-signals.ts +73 -0
  809. package/src/binary-lifecycle/backend.ts +117 -0
  810. package/src/binary-lifecycle/index.ts +35 -0
  811. package/src/binary-lifecycle/manifest.ts +79 -0
  812. package/src/binary-lifecycle/pipeline.ts +352 -0
  813. package/src/binary-lifecycle/scanner.ts +89 -0
  814. package/src/binary-lifecycle/types.ts +89 -0
  815. package/src/bootstrap.ts +178 -0
  816. package/src/boxes.ts +521 -0
  817. package/src/briefing.test.ts +1535 -0
  818. package/src/briefing.ts +1382 -0
  819. package/src/buffer-session.test.ts +443 -0
  820. package/src/buffer-surprise-report.ts +176 -0
  821. package/src/buffer-surprise-telemetry.test.ts +606 -0
  822. package/src/buffer-surprise-trigger.test.ts +766 -0
  823. package/src/buffer-surprise.test.ts +339 -0
  824. package/src/buffer-surprise.ts +203 -0
  825. package/src/buffer.ts +900 -0
  826. package/src/bulk-import/cli-command.test.ts +204 -0
  827. package/src/bulk-import/index.ts +34 -0
  828. package/src/bulk-import/pipeline.test.ts +445 -0
  829. package/src/bulk-import/pipeline.ts +178 -0
  830. package/src/bulk-import/registry.test.ts +151 -0
  831. package/src/bulk-import/registry.ts +72 -0
  832. package/src/bulk-import/types.test.ts +272 -0
  833. package/src/bulk-import/types.ts +145 -0
  834. package/src/calibration.ts +394 -0
  835. package/src/capsule-cli.test.ts +398 -0
  836. package/src/capsule-cli.ts +565 -0
  837. package/src/causal-behavior.ts +308 -0
  838. package/src/causal-chain.ts +419 -0
  839. package/src/causal-consolidation.ts +370 -0
  840. package/src/causal-retrieval.ts +286 -0
  841. package/src/causal-trajectory-graph.ts +60 -0
  842. package/src/causal-trajectory.ts +303 -0
  843. package/src/chunking.ts +220 -0
  844. package/src/citations.ts +232 -0
  845. package/src/cli.ts +9403 -0
  846. package/src/codex-cli-fallback.ts +162 -0
  847. package/src/codex-thread-key.ts +1 -0
  848. package/src/coding/access-coding-context.test.ts +197 -0
  849. package/src/coding/coding-branch-scope.test.ts +281 -0
  850. package/src/coding/coding-namespace.test.ts +360 -0
  851. package/src/coding/coding-namespace.ts +412 -0
  852. package/src/coding/coding-orchestrator.test.ts +249 -0
  853. package/src/coding/git-context.test.ts +507 -0
  854. package/src/coding/git-context.ts +336 -0
  855. package/src/coding/mcp-set-coding-context.test.ts +174 -0
  856. package/src/coding/review-context.test.ts +316 -0
  857. package/src/coding/review-context.ts +349 -0
  858. package/src/coding/wire-coding-context.test.ts +468 -0
  859. package/src/commitment-ledger.test.ts +78 -0
  860. package/src/commitment-ledger.ts +337 -0
  861. package/src/compat/checks.test.ts +206 -0
  862. package/src/compat/checks.ts +716 -0
  863. package/src/compat/types.ts +33 -0
  864. package/src/compounding/engine.ts +1686 -0
  865. package/src/compounding/preference-consolidator.ts +778 -0
  866. package/src/compression-optimizer.ts +312 -0
  867. package/src/config.test.ts +930 -0
  868. package/src/config.ts +3807 -0
  869. package/src/connectors/codex/instructions.md +160 -0
  870. package/src/connectors/codex/resources/namespace-cheatsheet.md +48 -0
  871. package/src/connectors/codex-marketplace.ts +500 -0
  872. package/src/connectors/codex-materialize-runner.ts +212 -0
  873. package/src/connectors/codex-materialize.ts +983 -0
  874. package/src/connectors/coerce.ts +62 -0
  875. package/src/connectors/index.test.ts +1570 -0
  876. package/src/connectors/index.ts +3222 -0
  877. package/src/connectors/live/framework.ts +164 -0
  878. package/src/connectors/live/github.test.ts +1218 -0
  879. package/src/connectors/live/github.ts +1068 -0
  880. package/src/connectors/live/gmail.test.ts +1706 -0
  881. package/src/connectors/live/gmail.ts +1293 -0
  882. package/src/connectors/live/google-drive.test.ts +696 -0
  883. package/src/connectors/live/google-drive.ts +724 -0
  884. package/src/connectors/live/index.ts +101 -0
  885. package/src/connectors/live/live-connectors.test.ts +689 -0
  886. package/src/connectors/live/notion.test.ts +1109 -0
  887. package/src/connectors/live/notion.ts +978 -0
  888. package/src/connectors/live/registry.ts +103 -0
  889. package/src/connectors/live/state-store.ts +399 -0
  890. package/src/connectors/live/transient-errors.ts +150 -0
  891. package/src/connectors/weclone-installer.test.ts +850 -0
  892. package/src/connectors-cli.ts +513 -0
  893. package/src/console/state.test.ts +224 -0
  894. package/src/console/state.ts +514 -0
  895. package/src/console/trace.test.ts +813 -0
  896. package/src/console/trace.ts +603 -0
  897. package/src/console/tui.test.ts +582 -0
  898. package/src/console/tui.ts +508 -0
  899. package/src/consolidation-operator.ts +182 -0
  900. package/src/consolidation-provenance-check.ts +551 -0
  901. package/src/consolidation-undo.ts +718 -0
  902. package/src/contradiction/contradiction-judge.test.ts +189 -0
  903. package/src/contradiction/contradiction-judge.ts +333 -0
  904. package/src/contradiction/contradiction-review.ts +574 -0
  905. package/src/contradiction/contradiction-scan.ts +504 -0
  906. package/src/contradiction/contradiction.test.ts +2230 -0
  907. package/src/contradiction/index.ts +37 -0
  908. package/src/contradiction/resolution.ts +383 -0
  909. package/src/conversation-index/backend.ts +323 -0
  910. package/src/conversation-index/chunker.ts +47 -0
  911. package/src/conversation-index/cleanup.ts +53 -0
  912. package/src/conversation-index/faiss-adapter.ts +384 -0
  913. package/src/conversation-index/indexer.test.ts +164 -0
  914. package/src/conversation-index/indexer.ts +192 -0
  915. package/src/conversation-index/search.ts +37 -0
  916. package/src/cross-namespace-budget.test.ts +275 -0
  917. package/src/cross-namespace-budget.ts +365 -0
  918. package/src/cue-anchors.ts +163 -0
  919. package/src/curation/index.ts +544 -0
  920. package/src/dashboard-runtime.ts +337 -0
  921. package/src/day-summary.ts +122 -0
  922. package/src/dedup/index.ts +330 -0
  923. package/src/dedup/semantic.test.ts +1577 -0
  924. package/src/dedup/semantic.ts +148 -0
  925. package/src/delinearize.ts +193 -0
  926. package/src/direct-answer-wiring.test.ts +473 -0
  927. package/src/direct-answer-wiring.ts +180 -0
  928. package/src/direct-answer.test.ts +484 -0
  929. package/src/direct-answer.ts +273 -0
  930. package/src/embedding-fallback.ts +565 -0
  931. package/src/enrichment/audit.ts +89 -0
  932. package/src/enrichment/index.ts +27 -0
  933. package/src/enrichment/pipeline.ts +197 -0
  934. package/src/enrichment/provider-registry.ts +85 -0
  935. package/src/enrichment/types.ts +100 -0
  936. package/src/enrichment/web-search-provider.ts +63 -0
  937. package/src/entity-retrieval.ts +774 -0
  938. package/src/entity-schema.ts +239 -0
  939. package/src/evals.ts +1312 -0
  940. package/src/event-order-recall.test.ts +4164 -0
  941. package/src/event-order-recall.ts +2802 -0
  942. package/src/evidence-pack.test.ts +89 -0
  943. package/src/evidence-pack.ts +388 -0
  944. package/src/explicit-capture.ts +530 -0
  945. package/src/explicit-cue-recall.test.ts +3019 -0
  946. package/src/explicit-cue-recall.ts +5545 -0
  947. package/src/extraction-judge-telemetry.ts +234 -0
  948. package/src/extraction-judge-training.ts +221 -0
  949. package/src/extraction-judge.ts +846 -0
  950. package/src/extraction-timeout.test.ts +265 -0
  951. package/src/extraction.ts +2719 -0
  952. package/src/fallback-llm.test.ts +1060 -0
  953. package/src/fallback-llm.ts +918 -0
  954. package/src/focused-list-recall.test.ts +734 -0
  955. package/src/focused-list-recall.ts +1160 -0
  956. package/src/graph-dashboard-diff.ts +35 -0
  957. package/src/graph-dashboard-key.ts +5 -0
  958. package/src/graph-dashboard-parser.ts +104 -0
  959. package/src/graph-edge-reinforcement.ts +192 -0
  960. package/src/graph-events.ts +151 -0
  961. package/src/graph-recall.test.ts +164 -0
  962. package/src/graph-recall.ts +189 -0
  963. package/src/graph-retrieval.test.ts +809 -0
  964. package/src/graph-retrieval.ts +823 -0
  965. package/src/graph-snapshot.ts +329 -0
  966. package/src/graph.ts +813 -0
  967. package/src/harmonic-retrieval.ts +223 -0
  968. package/src/himem.ts +154 -0
  969. package/src/hygiene.ts +87 -0
  970. package/src/identity-continuity.ts +333 -0
  971. package/src/importance.ts +328 -0
  972. package/src/importers/base.test.ts +294 -0
  973. package/src/importers/base.ts +436 -0
  974. package/src/importers/index.ts +21 -0
  975. package/src/index.ts +1204 -0
  976. package/src/intent.ts +154 -0
  977. package/src/json-extract.ts +85 -0
  978. package/src/json-store.ts +42 -0
  979. package/src/lcm/archive.ts +617 -0
  980. package/src/lcm/dag.ts +199 -0
  981. package/src/lcm/engine.ts +645 -0
  982. package/src/lcm/index.ts +7 -0
  983. package/src/lcm/queue.test.ts +178 -0
  984. package/src/lcm/queue.ts +200 -0
  985. package/src/lcm/recall.ts +117 -0
  986. package/src/lcm/schema.ts +154 -0
  987. package/src/lcm/summarizer.ts +235 -0
  988. package/src/lcm/tools.ts +191 -0
  989. package/src/lcm-engine.test.ts +660 -0
  990. package/src/legacy-hook-compat.test.ts +20 -0
  991. package/src/legacy-hook-compat.ts +45 -0
  992. package/src/lifecycle.ts +289 -0
  993. package/src/live-connectors-runner.ts +385 -0
  994. package/src/local-llm-qos.test.ts +303 -0
  995. package/src/local-llm-thinking.test.ts +292 -0
  996. package/src/local-llm.ts +1464 -0
  997. package/src/logger.ts +49 -0
  998. package/src/maintenance/archive-observations.ts +147 -0
  999. package/src/maintenance/backup-stamp.ts +3 -0
  1000. package/src/maintenance/dreams-ledger.ts +516 -0
  1001. package/src/maintenance/first-start-migration.ts +362 -0
  1002. package/src/maintenance/forget.test.ts +206 -0
  1003. package/src/maintenance/forget.ts +126 -0
  1004. package/src/maintenance/graph-edge-decay.test.ts +409 -0
  1005. package/src/maintenance/graph-edge-decay.ts +394 -0
  1006. package/src/maintenance/memory-governance-cron.ts +447 -0
  1007. package/src/maintenance/memory-governance.ts +1039 -0
  1008. package/src/maintenance/migrate-observations.ts +216 -0
  1009. package/src/maintenance/observation-ledger-utils.ts +54 -0
  1010. package/src/maintenance/pattern-reinforcement.test.ts +875 -0
  1011. package/src/maintenance/pattern-reinforcement.ts +369 -0
  1012. package/src/maintenance/purge.ts +334 -0
  1013. package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +78 -0
  1014. package/src/maintenance/rebuild-memory-projection.ts +1234 -0
  1015. package/src/maintenance/rebuild-observations.ts +178 -0
  1016. package/src/maintenance/tier-stats.test.ts +378 -0
  1017. package/src/maintenance/tier-stats.ts +222 -0
  1018. package/src/mcp-memory-inspector-app.ts +421 -0
  1019. package/src/memory-action-policy.ts +80 -0
  1020. package/src/memory-cache.ts +208 -0
  1021. package/src/memory-extension/claude-code-publisher.ts +51 -0
  1022. package/src/memory-extension/codex-publisher.ts +149 -0
  1023. package/src/memory-extension/hermes-publisher.ts +51 -0
  1024. package/src/memory-extension/index.ts +100 -0
  1025. package/src/memory-extension/shared-instructions.ts +133 -0
  1026. package/src/memory-extension/types.ts +86 -0
  1027. package/src/memory-extension-host/host-discovery.ts +276 -0
  1028. package/src/memory-extension-host/index.ts +14 -0
  1029. package/src/memory-extension-host/render-extensions-block.ts +73 -0
  1030. package/src/memory-extension-host/types.ts +21 -0
  1031. package/src/memory-lifecycle-ledger-utils.ts +116 -0
  1032. package/src/memory-projection-format.ts +11 -0
  1033. package/src/memory-projection-store.ts +951 -0
  1034. package/src/memory-provenance.test.ts +196 -0
  1035. package/src/memory-provenance.ts +484 -0
  1036. package/src/memory-worth-bench.test.ts +71 -0
  1037. package/src/memory-worth-bench.ts +265 -0
  1038. package/src/memory-worth-filter.test.ts +209 -0
  1039. package/src/memory-worth-filter.ts +204 -0
  1040. package/src/memory-worth-frontmatter.test.ts +311 -0
  1041. package/src/memory-worth-outcomes.test.ts +316 -0
  1042. package/src/memory-worth-outcomes.ts +286 -0
  1043. package/src/memory-worth.test.ts +317 -0
  1044. package/src/memory-worth.ts +215 -0
  1045. package/src/message-parts/index.ts +806 -0
  1046. package/src/message-parts/message-parts.test.ts +421 -0
  1047. package/src/migrate/from-engram.ts +789 -0
  1048. package/src/model-registry.ts +313 -0
  1049. package/src/models-json.ts +76 -0
  1050. package/src/namespaces/migrate.ts +187 -0
  1051. package/src/namespaces/path.ts +25 -0
  1052. package/src/namespaces/principal.test.ts +195 -0
  1053. package/src/namespaces/principal.ts +86 -0
  1054. package/src/namespaces/search.test.ts +105 -0
  1055. package/src/namespaces/search.ts +233 -0
  1056. package/src/namespaces/storage.ts +74 -0
  1057. package/src/native-knowledge.ts +1823 -0
  1058. package/src/negative.ts +72 -0
  1059. package/src/network/tailscale.ts +179 -0
  1060. package/src/network/webdav.ts +385 -0
  1061. package/src/objective-state-writers.ts +951 -0
  1062. package/src/objective-state.ts +320 -0
  1063. package/src/onboarding/index.ts +529 -0
  1064. package/src/openai-chat-compat.ts +56 -0
  1065. package/src/operator-toolkit.ts +2132 -0
  1066. package/src/opik-exporter.test.ts +72 -0
  1067. package/src/opik-exporter.ts +587 -0
  1068. package/src/orchestrator-extraction-queue.test.ts +197 -0
  1069. package/src/orchestrator-flush.test.ts +1171 -0
  1070. package/src/orchestrator-pattern-reinforcement.test.ts +128 -0
  1071. package/src/orchestrator-source-attribution.test.ts +701 -0
  1072. package/src/orchestrator.ts +16368 -0
  1073. package/src/page-versioning.ts +450 -0
  1074. package/src/patterns-cli.ts +574 -0
  1075. package/src/peers/index.ts +54 -0
  1076. package/src/peers/migrate-from-identity-anchor.test.ts +291 -0
  1077. package/src/peers/migrate-from-identity-anchor.ts +350 -0
  1078. package/src/peers/peers.test.ts +419 -0
  1079. package/src/peers/profile-reasoner.ts +694 -0
  1080. package/src/peers/storage.ts +1350 -0
  1081. package/src/peers/types.ts +138 -0
  1082. package/src/plugin-id.ts +84 -0
  1083. package/src/policy-runtime.ts +209 -0
  1084. package/src/procedural/procedure-miner.ts +150 -0
  1085. package/src/procedural/procedure-recall.ts +93 -0
  1086. package/src/procedural/procedure-stats.ts +213 -0
  1087. package/src/procedural/procedure-types.ts +132 -0
  1088. package/src/procedural/reinforcement-core.test.ts +132 -0
  1089. package/src/procedural/reinforcement-core.ts +73 -0
  1090. package/src/profiling.test.ts +263 -0
  1091. package/src/profiling.ts +435 -0
  1092. package/src/projection/index.ts +398 -0
  1093. package/src/qmd-recall-cache.test.ts +138 -0
  1094. package/src/qmd-recall-cache.ts +111 -0
  1095. package/src/qmd.test.ts +257 -0
  1096. package/src/qmd.ts +2614 -0
  1097. package/src/reasoning-trace-recall.ts +201 -0
  1098. package/src/reasoning-trace-types.ts +235 -0
  1099. package/src/recall-audit-anomaly.test.ts +246 -0
  1100. package/src/recall-audit-anomaly.ts +297 -0
  1101. package/src/recall-audit.test.ts +51 -0
  1102. package/src/recall-audit.ts +72 -0
  1103. package/src/recall-budget-config.test.ts +87 -0
  1104. package/src/recall-disclosure-escalation.test.ts +196 -0
  1105. package/src/recall-disclosure-escalation.ts +158 -0
  1106. package/src/recall-disclosure-shaping.test.ts +146 -0
  1107. package/src/recall-disclosure.test.ts +214 -0
  1108. package/src/recall-explain-renderer.test.ts +140 -0
  1109. package/src/recall-explain-renderer.ts +356 -0
  1110. package/src/recall-mmr.test.ts +808 -0
  1111. package/src/recall-mmr.ts +607 -0
  1112. package/src/recall-qos.test.ts +85 -0
  1113. package/src/recall-qos.ts +82 -0
  1114. package/src/recall-query-policy.ts +221 -0
  1115. package/src/recall-state.test.ts +233 -0
  1116. package/src/recall-state.ts +456 -0
  1117. package/src/recall-tag-filter.ts +143 -0
  1118. package/src/recall-tokenization.ts +35 -0
  1119. package/src/recall-xray-cli.test.ts +118 -0
  1120. package/src/recall-xray-cli.ts +100 -0
  1121. package/src/recall-xray-disclosure-telemetry.test.ts +183 -0
  1122. package/src/recall-xray-renderer.test.ts +539 -0
  1123. package/src/recall-xray-renderer.ts +487 -0
  1124. package/src/recall-xray.test.ts +503 -0
  1125. package/src/recall-xray.ts +621 -0
  1126. package/src/reconstruct.ts +41 -0
  1127. package/src/release-changelog.ts +35 -0
  1128. package/src/relevance.ts +67 -0
  1129. package/src/replay/normalizers/chatgpt.ts +133 -0
  1130. package/src/replay/normalizers/claude.ts +102 -0
  1131. package/src/replay/normalizers/openclaw.ts +119 -0
  1132. package/src/replay/normalizers/shared.ts +69 -0
  1133. package/src/replay/runner.ts +197 -0
  1134. package/src/replay/types.ts +143 -0
  1135. package/src/rerank.test.ts +48 -0
  1136. package/src/rerank.ts +176 -0
  1137. package/src/resolve-auth-token.test.ts +226 -0
  1138. package/src/resolve-auth-token.ts +151 -0
  1139. package/src/resolve-provider-secret.test.ts +187 -0
  1140. package/src/resolve-provider-secret.ts +410 -0
  1141. package/src/response-guidance-recall.test.ts +3952 -0
  1142. package/src/response-guidance-recall.ts +4431 -0
  1143. package/src/resume-bundles.ts +415 -0
  1144. package/src/retrieval-agents.ts +623 -0
  1145. package/src/retrieval-tiers.ts +25 -0
  1146. package/src/retrieval.ts +104 -0
  1147. package/src/review/index.test.ts +201 -0
  1148. package/src/review/index.ts +536 -0
  1149. package/src/routing/engine.ts +162 -0
  1150. package/src/routing/store.ts +321 -0
  1151. package/src/runtime/better-sqlite.test.ts +32 -0
  1152. package/src/runtime/better-sqlite.ts +76 -0
  1153. package/src/runtime/child-process.ts +67 -0
  1154. package/src/runtime/env.ts +48 -0
  1155. package/src/sanitize.ts +58 -0
  1156. package/src/schemas.ts +449 -0
  1157. package/src/sdk-compat.ts +87 -0
  1158. package/src/search/document-scanner.ts +96 -0
  1159. package/src/search/embed-helper.ts +142 -0
  1160. package/src/search/factory.ts +189 -0
  1161. package/src/search/index.ts +10 -0
  1162. package/src/search/lancedb-backend.ts +342 -0
  1163. package/src/search/meilisearch-backend.ts +232 -0
  1164. package/src/search/noop-backend.ts +57 -0
  1165. package/src/search/orama-backend.ts +358 -0
  1166. package/src/search/port.ts +86 -0
  1167. package/src/search/remote-backend.ts +124 -0
  1168. package/src/secure-store/cipher.ts +271 -0
  1169. package/src/secure-store/cli-handlers.ts +355 -0
  1170. package/src/secure-store/cli-renderer.ts +131 -0
  1171. package/src/secure-store/header.ts +373 -0
  1172. package/src/secure-store/index.ts +137 -0
  1173. package/src/secure-store/kdf.ts +263 -0
  1174. package/src/secure-store/keyring.ts +106 -0
  1175. package/src/secure-store/metadata.ts +394 -0
  1176. package/src/secure-store/passphrase-reader.ts +252 -0
  1177. package/src/secure-store/secure-fs.ts +571 -0
  1178. package/src/secure-store/secure-store.test.ts +755 -0
  1179. package/src/semantic-chunking.ts +545 -0
  1180. package/src/semantic-consolidation.test.ts +182 -0
  1181. package/src/semantic-consolidation.ts +432 -0
  1182. package/src/semantic-rule-promotion.ts +183 -0
  1183. package/src/semantic-rule-verifier.ts +160 -0
  1184. package/src/session-integrity.ts +569 -0
  1185. package/src/session-observer-bands.ts +11 -0
  1186. package/src/session-observer-state.ts +346 -0
  1187. package/src/session-toggles.test.ts +96 -0
  1188. package/src/session-toggles.ts +159 -0
  1189. package/src/shared-context/manager.ts +810 -0
  1190. package/src/signal.ts +84 -0
  1191. package/src/skills-registry.test.ts +277 -0
  1192. package/src/skills-registry.ts +120 -0
  1193. package/src/source-attribution-roundtrip.test.ts +215 -0
  1194. package/src/source-attribution.test.ts +1425 -0
  1195. package/src/source-attribution.ts +639 -0
  1196. package/src/spaces/index.ts +627 -0
  1197. package/src/storage-paths.ts +117 -0
  1198. package/src/storage.ts +6657 -0
  1199. package/src/store-contract.ts +55 -0
  1200. package/src/summarizer.ts +844 -0
  1201. package/src/summary-snapshot.test.ts +681 -0
  1202. package/src/summary-snapshot.ts +238 -0
  1203. package/src/surfaces/dreams.test.ts +394 -0
  1204. package/src/surfaces/dreams.ts +346 -0
  1205. package/src/surfaces/heartbeat.test.ts +415 -0
  1206. package/src/surfaces/heartbeat.ts +325 -0
  1207. package/src/sync/index.ts +308 -0
  1208. package/src/targeted-fact-recall.test.ts +1694 -0
  1209. package/src/targeted-fact-recall.ts +2905 -0
  1210. package/src/taxonomy/default-taxonomy.ts +87 -0
  1211. package/src/taxonomy/index.ts +26 -0
  1212. package/src/taxonomy/resolver-doc-generator.ts +57 -0
  1213. package/src/taxonomy/resolver.ts +184 -0
  1214. package/src/taxonomy/taxonomy-loader.ts +186 -0
  1215. package/src/taxonomy/types.ts +48 -0
  1216. package/src/telemetry-transcript.ts +70 -0
  1217. package/src/temporal-index.ts +890 -0
  1218. package/src/temporal-supersession.test.ts +2703 -0
  1219. package/src/temporal-supersession.ts +493 -0
  1220. package/src/temporal-validity.test.ts +448 -0
  1221. package/src/temporal-validity.ts +123 -0
  1222. package/src/threading.ts +395 -0
  1223. package/src/tier-migration.ts +124 -0
  1224. package/src/tier-routing.ts +102 -0
  1225. package/src/tmt.ts +462 -0
  1226. package/src/tokens.test.ts +178 -0
  1227. package/src/tokens.ts +279 -0
  1228. package/src/topics.ts +147 -0
  1229. package/src/training-export/cli-date-validation.test.ts +258 -0
  1230. package/src/training-export/converter.test.ts +452 -0
  1231. package/src/training-export/converter.ts +319 -0
  1232. package/src/training-export/date-parse.ts +117 -0
  1233. package/src/training-export/index.ts +26 -0
  1234. package/src/training-export/registry.test.ts +85 -0
  1235. package/src/training-export/registry.ts +57 -0
  1236. package/src/training-export/types.ts +31 -0
  1237. package/src/transcript.ts +1179 -0
  1238. package/src/transfer/autodetect.ts +30 -0
  1239. package/src/transfer/backup.ts +138 -0
  1240. package/src/transfer/capsule-crypto.ts +485 -0
  1241. package/src/transfer/capsule-encrypt.test.ts +690 -0
  1242. package/src/transfer/capsule-export.ts +543 -0
  1243. package/src/transfer/capsule-fork.ts +375 -0
  1244. package/src/transfer/capsule-import.ts +564 -0
  1245. package/src/transfer/capsule-merge.ts +433 -0
  1246. package/src/transfer/conflict-policy.ts +16 -0
  1247. package/src/transfer/constants.ts +13 -0
  1248. package/src/transfer/exclusions.ts +37 -0
  1249. package/src/transfer/export-json.ts +65 -0
  1250. package/src/transfer/export-md.ts +59 -0
  1251. package/src/transfer/export-sqlite.ts +52 -0
  1252. package/src/transfer/fs-utils.ts +269 -0
  1253. package/src/transfer/import-json.ts +108 -0
  1254. package/src/transfer/import-md.ts +84 -0
  1255. package/src/transfer/import-sqlite.ts +100 -0
  1256. package/src/transfer/integrity.ts +71 -0
  1257. package/src/transfer/sqlite-schema.ts +16 -0
  1258. package/src/transfer/types.ts +297 -0
  1259. package/src/trust-zones.ts +1186 -0
  1260. package/src/types.ts +3074 -0
  1261. package/src/user-model.test.ts +124 -0
  1262. package/src/user-model.ts +162 -0
  1263. package/src/utility-learner.ts +353 -0
  1264. package/src/utility-runtime.ts +88 -0
  1265. package/src/utility-telemetry.ts +215 -0
  1266. package/src/utils/category-dir.ts +44 -0
  1267. package/src/utils/errno.ts +6 -0
  1268. package/src/utils/iso-timestamp.test.ts +37 -0
  1269. package/src/utils/iso-timestamp.ts +164 -0
  1270. package/src/utils/path.ts +26 -0
  1271. package/src/verified-recall.ts +138 -0
  1272. package/src/version-utils.test.ts +10 -0
  1273. package/src/version-utils.ts +9 -0
  1274. package/src/whitespace.ts +10 -0
  1275. package/src/work/board.ts +359 -0
  1276. package/src/work/boundary.ts +107 -0
  1277. package/src/work/storage.ts +436 -0
  1278. package/src/work/types.ts +82 -0
  1279. package/src/work-product-ledger.ts +265 -0
  1280. package/dist/access-service-BkXt3di1.d.ts +0 -2039
  1281. package/dist/capsule-crypto-SJS5VVAP.js +0 -18
  1282. package/dist/capsule-export-LLEVB2RG.js +0 -17
  1283. package/dist/capsule-import-UW45R2MZ.js +0 -16
  1284. package/dist/capsule-merge-DI7PNQ2H.js +0 -189
  1285. package/dist/chunk-2LGMW3DJ.js +0 -111
  1286. package/dist/chunk-2YMTO4ZJ.js +0 -265
  1287. package/dist/chunk-2YMTO4ZJ.js.map +0 -1
  1288. package/dist/chunk-363MWCD3.js +0 -9683
  1289. package/dist/chunk-363MWCD3.js.map +0 -1
  1290. package/dist/chunk-36CTNQY7.js +0 -1554
  1291. package/dist/chunk-36CTNQY7.js.map +0 -1
  1292. package/dist/chunk-457A4P3L.js +0 -119
  1293. package/dist/chunk-457A4P3L.js.map +0 -1
  1294. package/dist/chunk-4DXC6HQQ.js +0 -1837
  1295. package/dist/chunk-4DXC6HQQ.js.map +0 -1
  1296. package/dist/chunk-4IS4SXIQ.js +0 -2040
  1297. package/dist/chunk-57QNCUEZ.js +0 -1914
  1298. package/dist/chunk-57QNCUEZ.js.map +0 -1
  1299. package/dist/chunk-6AUUAZEX.js +0 -150
  1300. package/dist/chunk-6AUUAZEX.js.map +0 -1
  1301. package/dist/chunk-6TBWYBJ3.js +0 -236
  1302. package/dist/chunk-6XA7UN4Z.js +0 -135
  1303. package/dist/chunk-6Z6UH6TK.js +0 -2129
  1304. package/dist/chunk-6Z6UH6TK.js.map +0 -1
  1305. package/dist/chunk-74EMIVE4.js +0 -329
  1306. package/dist/chunk-74EMIVE4.js.map +0 -1
  1307. package/dist/chunk-74WWN7ZW.js +0 -82
  1308. package/dist/chunk-74WWN7ZW.js.map +0 -1
  1309. package/dist/chunk-767ODGE6.js +0 -183
  1310. package/dist/chunk-A4ACKWIW.js +0 -289
  1311. package/dist/chunk-A4ACKWIW.js.map +0 -1
  1312. package/dist/chunk-ASAITVLA.js +0 -64
  1313. package/dist/chunk-ASAITVLA.js.map +0 -1
  1314. package/dist/chunk-C5HUWVH2.js +0 -891
  1315. package/dist/chunk-C5HUWVH2.js.map +0 -1
  1316. package/dist/chunk-D54LZC5L.js +0 -147
  1317. package/dist/chunk-DF3RVK3X.js +0 -119
  1318. package/dist/chunk-DF3RVK3X.js.map +0 -1
  1319. package/dist/chunk-E6K4NIEU.js +0 -747
  1320. package/dist/chunk-E6K4NIEU.js.map +0 -1
  1321. package/dist/chunk-EEQLFRUM.js +0 -89
  1322. package/dist/chunk-EQINRHYR.js +0 -672
  1323. package/dist/chunk-EQINRHYR.js.map +0 -1
  1324. package/dist/chunk-ETOW6ACV.js +0 -158
  1325. package/dist/chunk-ETOW6ACV.js.map +0 -1
  1326. package/dist/chunk-EYNQTST2.js +0 -721
  1327. package/dist/chunk-FYIYMQ5N.js +0 -221
  1328. package/dist/chunk-FYIYMQ5N.js.map +0 -1
  1329. package/dist/chunk-G2WADRQ3.js +0 -219
  1330. package/dist/chunk-G4SK7DSQ.js +0 -121
  1331. package/dist/chunk-GGD5W7TB.js +0 -105
  1332. package/dist/chunk-GGD5W7TB.js.map +0 -1
  1333. package/dist/chunk-GVPWB7EY.js +0 -390
  1334. package/dist/chunk-GVPWB7EY.js.map +0 -1
  1335. package/dist/chunk-HJYHRE4S.js +0 -647
  1336. package/dist/chunk-I6BQZSML.js +0 -1451
  1337. package/dist/chunk-I6BQZSML.js.map +0 -1
  1338. package/dist/chunk-IBX3VFOM.js +0 -446
  1339. package/dist/chunk-IBX3VFOM.js.map +0 -1
  1340. package/dist/chunk-IXEJRKCZ.js +0 -18
  1341. package/dist/chunk-JBMSGZEQ.js +0 -441
  1342. package/dist/chunk-JBMSGZEQ.js.map +0 -1
  1343. package/dist/chunk-JRNQ3RNA.js +0 -284
  1344. package/dist/chunk-JRNQ3RNA.js.map +0 -1
  1345. package/dist/chunk-K6WK37A6.js +0 -865
  1346. package/dist/chunk-K6WK37A6.js.map +0 -1
  1347. package/dist/chunk-KBYWQWSB.js +0 -271
  1348. package/dist/chunk-KUHRUM6B.js +0 -14397
  1349. package/dist/chunk-KUHRUM6B.js.map +0 -1
  1350. package/dist/chunk-KWBPHZUU.js +0 -83
  1351. package/dist/chunk-KWBPHZUU.js.map +0 -1
  1352. package/dist/chunk-LIO5X3CM.js +0 -596
  1353. package/dist/chunk-MARWOCVP.js +0 -48
  1354. package/dist/chunk-MCC6KDQF.js +0 -5095
  1355. package/dist/chunk-MCC6KDQF.js.map +0 -1
  1356. package/dist/chunk-N5AKDXAI.js +0 -74
  1357. package/dist/chunk-NN3LPQ5D.js +0 -936
  1358. package/dist/chunk-NN3LPQ5D.js.map +0 -1
  1359. package/dist/chunk-O4XJUPSF.js +0 -533
  1360. package/dist/chunk-O4XJUPSF.js.map +0 -1
  1361. package/dist/chunk-OA3L7BFR.js +0 -183
  1362. package/dist/chunk-OA3L7BFR.js.map +0 -1
  1363. package/dist/chunk-OR64ZGRZ.js +0 -23
  1364. package/dist/chunk-P73JTV34.js +0 -275
  1365. package/dist/chunk-P73JTV34.js.map +0 -1
  1366. package/dist/chunk-P77UEOU2.js +0 -1521
  1367. package/dist/chunk-P77UEOU2.js.map +0 -1
  1368. package/dist/chunk-PB5KW5PL.js +0 -118
  1369. package/dist/chunk-PHNGXFQ6.js +0 -623
  1370. package/dist/chunk-PHNGXFQ6.js.map +0 -1
  1371. package/dist/chunk-QIGOEM65.js +0 -228
  1372. package/dist/chunk-RXTFCYQF.js +0 -108
  1373. package/dist/chunk-S2JJBLJG.js +0 -2101
  1374. package/dist/chunk-S2JJBLJG.js.map +0 -1
  1375. package/dist/chunk-S3IP6R6K.js +0 -219
  1376. package/dist/chunk-S3IP6R6K.js.map +0 -1
  1377. package/dist/chunk-SRBJUAMP.js +0 -403
  1378. package/dist/chunk-SRBJUAMP.js.map +0 -1
  1379. package/dist/chunk-URB2WSKZ.js +0 -350
  1380. package/dist/chunk-URB2WSKZ.js.map +0 -1
  1381. package/dist/chunk-VQXK37XA.js +0 -26
  1382. package/dist/chunk-VQXK37XA.js.map +0 -1
  1383. package/dist/chunk-VTU2B4VF.js +0 -146
  1384. package/dist/chunk-VTU2B4VF.js.map +0 -1
  1385. package/dist/chunk-VX2IUQFE.js +0 -613
  1386. package/dist/chunk-VX2IUQFE.js.map +0 -1
  1387. package/dist/chunk-WGK4VHGP.js +0 -4292
  1388. package/dist/chunk-WGK4VHGP.js.map +0 -1
  1389. package/dist/chunk-WTFWLUSX.js +0 -827
  1390. package/dist/chunk-WTFWLUSX.js.map +0 -1
  1391. package/dist/chunk-XJKFSSDW.js +0 -726
  1392. package/dist/chunk-XJKFSSDW.js.map +0 -1
  1393. package/dist/chunk-XMHBH5H6.js +0 -283
  1394. package/dist/chunk-XMHBH5H6.js.map +0 -1
  1395. package/dist/chunk-XMVFHBHT.js +0 -277
  1396. package/dist/chunk-Y5KDIOKF.js +0 -2403
  1397. package/dist/chunk-Y5KDIOKF.js.map +0 -1
  1398. package/dist/chunk-YNB73F22.js +0 -137
  1399. package/dist/chunk-YNB73F22.js.map +0 -1
  1400. package/dist/chunk-Z2E7VW55.js +0 -335
  1401. package/dist/chunk-Z2E7VW55.js.map +0 -1
  1402. package/dist/chunk-Z5S5HNGY.js +0 -2280
  1403. package/dist/chunk-Z5S5HNGY.js.map +0 -1
  1404. package/dist/chunk-ZL4S7ARC.js +0 -53
  1405. package/dist/chunk-ZTSE2ZJ6.js +0 -190
  1406. package/dist/chunk-ZTSE2ZJ6.js.map +0 -1
  1407. package/dist/cli-Cvy2SNhF.d.ts +0 -1259
  1408. package/dist/codex-materialize-CQlLTzke.d.ts +0 -139
  1409. package/dist/connectors-cli-DFGtY2DB.d.ts +0 -257
  1410. package/dist/contradiction-review-5LTTVDQV.js +0 -22
  1411. package/dist/contradiction-scan-3Z6YW7YA.js +0 -413
  1412. package/dist/contradiction-scan-3Z6YW7YA.js.map +0 -1
  1413. package/dist/engine-FOC3IJLA.js +0 -28
  1414. package/dist/fs-utils-IRVUFB6G.js +0 -30
  1415. package/dist/graph-edge-decay-PWB63GRE.js +0 -207
  1416. package/dist/index-1qIcnbG1.d.ts +0 -34
  1417. package/dist/memory-governance-F3QOJGEY.js +0 -37
  1418. package/dist/memory-projection-store-CY8TU40w.d.ts +0 -222
  1419. package/dist/orchestrator-AOQMo7QI.d.ts +0 -1784
  1420. package/dist/path-RMTY5Y5A.js +0 -9
  1421. package/dist/port-B6VEDIkC.d.ts +0 -53
  1422. package/dist/resolution-YGIBORXI.js +0 -101
  1423. package/dist/resolution-YGIBORXI.js.map +0 -1
  1424. package/dist/secure-store-4R2GSO7S.js +0 -156
  1425. package/dist/semantic-consolidation-ByBXb-sf.d.ts +0 -180
  1426. package/dist/state-store-3EH7HYIN.js +0 -16
  1427. package/dist/types-V3FJ26TF.js +0 -30
  1428. /package/dist/{capsule-crypto-SJS5VVAP.js.map → action-confidence.js.map} +0 -0
  1429. /package/dist/{capsule-export-LLEVB2RG.js.map → adapters/claude-code.js.map} +0 -0
  1430. /package/dist/{capsule-import-UW45R2MZ.js.map → adapters/codex.js.map} +0 -0
  1431. /package/dist/{contradiction-review-5LTTVDQV.js.map → adapters/hermes.js.map} +0 -0
  1432. /package/dist/{engine-FOC3IJLA.js.map → adapters/index.js.map} +0 -0
  1433. /package/dist/{fs-utils-IRVUFB6G.js.map → adapters/registry.js.map} +0 -0
  1434. /package/dist/{memory-governance-F3QOJGEY.js.map → adapters/replit.js.map} +0 -0
  1435. /package/dist/{path-RMTY5Y5A.js.map → adapters/types.js.map} +0 -0
  1436. /package/dist/{secure-store-4R2GSO7S.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
  1437. /package/dist/{capsule-merge-DI7PNQ2H.js.map → capsule-merge-4MGKE7C5.js.map} +0 -0
  1438. /package/dist/{chunk-G4SK7DSQ.js.map → chunk-2WWLHTZY.js.map} +0 -0
  1439. /package/dist/{chunk-KBYWQWSB.js.map → chunk-4CRG46BG.js.map} +0 -0
  1440. /package/dist/{chunk-LIO5X3CM.js.map → chunk-7IASACLB.js.map} +0 -0
  1441. /package/dist/{chunk-EYNQTST2.js.map → chunk-EFJ3MQ4V.js.map} +0 -0
  1442. /package/dist/{chunk-D54LZC5L.js.map → chunk-FDU6HUUL.js.map} +0 -0
  1443. /package/dist/{chunk-QIGOEM65.js.map → chunk-GGKRUQOO.js.map} +0 -0
  1444. /package/dist/{chunk-HJYHRE4S.js.map → chunk-GL6I6MEQ.js.map} +0 -0
  1445. /package/dist/{state-store-3EH7HYIN.js.map → chunk-HHLLAQGZ.js.map} +0 -0
  1446. /package/dist/{chunk-4IS4SXIQ.js.map → chunk-HXXBL2KD.js.map} +0 -0
  1447. /package/dist/{chunk-767ODGE6.js.map → chunk-KNKUID7G.js.map} +0 -0
  1448. /package/dist/{chunk-6TBWYBJ3.js.map → chunk-LPMVBPA3.js.map} +0 -0
  1449. /package/dist/{chunk-PB5KW5PL.js.map → chunk-MC26UJIM.js.map} +0 -0
  1450. /package/dist/{chunk-ZL4S7ARC.js.map → chunk-MT4HVDUZ.js.map} +0 -0
  1451. /package/dist/{chunk-G2WADRQ3.js.map → chunk-MY6TPVXW.js.map} +0 -0
  1452. /package/dist/{chunk-OR64ZGRZ.js.map → chunk-NNVTUXEB.js.map} +0 -0
  1453. /package/dist/{chunk-RXTFCYQF.js.map → chunk-P4NEIHUT.js.map} +0 -0
  1454. /package/dist/{chunk-IXEJRKCZ.js.map → chunk-QRNI5JBH.js.map} +0 -0
  1455. /package/dist/{chunk-EEQLFRUM.js.map → chunk-RRF5UOBJ.js.map} +0 -0
  1456. /package/dist/{types-V3FJ26TF.js.map → chunk-SEDEKFYQ.js.map} +0 -0
  1457. /package/dist/{chunk-2LGMW3DJ.js.map → chunk-U3PN77QT.js.map} +0 -0
  1458. /package/dist/{chunk-XMVFHBHT.js.map → chunk-U3WSW6PZ.js.map} +0 -0
  1459. /package/dist/{chunk-N5AKDXAI.js.map → chunk-UWVJF25J.js.map} +0 -0
  1460. /package/dist/{chunk-MARWOCVP.js.map → chunk-XIG5PDM7.js.map} +0 -0
  1461. /package/dist/{chunk-6XA7UN4Z.js.map → chunk-XVZ7B3HG.js.map} +0 -0
  1462. /package/dist/{graph-edge-decay-PWB63GRE.js.map → graph-edge-decay-5DI5GUNL.js.map} +0 -0
@@ -0,0 +1,1350 @@
1
+ /**
2
+ * Peer registry storage primitives — issue #679 PR 1/5.
3
+ *
4
+ * Pure file-I/O helpers for the per-peer kernel files:
5
+ *
6
+ * peers/{peer-id}/identity.md — slow, human-edited identity facts
7
+ * peers/{peer-id}/profile.md — evolving profile (reasoner-owned)
8
+ * peers/{peer-id}/interactions.log.md — append-only signal log
9
+ *
10
+ * No reasoner logic, no recall integration, no migration of existing
11
+ * identity-anchor data — those land in PR 2/5 — 5/5.
12
+ *
13
+ * Path safety: `peerId` is validated against PEER_ID_PATTERN before any
14
+ * filesystem operation. Reading a non-existent peer returns null (does not
15
+ * throw). Reading malformed files throws — callers can catch and recover.
16
+ */
17
+
18
+ import { promises as fs, constants as fsConstants } from "node:fs";
19
+ import path from "node:path";
20
+
21
+ /**
22
+ * Atomic, symlink-rejecting open: returns a file handle whose
23
+ * underlying open(2) call carried `O_NOFOLLOW`, so the kernel itself
24
+ * refuses to follow a symlink at the target path. Closes the
25
+ * check-then-use TOCTOU race that a separate `assertPathNotSymlink`
26
+ * + `fs.writeFile` pattern leaves open (codex P1 round 5 on PR #723).
27
+ */
28
+ async function openNoFollow(file: string, flags: number): Promise<import("node:fs/promises").FileHandle> {
29
+ return fs.open(file, flags | fsConstants.O_NOFOLLOW);
30
+ }
31
+
32
+ /** Read a file, refusing to follow symlinks at the kernel level
33
+ * AND verifying parent-dir inode stability (codex P1 round 9). */
34
+ async function readFileNoFollow(file: string): Promise<string> {
35
+ await assertParentDirInodeStable(file);
36
+ const fh = await openNoFollow(file, fsConstants.O_RDONLY);
37
+ try {
38
+ return await fh.readFile("utf8");
39
+ } finally {
40
+ await fh.close();
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Codex P1 round 9: O_NOFOLLOW only protects the FINAL path
46
+ * component, so a parent-directory swap mid-flight (peers/<id>
47
+ * unlinked + replaced with a symlink between assertPeerDirNotEscaped
48
+ * and the open) could still write outside memoryDir. Without
49
+ * `openat` (Node has no stable JS binding for it), the best
50
+ * pure-Node mitigation is to:
51
+ * 1. Open the parent directory with O_DIRECTORY | O_NOFOLLOW so
52
+ * WE hold the original-inode handle.
53
+ * 2. fstat the parent handle and compare its (dev, inode) against
54
+ * lstat of the parent path. If they diverge, a swap happened
55
+ * between mkdir and now — abort.
56
+ * 3. Then do the symlink-rejecting open of the file.
57
+ * This narrows the race window to the few microseconds between the
58
+ * fstat/lstat compare and the open. Fully closing the race needs
59
+ * `openat`, which is tracked as a follow-up.
60
+ */
61
+ async function assertParentDirInodeStable(filePath: string): Promise<void> {
62
+ const parent = path.dirname(filePath);
63
+ const dh = await fs.open(
64
+ parent,
65
+ fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW,
66
+ );
67
+ try {
68
+ const fstatInfo = await dh.stat();
69
+ const lstatInfo = await fs.lstat(parent);
70
+ if (fstatInfo.ino !== lstatInfo.ino || fstatInfo.dev !== lstatInfo.dev) {
71
+ throw new Error(
72
+ `parent directory "${parent}" was swapped between checks (inode mismatch)`,
73
+ );
74
+ }
75
+ if (lstatInfo.isSymbolicLink()) {
76
+ throw new Error(`parent directory "${parent}" is a symlink and is rejected`);
77
+ }
78
+ } finally {
79
+ await dh.close();
80
+ }
81
+ }
82
+
83
+ /** Overwrite a file, refusing to follow symlinks at the kernel level
84
+ * AND verifying the parent directory inode is stable across the open
85
+ * (codex P1 round 9). */
86
+ async function writeFileNoFollow(file: string, data: string): Promise<void> {
87
+ await assertParentDirInodeStable(file);
88
+ const fh = await openNoFollow(
89
+ file,
90
+ fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_TRUNC,
91
+ );
92
+ try {
93
+ await fh.writeFile(data, "utf8");
94
+ } finally {
95
+ await fh.close();
96
+ }
97
+ }
98
+
99
+ /** Append to a file, refusing to follow symlinks AND verifying parent
100
+ * inode stability (codex P1 round 9). */
101
+ async function appendFileNoFollow(file: string, data: string): Promise<void> {
102
+ await assertParentDirInodeStable(file);
103
+ const fh = await openNoFollow(
104
+ file,
105
+ fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_APPEND,
106
+ );
107
+ try {
108
+ await fh.writeFile(data, "utf8");
109
+ } finally {
110
+ await fh.close();
111
+ }
112
+ }
113
+
114
+ import {
115
+ PEER_ID_MAX_LENGTH,
116
+ PEER_ID_PATTERN,
117
+ type Peer,
118
+ type PeerInteractionLogEntry,
119
+ type PeerKind,
120
+ type PeerProfile,
121
+ type PeerProfileFieldProvenance,
122
+ } from "./types.js";
123
+
124
+ // ──────────────────────────────────────────────────────────────────────
125
+ // Validation
126
+ // ──────────────────────────────────────────────────────────────────────
127
+
128
+ const ALLOWED_KINDS: ReadonlySet<PeerKind> = new Set<PeerKind>([
129
+ "self",
130
+ "human",
131
+ "agent",
132
+ "integration",
133
+ ]);
134
+
135
+ /**
136
+ * Validate a peer id. Throws `Error` with a descriptive message on failure.
137
+ * Exported so callers can pre-check user input before constructing a Peer.
138
+ */
139
+ export function assertValidPeerId(peerId: unknown): asserts peerId is string {
140
+ if (typeof peerId !== "string") {
141
+ throw new Error("peerId must be a string");
142
+ }
143
+ if (peerId.length === 0) {
144
+ throw new Error("peerId must not be empty");
145
+ }
146
+ if (peerId.length > PEER_ID_MAX_LENGTH) {
147
+ throw new Error(`peerId must be ≤ ${PEER_ID_MAX_LENGTH} characters`);
148
+ }
149
+ if (!PEER_ID_PATTERN.test(peerId)) {
150
+ throw new Error(
151
+ `peerId "${peerId}" is invalid — must match ${PEER_ID_PATTERN}`,
152
+ );
153
+ }
154
+ // Defence-in-depth: reject consecutive dots/dashes/underscores. The
155
+ // regex already prevents leading/trailing separators, but explicit
156
+ // adjacency checks document intent and survive future regex refactors.
157
+ if (/[.\-_]{2,}/.test(peerId)) {
158
+ throw new Error(
159
+ `peerId "${peerId}" is invalid — must not contain consecutive separators`,
160
+ );
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Strict plain-object check. Rejects arrays, null, Maps, Sets, class
166
+ * instances, and anything else with a non-Object/null prototype.
167
+ * Codex P2 round 13: writePeerProfile previously treated any
168
+ * non-null non-array object as plain; inputs like `new Map()` would
169
+ * pass and serialize to `{}` losing all data.
170
+ */
171
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
172
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
173
+ return false;
174
+ }
175
+ const proto = Object.getPrototypeOf(value);
176
+ return proto === Object.prototype || proto === null;
177
+ }
178
+
179
+ function assertValidKind(kind: unknown): asserts kind is PeerKind {
180
+ if (typeof kind !== "string" || !ALLOWED_KINDS.has(kind as PeerKind)) {
181
+ throw new Error(
182
+ `peer kind must be one of ${Array.from(ALLOWED_KINDS).join(", ")}`,
183
+ );
184
+ }
185
+ }
186
+
187
+ // ──────────────────────────────────────────────────────────────────────
188
+ // Path helpers
189
+ // ──────────────────────────────────────────────────────────────────────
190
+
191
+ /** Root directory holding the peer registry, relative to memoryDir. */
192
+ export const PEERS_DIR_NAME = "peers";
193
+
194
+ function peersRoot(memoryDir: string): string {
195
+ return path.join(memoryDir, PEERS_DIR_NAME);
196
+ }
197
+
198
+ function peerDir(memoryDir: string, peerId: string): string {
199
+ // Guard against path traversal on top of regex validation. After
200
+ // assertValidPeerId, peerId cannot contain `/`, `..`, or NUL — but we
201
+ // re-check defensively here.
202
+ assertValidPeerId(peerId);
203
+ const candidate = path.join(peersRoot(memoryDir), peerId);
204
+ const root = peersRoot(memoryDir);
205
+ // Ensure resolved path stays within peersRoot. Note: this is a
206
+ // lexical check only — a symlinked peer directory can still escape.
207
+ // I/O sites must additionally call `assertPeerDirNotEscaped` (below)
208
+ // before reading or writing, which uses lstat to reject symlinks
209
+ // and realpath to confirm physical containment (codex P1 #723).
210
+ const relative = path.relative(root, candidate);
211
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
212
+ throw new Error(`peerId "${peerId}" resolves outside peers root`);
213
+ }
214
+ return candidate;
215
+ }
216
+
217
+ /**
218
+ * Reject the peers root if it is itself a symlink. Called BEFORE any
219
+ * `fs.mkdir`, so a `peers → /tmp/outside` symlink can't get its
220
+ * target mutated by a recursive mkdir before subsequent checks fire
221
+ * (codex P2 + cursor M on PR #723).
222
+ */
223
+ async function assertPeersRootNotSymlink(memoryDir: string): Promise<void> {
224
+ const root = peersRoot(memoryDir);
225
+ let rootStat: import("node:fs").Stats | null = null;
226
+ try {
227
+ rootStat = await fs.lstat(root);
228
+ } catch (err) {
229
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
230
+ }
231
+ if (rootStat && rootStat.isSymbolicLink()) {
232
+ throw new Error(`peers root "${root}" is a symlink and is rejected`);
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Codex P1 round 13: atomic mkdir of the peer directory under a
238
+ * verified peers root. Opens the peers root with O_DIRECTORY|
239
+ * O_NOFOLLOW first (creating it if missing under the same flags) so
240
+ * a root-symlink swap can't redirect the subsequent mkdir to its
241
+ * target. The dir handle is held across the mkdir, then we lstat
242
+ * the candidate and reject if it's a symlink.
243
+ */
244
+ async function mkdirPeerDirAtomic(memoryDir: string, peerId: string): Promise<void> {
245
+ const root = peersRoot(memoryDir);
246
+ // Ensure the root exists as a real directory, not a symlink. Use
247
+ // mkdir + lstat: if mkdir succeeds (or EEXIST), lstat must report
248
+ // a directory and not a symlink.
249
+ await fs.mkdir(memoryDir, { recursive: true });
250
+ try {
251
+ await fs.mkdir(root);
252
+ } catch (err) {
253
+ if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err;
254
+ }
255
+ const rootLstat = await fs.lstat(root);
256
+ if (rootLstat.isSymbolicLink()) {
257
+ throw new Error(`peers root "${root}" is a symlink and is rejected`);
258
+ }
259
+ if (!rootLstat.isDirectory()) {
260
+ throw new Error(`peers root "${root}" exists but is not a directory`);
261
+ }
262
+ // Open the root with O_DIRECTORY|O_NOFOLLOW to anchor the inode.
263
+ // Hold the handle across the peer-dir mkdir so a root swap
264
+ // mid-operation is detected via fstat-vs-lstat compare afterward.
265
+ const rootHandle = await fs.open(
266
+ root,
267
+ fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW,
268
+ );
269
+ try {
270
+ const candidate = peerDir(memoryDir, peerId);
271
+ await fs.mkdir(candidate, { recursive: true });
272
+ // Verify root inode unchanged across the mkdir.
273
+ const fstatRoot = await rootHandle.stat();
274
+ const lstatRoot = await fs.lstat(root);
275
+ if (fstatRoot.ino !== lstatRoot.ino || fstatRoot.dev !== lstatRoot.dev) {
276
+ throw new Error(`peers root "${root}" was swapped during mkdir`);
277
+ }
278
+ // Reject symlinked peer dir.
279
+ const peerLstat = await fs.lstat(candidate);
280
+ if (peerLstat.isSymbolicLink()) {
281
+ throw new Error(`peer directory "${peerId}" is a symlink and is rejected`);
282
+ }
283
+ } finally {
284
+ await rootHandle.close();
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Codex P1 on PR #723: `peerDir` only enforces a lexical
290
+ * `path.relative` check, so a symlinked peer directory like
291
+ * `peers/self → /tmp/outside` would slip through. Run this guard
292
+ * AFTER the peer directory has been (or is known to) exist, so we
293
+ * can lstat it and realpath-check containment. For first-time writes,
294
+ * call `mkdirPeerDirAtomic` BEFORE this; for reads, this alone.
295
+ */
296
+ async function assertPeerDirNotEscaped(memoryDir: string, peerId: string): Promise<void> {
297
+ const candidate = peerDir(memoryDir, peerId);
298
+ const root = peersRoot(memoryDir);
299
+ // 1. The peers root must not be a symlink (defensive — writers
300
+ // already checked this before mkdir, but reads must still verify).
301
+ await assertPeersRootNotSymlink(memoryDir);
302
+ // 2. lstat on the candidate itself. If it's a symlink, refuse.
303
+ let candidateStat: import("node:fs").Stats | null = null;
304
+ try {
305
+ candidateStat = await fs.lstat(candidate);
306
+ } catch (err) {
307
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
308
+ }
309
+ if (candidateStat && candidateStat.isSymbolicLink()) {
310
+ throw new Error(`peer directory "${peerId}" is a symlink and is rejected`);
311
+ }
312
+ // 3. Real-path containment. Only meaningful if the candidate exists.
313
+ if (candidateStat) {
314
+ const realRoot = await fs.realpath(root);
315
+ const realCandidate = await fs.realpath(candidate);
316
+ const rel = path.relative(realRoot, realCandidate);
317
+ if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) {
318
+ throw new Error(`peer directory "${peerId}" escapes the peers root`);
319
+ }
320
+ }
321
+ }
322
+
323
+ function identityPath(memoryDir: string, peerId: string): string {
324
+ return path.join(peerDir(memoryDir, peerId), "identity.md");
325
+ }
326
+
327
+ function profilePath(memoryDir: string, peerId: string): string {
328
+ return path.join(peerDir(memoryDir, peerId), "profile.md");
329
+ }
330
+
331
+ function interactionsPath(memoryDir: string, peerId: string): string {
332
+ return path.join(peerDir(memoryDir, peerId), "interactions.log.md");
333
+ }
334
+
335
+ // ──────────────────────────────────────────────────────────────────────
336
+ // Minimal YAML helpers (peer files only)
337
+ // ──────────────────────────────────────────────────────────────────────
338
+ //
339
+ // We deliberately do not depend on the codebase's primary YAML parser
340
+ // (`storage.ts`) because the peer schema is small and structured. We emit
341
+ // a strict, predictable subset:
342
+ //
343
+ // ---
344
+ // id: my-peer
345
+ // kind: agent
346
+ // displayName: "Codex"
347
+ // createdAt: 2026-04-25T00:00:00.000Z
348
+ // updatedAt: 2026-04-25T00:00:00.000Z
349
+ // ---
350
+ // {free-form markdown notes}
351
+ //
352
+ // String values are always double-quoted with `\\` and `\"` escapes. ISO
353
+ // timestamps and the kind enum are emitted bare. This keeps round-trip
354
+ // behaviour deterministic and easy to validate.
355
+
356
+ function escapeYamlString(value: string): string {
357
+ // Cursor Medium: must escape newlines / carriage returns / tabs so a
358
+ // value like `displayName: "first\nsecond"` doesn't blow up the
359
+ // line-oriented parsePeerFrontmatter. Backslash → `\\`, double-quote
360
+ // → `\"`, newline → `\n`, carriage return → `\r`, tab → `\t`.
361
+ return `"${value
362
+ .replace(/\\/g, "\\\\")
363
+ .replace(/"/g, '\\"')
364
+ .replace(/\n/g, "\\n")
365
+ .replace(/\r/g, "\\r")
366
+ .replace(/\t/g, "\\t")}"`;
367
+ }
368
+
369
+ function unescapeYamlString(quoted: string): string {
370
+ // Caller has already verified `quoted` starts and ends with a double quote.
371
+ const body = quoted.slice(1, -1);
372
+ let out = "";
373
+ for (let i = 0; i < body.length; i++) {
374
+ const ch = body[i];
375
+ if (ch === "\\" && i + 1 < body.length) {
376
+ const next = body[i + 1];
377
+ if (next === "\\" || next === '"') {
378
+ out += next;
379
+ i++;
380
+ continue;
381
+ }
382
+ if (next === "n") {
383
+ out += "\n";
384
+ i++;
385
+ continue;
386
+ }
387
+ if (next === "r") {
388
+ out += "\r";
389
+ i++;
390
+ continue;
391
+ }
392
+ if (next === "t") {
393
+ out += "\t";
394
+ i++;
395
+ continue;
396
+ }
397
+ }
398
+ out += ch;
399
+ }
400
+ return out;
401
+ }
402
+
403
+ interface ParsedFrontmatter {
404
+ fields: Record<string, string>;
405
+ body: string;
406
+ }
407
+
408
+ function parsePeerFrontmatter(raw: string): ParsedFrontmatter {
409
+ // Frontmatter must begin with a `---` line. We tolerate a leading BOM
410
+ // and trailing newlines but otherwise require a strict, line-oriented
411
+ // YAML subset of `key: value` pairs.
412
+ const text = raw.replace(/^\uFEFF/, "");
413
+ if (!text.startsWith("---")) {
414
+ throw new Error("peer file is missing YAML frontmatter delimiter");
415
+ }
416
+ // Split on the first occurrence of `\n---` after the leading `---`.
417
+ const after = text.slice(3);
418
+ const close = after.indexOf("\n---");
419
+ if (close === -1) {
420
+ throw new Error("peer file frontmatter is not terminated");
421
+ }
422
+ const fmBlock = after.slice(0, close).replace(/^\n/, "");
423
+ const body = after.slice(close + 4).replace(/^\n/, "");
424
+ const fields: Record<string, string> = {};
425
+ for (const lineRaw of fmBlock.split("\n")) {
426
+ const line = lineRaw.trim();
427
+ if (line === "" || line.startsWith("#")) continue;
428
+ const colon = line.indexOf(":");
429
+ if (colon === -1) {
430
+ throw new Error(`peer frontmatter line is malformed: ${line}`);
431
+ }
432
+ const key = line.slice(0, colon).trim();
433
+ const valueRaw = line.slice(colon + 1).trim();
434
+ if (key === "") {
435
+ throw new Error(`peer frontmatter has empty key: ${line}`);
436
+ }
437
+ let value: string;
438
+ if (valueRaw.startsWith('"') && valueRaw.endsWith('"') && valueRaw.length >= 2) {
439
+ value = unescapeYamlString(valueRaw);
440
+ } else {
441
+ value = valueRaw;
442
+ }
443
+ fields[key] = value;
444
+ }
445
+ return { fields, body };
446
+ }
447
+
448
+ function emitPeerIdentity(peer: Peer): string {
449
+ const lines: string[] = ["---"];
450
+ lines.push(`id: ${escapeYamlString(peer.id)}`);
451
+ lines.push(`kind: ${peer.kind}`);
452
+ lines.push(`displayName: ${escapeYamlString(peer.displayName)}`);
453
+ // Cursor M: emit timestamps as quoted YAML strings — bare emission
454
+ // would let a `createdAt` value containing a newline inject extra
455
+ // frontmatter fields when round-tripped through `parsePeerFrontmatter`.
456
+ // (`writePeer` validates these are non-empty strings, but the type
457
+ // doesn't constrain content.)
458
+ lines.push(`createdAt: ${escapeYamlString(peer.createdAt)}`);
459
+ lines.push(`updatedAt: ${escapeYamlString(peer.updatedAt)}`);
460
+ lines.push("---");
461
+ lines.push("");
462
+ lines.push(peer.notes ?? "");
463
+ // Trailing newline for POSIX friendliness.
464
+ // CodeQL: the previous `replace(/\n+$/, "\n")` flagged as
465
+ // polynomial-regex risk because `\n+` can backtrack on long
466
+ // trailing-newline runs. Strip trailing newlines with a bounded
467
+ // loop instead — O(N) over the trailing-newline count, no regex.
468
+ let out = lines.join("\n");
469
+ while (out.endsWith("\n")) out = out.slice(0, -1);
470
+ return out + "\n";
471
+ }
472
+
473
+ // ──────────────────────────────────────────────────────────────────────
474
+ // Public storage API
475
+ // ──────────────────────────────────────────────────────────────────────
476
+
477
+ /**
478
+ * Read a peer's identity kernel.
479
+ *
480
+ * Returns `null` (does not throw) when the peer directory or identity
481
+ * file does not exist. Throws on filesystem errors other than ENOENT and
482
+ * on malformed files.
483
+ */
484
+ export async function readPeer(
485
+ memoryDir: string,
486
+ peerId: string,
487
+ ): Promise<Peer | null> {
488
+ assertValidPeerId(peerId);
489
+ await assertPeerDirNotEscaped(memoryDir, peerId);
490
+ const file = identityPath(memoryDir, peerId);
491
+ // Codex P1 #2: even with the directory validated, a symlinked
492
+ // identity.md inside a real peer dir would let us read arbitrary
493
+ // out-of-scope files. Reject symlinks at the file level too.
494
+ let raw: string;
495
+ try {
496
+ raw = await readFileNoFollow(file);
497
+ } catch (err) {
498
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
499
+ return null;
500
+ }
501
+ throw err;
502
+ }
503
+ const { fields, body } = parsePeerFrontmatter(raw);
504
+ const id = fields.id ?? peerId;
505
+ if (id !== peerId) {
506
+ throw new Error(
507
+ `peer identity file mismatch — expected id "${peerId}", file claims "${id}"`,
508
+ );
509
+ }
510
+ const kind = fields.kind;
511
+ assertValidKind(kind);
512
+ const displayName = fields.displayName ?? "";
513
+ const createdAt = fields.createdAt ?? "";
514
+ if (createdAt === "") {
515
+ throw new Error(`peer "${peerId}" is missing createdAt`);
516
+ }
517
+ // Codex P2: nullish-coalescing alone treated `updatedAt: ""` as a
518
+ // valid empty timestamp, contradicting writePeer's non-empty
519
+ // validation and the module's "malformed files throw" contract.
520
+ // Treat empty string as missing and fall back to createdAt; throw
521
+ // only when the field is malformed in some other way (caught by
522
+ // the typeof check).
523
+ const rawUpdatedAt = fields.updatedAt;
524
+ const updatedAt =
525
+ typeof rawUpdatedAt === "string" && rawUpdatedAt.length > 0
526
+ ? rawUpdatedAt
527
+ : createdAt;
528
+ // Codex P2 + CodeQL: previously used `body.replace(/^\s+/, "")`
529
+ // which stripped ALL leading whitespace — including indentation in
530
+ // notes. The `\s+` patterns also flagged as polynomial-regex risk
531
+ // (CodeQL alert #74) because they can backtrack on adversarial
532
+ // inputs. Strip exactly one leading separator newline and one
533
+ // trailing newline — internal AND user-authored leading
534
+ // indentation are preserved verbatim, and the regex is bounded.
535
+ let trimmedBody = body;
536
+ if (trimmedBody.startsWith("\r\n")) trimmedBody = trimmedBody.slice(2);
537
+ else if (trimmedBody.startsWith("\n")) trimmedBody = trimmedBody.slice(1);
538
+ if (trimmedBody.endsWith("\r\n")) trimmedBody = trimmedBody.slice(0, -2);
539
+ else if (trimmedBody.endsWith("\n")) trimmedBody = trimmedBody.slice(0, -1);
540
+ return {
541
+ id: peerId,
542
+ kind,
543
+ displayName,
544
+ createdAt,
545
+ updatedAt,
546
+ notes: trimmedBody === "" ? undefined : trimmedBody,
547
+ };
548
+ }
549
+
550
+ /**
551
+ * Write (create or overwrite) a peer's identity kernel.
552
+ *
553
+ * Creates `peers/{id}/` if it does not exist. Does not touch the peer's
554
+ * profile or interaction log. Atomic-write semantics are deferred to
555
+ * later PRs — for the schema slice we simply write the file.
556
+ */
557
+ export async function writePeer(memoryDir: string, peer: Peer): Promise<void> {
558
+ assertValidPeerId(peer.id);
559
+ assertValidKind(peer.kind);
560
+ if (typeof peer.displayName !== "string") {
561
+ throw new Error("peer.displayName must be a string");
562
+ }
563
+ if (typeof peer.createdAt !== "string" || peer.createdAt === "") {
564
+ throw new Error("peer.createdAt must be a non-empty ISO-8601 string");
565
+ }
566
+ if (typeof peer.updatedAt !== "string" || peer.updatedAt === "") {
567
+ throw new Error("peer.updatedAt must be a non-empty ISO-8601 string");
568
+ }
569
+ // Codex P2 round 8: reject non-string `peer.notes`. Without this,
570
+ // an untyped JS caller passing an object/number would silently
571
+ // coerce to "[object Object]"/"42" via lines.push(notes ?? "")
572
+ // and corrupt user-authored identity content. Notes are optional;
573
+ // omit by passing undefined (NOT null).
574
+ if (peer.notes !== undefined && typeof peer.notes !== "string") {
575
+ throw new Error("peer.notes must be a string when provided");
576
+ }
577
+ // Codex P1 round 13: atomic root validation. Open the peers root
578
+ // (or its parent if peers doesn't exist yet) with O_DIRECTORY|
579
+ // O_NOFOLLOW BEFORE the mkdir. This holds a kernel handle on the
580
+ // original-inode root, so a swap between the symlink check and
581
+ // the mkdir can't redirect mkdir to a symlink-target. We then
582
+ // mkdir, lstat the result against the held handle, and only
583
+ // proceed if they match.
584
+ await mkdirPeerDirAtomic(memoryDir, peer.id);
585
+ await assertPeerDirNotEscaped(memoryDir, peer.id);
586
+ const file = identityPath(memoryDir, peer.id);
587
+ // Codex P1 #2: reject if identity.md exists as a symlink so we
588
+ // don't follow it on overwrite.
589
+ await writeFileNoFollow(file, emitPeerIdentity(peer));
590
+ }
591
+
592
+ /**
593
+ * Delete a peer's `identity.md` if present, applying the same symlink
594
+ * and path-escape protections as the read/write paths.
595
+ *
596
+ * Returns `true` if a regular file was unlinked, `false` if no
597
+ * `identity.md` existed at the time of the call. The peer directory
598
+ * itself is left in place so any companion files (`profile.md`,
599
+ * `interactions/`, etc.) are untouched. Idempotent: missing target
600
+ * returns `false` rather than throwing.
601
+ *
602
+ * Cursor M (PR #756 review): a manual `path.join` + raw `fs.unlink`
603
+ * bypasses `assertPeerDirNotEscaped`, the peers-root symlink check,
604
+ * and the parent-inode-stable / lstat guards used by every other
605
+ * peer I/O entrypoint. A symlinked `peers/<id>/` could redirect the
606
+ * unlink to an arbitrary `identity.md` outside `memoryDir`. This
607
+ * function consolidates the safe-delete contract so callers cannot
608
+ * skip the guards.
609
+ */
610
+ export async function deletePeer(memoryDir: string, peerId: string): Promise<boolean> {
611
+ assertValidPeerId(peerId);
612
+ await assertPeerDirNotEscaped(memoryDir, peerId);
613
+ const file = identityPath(memoryDir, peerId);
614
+ // Refuse to follow a symlink at `identity.md` itself: lstat first
615
+ // and reject if the target is a symlink. Then verify the parent
616
+ // directory inode is stable across the unlink (mirrors the
617
+ // assertParentDirInodeStable + O_NOFOLLOW pattern used by
618
+ // writeFileNoFollow). This narrows the TOCTOU window between the
619
+ // lstat and the unlink to the same few microseconds the write path
620
+ // accepts.
621
+ let lstatBefore: import("node:fs").Stats;
622
+ try {
623
+ lstatBefore = await fs.lstat(file);
624
+ } catch (err) {
625
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
626
+ return false;
627
+ }
628
+ throw err;
629
+ }
630
+ if (lstatBefore.isSymbolicLink()) {
631
+ throw new Error(`refusing to unlink "${file}": target is a symlink`);
632
+ }
633
+ if (!lstatBefore.isFile()) {
634
+ throw new Error(`refusing to unlink "${file}": target is not a regular file`);
635
+ }
636
+ await assertParentDirInodeStable(file);
637
+ try {
638
+ await fs.unlink(file);
639
+ } catch (err) {
640
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
641
+ return false;
642
+ }
643
+ throw err;
644
+ }
645
+ return true;
646
+ }
647
+
648
+ /**
649
+ * Destructively purge the entire peer directory for a given peerId —
650
+ * `identity.md`, `profile.md`, `interactions.log.md`, and any other
651
+ * files written by future extensions.
652
+ *
653
+ * This is the DESTRUCTIVE counterpart to `deletePeer` (which only
654
+ * removes `identity.md`). Callers must pass `confirm: "yes"` to prevent
655
+ * accidental data loss — the function throws `Error("forgetPeer requires
656
+ * confirm: 'yes'")` when the flag is absent or wrong.
657
+ *
658
+ * Idempotent: if the peer directory does not exist the function returns
659
+ * `{ purged: false }` rather than throwing. Safe to run twice.
660
+ *
661
+ * Security contract (mirrors `deletePeer`):
662
+ * - `peerId` is validated against `PEER_ID_PATTERN`.
663
+ * - The peers root is checked for symlink swap (assertPeersRootNotSymlink).
664
+ * - `assertPeerDirNotEscaped` performs realpath containment: symlinked
665
+ * peer directories are rejected and the resolved path is confirmed to
666
+ * stay inside peersRoot (same guard used by every other I/O entry-point).
667
+ * - A secondary `lstat` + `isSymbolicLink()` check is kept as defence-in-
668
+ * depth before the `fs.rm` call itself.
669
+ * - `fs.rm` with `{ recursive: true, force: true }` is used for the
670
+ * actual removal so partially-populated directories are handled
671
+ * atomically by the OS rather than file-by-file in userland.
672
+ *
673
+ * Returns:
674
+ * `{ purged: true }` — directory existed and was removed.
675
+ * `{ purged: false }` — directory did not exist (no-op).
676
+ */
677
+ export async function forgetPeer(
678
+ memoryDir: string,
679
+ peerId: string,
680
+ opts: { confirm: string },
681
+ ): Promise<{ purged: boolean }> {
682
+ if (opts.confirm !== "yes") {
683
+ throw new Error("forgetPeer requires confirm: 'yes'");
684
+ }
685
+ assertValidPeerId(peerId);
686
+ await assertPeersRootNotSymlink(memoryDir);
687
+ // assertPeerDirNotEscaped performs the realpath-containment check that
688
+ // the lexical `peerDir()` guard alone cannot provide: it lstat-checks
689
+ // the candidate, rejects symlinks, and confirms the resolved path stays
690
+ // inside peersRoot. Mirrors the contract of every other I/O entry-point
691
+ // in this module (`readPeer`, `writePeer`, `deletePeer`,
692
+ // `appendInteractionLog`, `readPeerProfile`, `writePeerProfile`).
693
+ // The function handles ENOENT gracefully (returns without throwing), so
694
+ // calling it here is safe for the idempotent no-op path as well.
695
+ await assertPeerDirNotEscaped(memoryDir, peerId);
696
+
697
+ const dir = peerDir(memoryDir, peerId);
698
+
699
+ // lstat the candidate directory. Return early (idempotent) if it
700
+ // doesn't exist. Reject if it resolves to a symlink.
701
+ let dirStat: import("node:fs").Stats;
702
+ try {
703
+ dirStat = await fs.lstat(dir);
704
+ } catch (err) {
705
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
706
+ return { purged: false };
707
+ }
708
+ throw err;
709
+ }
710
+ if (dirStat.isSymbolicLink()) {
711
+ throw new Error(
712
+ `refusing to purge peer directory "${peerId}": target is a symlink`,
713
+ );
714
+ }
715
+ if (!dirStat.isDirectory()) {
716
+ throw new Error(
717
+ `refusing to purge peer directory "${peerId}": target is not a directory`,
718
+ );
719
+ }
720
+
721
+ // Perform the recursive removal. `fs.rm` with `{ recursive: true }`
722
+ // is the correct API (replaces the deprecated `fs.rmdir`). The
723
+ // `force: true` flag makes it a no-op if the directory was already
724
+ // removed between our lstat and this call (race-safe idempotency).
725
+ await fs.rm(dir, { recursive: true, force: true });
726
+ return { purged: true };
727
+ }
728
+
729
+ /**
730
+ * Enumerate all peers under `memoryDir/peers/`.
731
+ *
732
+ * Returns an empty array if the peers root does not exist. Subdirectories
733
+ * whose name fails `PEER_ID_PATTERN` are skipped (defensive: the user
734
+ * may have hand-edited the directory). Directories that exist but lack
735
+ * `identity.md` are also skipped.
736
+ */
737
+ export async function listPeers(memoryDir: string): Promise<Peer[]> {
738
+ // Codex P2 round 13: atomic readdir against the root. Open the
739
+ // peers directory with O_DIRECTORY|O_NOFOLLOW first so we hold the
740
+ // original-inode handle, then readdir from that handle. A
741
+ // root-symlink swap between assertPeersRootNotSymlink and a path-
742
+ // based readdir is no longer possible — the kernel rejects the
743
+ // open if the path resolves to a symlink, and the dirHandle.read()
744
+ // operates on the original inode regardless of subsequent path-
745
+ // based mutations.
746
+ const root = peersRoot(memoryDir);
747
+ // Cursor M round 14: Node's FileHandle has no `readdir` method
748
+ // (only `fs.readdir(path)` is exposed), so we can't read directly
749
+ // from the handle. Instead anchor the original-inode by opening
750
+ // the dir with O_NOFOLLOW, fstat it, then do path-based readdir
751
+ // and confirm the path's lstat AFTER matches the held inode. If
752
+ // it diverges, a swap happened — abort. This is a fence around
753
+ // the readdir, not the kernel-level guarantee a true `fdreaddir`
754
+ // would give, but it closes the path-traversal race without
755
+ // requiring native bindings.
756
+ let entries: string[];
757
+ let dh: import("node:fs/promises").FileHandle | null = null;
758
+ try {
759
+ dh = await fs.open(
760
+ root,
761
+ fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW,
762
+ );
763
+ const fstatBefore = await dh.stat();
764
+ entries = await fs.readdir(root);
765
+ const lstatAfter = await fs.lstat(root);
766
+ if (
767
+ fstatBefore.ino !== lstatAfter.ino ||
768
+ fstatBefore.dev !== lstatAfter.dev ||
769
+ lstatAfter.isSymbolicLink()
770
+ ) {
771
+ throw new Error(`peers root "${root}" was swapped during readdir`);
772
+ }
773
+ } catch (err) {
774
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
775
+ return [];
776
+ }
777
+ throw err;
778
+ } finally {
779
+ if (dh) await dh.close();
780
+ }
781
+ const peers: Peer[] = [];
782
+ // Sort for deterministic ordering — callers that need a different
783
+ // sort order can re-sort the result.
784
+ entries.sort();
785
+ for (const name of entries) {
786
+ if (!PEER_ID_PATTERN.test(name) || name.length > PEER_ID_MAX_LENGTH) {
787
+ continue;
788
+ }
789
+ let stat;
790
+ try {
791
+ // Codex P1: use `lstat` so we don't follow symlinks. A
792
+ // `peers/<valid-id>` symlink pointing at an arbitrary directory
793
+ // would otherwise let listPeers (and the readPeer that
794
+ // follows) traverse outside the peers root.
795
+ stat = await fs.lstat(path.join(root, name));
796
+ } catch (err) {
797
+ // Codex P2 round 13: don't swallow non-ENOENT errors.
798
+ // Permission-denied / EACCES / EIO need to surface so the
799
+ // operator sees a real I/O problem rather than a silently
800
+ // truncated peer list.
801
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") continue;
802
+ throw err;
803
+ }
804
+ // Skip symlinks entirely — only real directories are peers.
805
+ if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
806
+ let peer: Peer | null = null;
807
+ try {
808
+ peer = await readPeer(memoryDir, name);
809
+ } catch (err) {
810
+ // Cursor M round 14: classifying by `.code` alone treated
811
+ // security-check failures (assertPeerDirNotEscaped /
812
+ // assertParentDirInodeStable / "is a symlink and is rejected"
813
+ // / "inode mismatch") as parse failures and silently skipped
814
+ // them. Those messages are critical signals — an attacker
815
+ // attempted to redirect a read outside the peers root. Match
816
+ // by message prefix to detect them and propagate.
817
+ const code = (err as NodeJS.ErrnoException).code;
818
+ const message = err instanceof Error ? err.message : String(err);
819
+ const isSecurityFailure =
820
+ message.startsWith("peers root") ||
821
+ message.startsWith("peer directory") ||
822
+ message.startsWith("parent directory") ||
823
+ message.startsWith("path ") /* "path \"...\" is a symlink" */ ||
824
+ message.includes("escapes the peers root") ||
825
+ message.includes("inode mismatch") ||
826
+ message.includes("is a symlink and is rejected") ||
827
+ message.includes("was swapped");
828
+ if (isSecurityFailure) throw err;
829
+ // Real I/O errors (EACCES, EIO, EBUSY, etc.) propagate too.
830
+ if (code && code !== "ENOENT") throw err;
831
+ // Schema/parse failures fall through and skip the entry.
832
+ continue;
833
+ }
834
+ if (peer !== null) {
835
+ peers.push(peer);
836
+ }
837
+ }
838
+ return peers;
839
+ }
840
+
841
+ // ──────────────────────────────────────────────────────────────────────
842
+ // Interaction log (append-only)
843
+ // ──────────────────────────────────────────────────────────────────────
844
+
845
+ function sanitizeLogField(value: string): string {
846
+ // Cursor Medium: every interaction-log field — not just summary —
847
+ // must collapse newlines so a malicious or buggy `timestamp` /
848
+ // `kind` / `sessionId` value can't break the one-line-per-entry
849
+ // invariant the append-only log relies on. Replace CR/LF/Tab with
850
+ // a single space; trim leading/trailing whitespace.
851
+ return value.replace(/[\r\n\t]+/g, " ").trim();
852
+ }
853
+
854
+ function formatLogEntry(entry: PeerInteractionLogEntry): string {
855
+ // One line per entry. We use a leading bullet so the file remains
856
+ // readable as ordinary markdown. Order: timestamp, kind, optional
857
+ // session id, summary. ALL fields are passed through `sanitizeLogField`
858
+ // so a stray newline anywhere can't shatter the append-only invariant
859
+ // (cursor Medium on PR #723).
860
+ //
861
+ // Cursor Low on PR #736: the previous bare `session=<id>` token was
862
+ // ambiguous with summaries that literally start with `session=`
863
+ // (e.g. summary `"session=foo bar"` round-tripped to
864
+ // `{sessionId: "foo", summary: "bar"}`). New entries wrap the
865
+ // session marker in square brackets — `[session=<id>]` — which a
866
+ // sanitized summary can never start with (sanitizeLogField never
867
+ // emits `[` as the first character of a summary that originally
868
+ // started with `session=`, and bracketed metadata is unambiguously
869
+ // distinct from `session=` text). Old entries written by previous
870
+ // code remain parseable through the legacy fallback in
871
+ // `parseLogLine`.
872
+ const ts = sanitizeLogField(entry.timestamp);
873
+ const kind = sanitizeLogField(entry.kind);
874
+ const summary = sanitizeLogField(entry.summary);
875
+ const session = entry.sessionId
876
+ ? ` [session=${sanitizeLogField(entry.sessionId)}]`
877
+ : "";
878
+ return `- [${ts}] (${kind})${session} ${summary}`;
879
+ }
880
+
881
+ /**
882
+ * Append one entry to a peer's interaction log.
883
+ *
884
+ * Creates `peers/{id}/` and `interactions.log.md` if needed. The file is
885
+ * append-only by contract — this helper never rewrites prior entries.
886
+ * Returns the absolute path of the log file (useful for tests).
887
+ */
888
+ export async function appendInteractionLog(
889
+ memoryDir: string,
890
+ peerId: string,
891
+ entry: PeerInteractionLogEntry,
892
+ ): Promise<string> {
893
+ assertValidPeerId(peerId);
894
+ // Codex P2 round 10: trim() before the empty check matches what
895
+ // sanitizeLogField does at format time. A whitespace-only
896
+ // `timestamp` or `kind` would previously pass validation and then
897
+ // be normalized to "" later, producing an entry like `- [] ()`
898
+ // that breaks downstream parsers.
899
+ if (typeof entry.timestamp !== "string" || entry.timestamp.trim() === "") {
900
+ throw new Error("interaction entry must have a non-whitespace timestamp");
901
+ }
902
+ if (typeof entry.kind !== "string" || entry.kind.trim() === "") {
903
+ throw new Error("interaction entry must have a non-whitespace kind");
904
+ }
905
+ if (typeof entry.summary !== "string") {
906
+ throw new Error("interaction entry must have a string summary");
907
+ }
908
+ // Codex P2 round 6/8/9: optional sessionId must be a non-empty
909
+ // string OR strictly `undefined` (omitted). null and empty string
910
+ // are both rejected explicitly: the previous behavior silently
911
+ // dropped them during formatting, which reinterprets invalid input
912
+ // instead of failing fast like other validators in this module.
913
+ // Codex P2 round 12: trim() before the empty check matches what
914
+ // sanitizeLogField does at format time. Whitespace-only sessionId
915
+ // would otherwise pass validation and produce a `session=` token
916
+ // with an empty value that breaks downstream log parsers.
917
+ if (entry.sessionId !== undefined) {
918
+ if (typeof entry.sessionId !== "string" || entry.sessionId.trim() === "") {
919
+ throw new Error("interaction entry sessionId must be a non-whitespace string when provided");
920
+ }
921
+ }
922
+ await mkdirPeerDirAtomic(memoryDir, peerId);
923
+ await assertPeerDirNotEscaped(memoryDir, peerId);
924
+ const file = interactionsPath(memoryDir, peerId);
925
+ const line = formatLogEntry(entry) + "\n";
926
+ // `appendFile` creates the file if it does not exist. POSIX guarantees
927
+ // writes < PIPE_BUF are atomic; entries on this path are well under
928
+ // that bound. Ordering across concurrent writers is the caller's
929
+ // responsibility for now — the reasoner runs serially in PR 2/5.
930
+ await appendFileNoFollow(file, line);
931
+ return file;
932
+ }
933
+
934
+ // ──────────────────────────────────────────────────────────────────────
935
+ // Profile read/write (schema scaffold; reasoner ships in PR 2/5)
936
+ // ──────────────────────────────────────────────────────────────────────
937
+
938
+ interface ProfileFile {
939
+ updatedAt: string;
940
+ fields: Record<string, string>;
941
+ provenance: Record<string, PeerProfileFieldProvenance[]>;
942
+ }
943
+
944
+ function emitPeerProfile(profile: PeerProfile): string {
945
+ // Profiles use a JSON-in-fenced-code-block payload inside a markdown
946
+ // file so they remain human-readable. The frontmatter holds the
947
+ // updatedAt stamp; the body holds the full structured payload.
948
+ const payload: ProfileFile = {
949
+ updatedAt: profile.updatedAt,
950
+ fields: { ...profile.fields },
951
+ provenance: Object.fromEntries(
952
+ Object.entries(profile.provenance).map(([k, v]) => [k, [...v]]),
953
+ ),
954
+ };
955
+ const json = JSON.stringify(payload, null, 2);
956
+ return [
957
+ "---",
958
+ `peerId: ${escapeYamlString(profile.peerId)}`,
959
+ `updatedAt: ${escapeYamlString(profile.updatedAt)}`,
960
+ "---",
961
+ "",
962
+ "<!-- peer profile — managed by the async reasoner. Manual edits will be overwritten. -->",
963
+ "",
964
+ "```json",
965
+ json,
966
+ "```",
967
+ "",
968
+ ].join("\n");
969
+ }
970
+
971
+ function parsePeerProfile(raw: string, peerId: string): PeerProfile {
972
+ const { fields: fm, body } = parsePeerFrontmatter(raw);
973
+ if (fm.peerId !== undefined && fm.peerId !== peerId) {
974
+ throw new Error(
975
+ `peer profile mismatch — expected "${peerId}", file claims "${fm.peerId}"`,
976
+ );
977
+ }
978
+ const fenceMatch = body.match(/```json\s*\n([\s\S]*?)\n```/);
979
+ if (!fenceMatch) {
980
+ throw new Error(`peer profile for "${peerId}" is missing JSON payload`);
981
+ }
982
+ let parsed: unknown;
983
+ try {
984
+ parsed = JSON.parse(fenceMatch[1]);
985
+ } catch (err) {
986
+ throw new Error(
987
+ `peer profile for "${peerId}" has invalid JSON: ${(err as Error).message}`,
988
+ );
989
+ }
990
+ if (typeof parsed !== "object" || parsed === null) {
991
+ throw new Error(`peer profile for "${peerId}" is not an object`);
992
+ }
993
+ const payload = parsed as Partial<ProfileFile>;
994
+ // Codex P2: only accept string updatedAt values. A malformed payload
995
+ // like `{ "updatedAt": 123 }` would previously short-circuit through
996
+ // `payload.updatedAt ?? fm.updatedAt ?? ""` and produce a `PeerProfile`
997
+ // whose updatedAt is a number — corrupting any downstream code that
998
+ // assumes the contract.
999
+ const payloadUpdatedAt = typeof payload.updatedAt === "string" ? payload.updatedAt : undefined;
1000
+ const updatedAt = payloadUpdatedAt ?? fm.updatedAt ?? "";
1001
+ if (typeof updatedAt !== "string" || updatedAt === "") {
1002
+ throw new Error(`peer profile for "${peerId}" is missing updatedAt`);
1003
+ }
1004
+ // Codex P2 round 10: a malformed `fields: "wat"` or
1005
+ // `provenance: 42` previously coerced to {} and silently dropped
1006
+ // the section. That contradicts the "malformed files throw"
1007
+ // contract — the file IS malformed, not just empty. Reject loudly.
1008
+ // `undefined` is still tolerated (an older profile file might omit
1009
+ // the section entirely).
1010
+ let fieldsObj: object;
1011
+ if (payload.fields === undefined) {
1012
+ fieldsObj = {};
1013
+ } else if (
1014
+ typeof payload.fields === "object" &&
1015
+ payload.fields !== null &&
1016
+ !Array.isArray(payload.fields)
1017
+ ) {
1018
+ fieldsObj = payload.fields;
1019
+ } else {
1020
+ throw new Error(`peer profile for "${peerId}" has malformed fields section`);
1021
+ }
1022
+ let provenanceObj: object;
1023
+ if (payload.provenance === undefined) {
1024
+ provenanceObj = {};
1025
+ } else if (
1026
+ typeof payload.provenance === "object" &&
1027
+ payload.provenance !== null &&
1028
+ !Array.isArray(payload.provenance)
1029
+ ) {
1030
+ provenanceObj = payload.provenance;
1031
+ } else {
1032
+ throw new Error(`peer profile for "${peerId}" has malformed provenance section`);
1033
+ }
1034
+ // Coerce values defensively. We never trust the on-disk shape.
1035
+ // Codex P1: skip prototype-pollution keys explicitly. We don't use
1036
+ // null-prototype objects in the returned shape because callers
1037
+ // (tests, downstream consumers) expect plain objects with normal
1038
+ // semantics — the assertion `assert.deepEqual` differentiates by
1039
+ // prototype. The skip-list is the load-bearing defense; iteration
1040
+ // via Object.entries() of attacker-controlled JSON objects is safe
1041
+ // as long as we never assign through dangerous keys.
1042
+ const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
1043
+ const fields: Record<string, string> = {};
1044
+ for (const [k, v] of Object.entries(fieldsObj)) {
1045
+ if (DANGEROUS_KEYS.has(k)) continue;
1046
+ if (typeof v === "string") fields[k] = v;
1047
+ }
1048
+ const provenance: Record<string, PeerProfileFieldProvenance[]> = {};
1049
+ for (const [k, v] of Object.entries(provenanceObj)) {
1050
+ if (DANGEROUS_KEYS.has(k)) continue;
1051
+ if (!Array.isArray(v)) continue;
1052
+ const list: PeerProfileFieldProvenance[] = [];
1053
+ for (const item of v) {
1054
+ if (
1055
+ typeof item !== "object" ||
1056
+ item === null ||
1057
+ Array.isArray(item)
1058
+ ) {
1059
+ continue;
1060
+ }
1061
+ const r = item as unknown as Record<string, unknown>;
1062
+ // Codex P2 round 9: empty observedAt/signal strings should be
1063
+ // treated as malformed, not valid. Drop the entry.
1064
+ if (typeof r.observedAt !== "string" || r.observedAt === "") continue;
1065
+ if (typeof r.signal !== "string" || r.signal === "") continue;
1066
+ // Codex P2 round 6: previously the optional fields were never
1067
+ // type-checked, so a hand-edited `{sourceSessionId: 123}`
1068
+ // survived and corrupted the PeerProfileFieldProvenance contract.
1069
+ // Build a clean record with only string-typed optional fields.
1070
+ const clean: PeerProfileFieldProvenance = {
1071
+ observedAt: r.observedAt,
1072
+ signal: r.signal,
1073
+ ...(typeof r.sourceSessionId === "string" && r.sourceSessionId.length > 0
1074
+ ? { sourceSessionId: r.sourceSessionId }
1075
+ : {}),
1076
+ ...(typeof r.note === "string" && r.note.length > 0
1077
+ ? { note: r.note }
1078
+ : {}),
1079
+ };
1080
+ list.push(clean);
1081
+ }
1082
+ provenance[k] = list;
1083
+ }
1084
+ return { peerId, updatedAt, fields, provenance };
1085
+ }
1086
+
1087
+ /**
1088
+ * Read a peer's profile. Returns null if the profile file does not exist.
1089
+ *
1090
+ * The PR-1 surface only ships the structured read/write so the reasoner
1091
+ * (PR 2/5) and recall integration (PR 3/5) have a stable target. We do
1092
+ * not yet expose any field-update helpers.
1093
+ */
1094
+ export async function readPeerProfile(
1095
+ memoryDir: string,
1096
+ peerId: string,
1097
+ ): Promise<PeerProfile | null> {
1098
+ assertValidPeerId(peerId);
1099
+ await assertPeerDirNotEscaped(memoryDir, peerId);
1100
+ const file = profilePath(memoryDir, peerId);
1101
+ let raw: string;
1102
+ try {
1103
+ raw = await readFileNoFollow(file);
1104
+ } catch (err) {
1105
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
1106
+ return null;
1107
+ }
1108
+ throw err;
1109
+ }
1110
+ return parsePeerProfile(raw, peerId);
1111
+ }
1112
+
1113
+ /**
1114
+ * Write (create or overwrite) a peer's profile.
1115
+ */
1116
+ export async function writePeerProfile(
1117
+ memoryDir: string,
1118
+ profile: PeerProfile,
1119
+ ): Promise<void> {
1120
+ assertValidPeerId(profile.peerId);
1121
+ if (typeof profile.updatedAt !== "string" || profile.updatedAt === "") {
1122
+ throw new Error("profile.updatedAt must be a non-empty ISO-8601 string");
1123
+ }
1124
+ // Codex P2 round 6: validate the nested payload shape on write so
1125
+ // round-trip semantics are preserved. Without this, untyped JS
1126
+ // callers can persist non-string field values or malformed
1127
+ // provenance entries — readPeerProfile silently scrubs them on
1128
+ // the way back, so data is lost without an error. Fail fast at
1129
+ // the boundary instead.
1130
+ if (!isPlainObject(profile.fields)) {
1131
+ throw new Error("profile.fields must be a plain object");
1132
+ }
1133
+ // Codex P2 round 11: parsePeerProfile drops "__proto__" /
1134
+ // "constructor" / "prototype" keys on read. Without symmetric
1135
+ // rejection on write, those keys silently disappear during
1136
+ // round-trip — failing the contract that what writes succeeds
1137
+ // also reads back. Reject at the boundary so JS callers learn
1138
+ // immediately that those keys aren't allowed.
1139
+ const RESERVED_KEYS: ReadonlySet<string> = new Set(["__proto__", "constructor", "prototype"]);
1140
+ for (const [key, value] of Object.entries(profile.fields)) {
1141
+ if (RESERVED_KEYS.has(key)) {
1142
+ throw new Error(`profile.fields key "${key}" is reserved and cannot be persisted`);
1143
+ }
1144
+ if (typeof value !== "string") {
1145
+ throw new Error(`profile.fields["${key}"] must be a string`);
1146
+ }
1147
+ }
1148
+ if (!isPlainObject(profile.provenance)) {
1149
+ throw new Error("profile.provenance must be a plain object");
1150
+ }
1151
+ for (const [key, list] of Object.entries(profile.provenance)) {
1152
+ if (RESERVED_KEYS.has(key)) {
1153
+ throw new Error(`profile.provenance key "${key}" is reserved and cannot be persisted`);
1154
+ }
1155
+ if (!Array.isArray(list)) {
1156
+ throw new Error(`profile.provenance["${key}"] must be an array`);
1157
+ }
1158
+ for (let i = 0; i < list.length; i++) {
1159
+ const item = list[i] as unknown;
1160
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
1161
+ throw new Error(`profile.provenance["${key}"][${i}] must be an object`);
1162
+ }
1163
+ const r = item as Record<string, unknown>;
1164
+ if (typeof r.observedAt !== "string" || r.observedAt === "") {
1165
+ throw new Error(`profile.provenance["${key}"][${i}].observedAt must be a non-empty string`);
1166
+ }
1167
+ if (typeof r.signal !== "string" || r.signal === "") {
1168
+ throw new Error(`profile.provenance["${key}"][${i}].signal must be a non-empty string`);
1169
+ }
1170
+ // Cursor M round 9: empty optional strings would round-trip lose
1171
+ // (parsePeerProfile drops them on read). Reject at the boundary
1172
+ // for consistency with other validators in this module.
1173
+ if (r.sourceSessionId !== undefined) {
1174
+ if (typeof r.sourceSessionId !== "string" || r.sourceSessionId === "") {
1175
+ throw new Error(`profile.provenance["${key}"][${i}].sourceSessionId must be a non-empty string when provided`);
1176
+ }
1177
+ }
1178
+ if (r.note !== undefined) {
1179
+ if (typeof r.note !== "string" || r.note === "") {
1180
+ throw new Error(`profile.provenance["${key}"][${i}].note must be a non-empty string when provided`);
1181
+ }
1182
+ }
1183
+ }
1184
+ }
1185
+ await mkdirPeerDirAtomic(memoryDir, profile.peerId);
1186
+ await assertPeerDirNotEscaped(memoryDir, profile.peerId);
1187
+ const file = profilePath(memoryDir, profile.peerId);
1188
+ await writeFileNoFollow(file, emitPeerProfile(profile));
1189
+ }
1190
+
1191
+ /**
1192
+ * Read the raw interaction log for a peer.
1193
+ *
1194
+ * Returns the empty string if the log does not yet exist. Callers parse
1195
+ * the log themselves — this PR does not ship structured log parsing.
1196
+ * Exposed primarily so tests can verify monotonic append semantics.
1197
+ */
1198
+ export async function readInteractionLogRaw(
1199
+ memoryDir: string,
1200
+ peerId: string,
1201
+ ): Promise<string> {
1202
+ assertValidPeerId(peerId);
1203
+ await assertPeerDirNotEscaped(memoryDir, peerId);
1204
+ const file = interactionsPath(memoryDir, peerId);
1205
+ try {
1206
+ return await readFileNoFollow(file);
1207
+ } catch (err) {
1208
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
1209
+ return "";
1210
+ }
1211
+ throw err;
1212
+ }
1213
+ }
1214
+
1215
+ /**
1216
+ * Inverse of `formatLogEntry`. Used by `readPeerInteractionLog` to
1217
+ * convert the on-disk one-line bullet form back into a structured
1218
+ * `PeerInteractionLogEntry`. Returns `null` for malformed lines so
1219
+ * callers can keep the rest of the log even when a single entry was
1220
+ * hand-edited or partially written.
1221
+ *
1222
+ * Formats accepted (must match `formatLogEntry`):
1223
+ *
1224
+ * - [TS] (KIND) [session=SID] SUMMARY // canonical (PR #736)
1225
+ * - [TS] (KIND) SUMMARY // session optional
1226
+ *
1227
+ * The unbracketed `session=SID` form (which would have been written
1228
+ * by an earlier draft of #679 PR 2/5) is intentionally NOT parsed —
1229
+ * the cursor #736 finding showed it was indistinguishable from a
1230
+ * summary that legitimately starts with `session=`. Since #679 PR
1231
+ * 2/5 is the first shipped writer for `sessionId` AND ships the
1232
+ * bracketed canonical form simultaneously, there is no real legacy
1233
+ * data on disk to support. A summary like `session=foo bar` thus
1234
+ * round-trips verbatim into `summary` rather than being mis-claimed
1235
+ * as a session id.
1236
+ *
1237
+ * Whitespace inside SUMMARY is preserved verbatim. The parser is
1238
+ * deliberately strict — anything that doesn't start with `- [` is
1239
+ * rejected and dropped (the file is also markdown-friendly, so blank
1240
+ * lines and stray prose are simply ignored).
1241
+ */
1242
+ function parseLogLine(line: string): PeerInteractionLogEntry | null {
1243
+ if (!line.startsWith("- [")) return null;
1244
+ const tsClose = line.indexOf("]", 3);
1245
+ if (tsClose === -1) return null;
1246
+ const timestamp = line.slice(3, tsClose).trim();
1247
+ if (timestamp === "") return null;
1248
+ // Skip optional whitespace between `]` and `(`.
1249
+ let cursor = tsClose + 1;
1250
+ while (cursor < line.length && line[cursor] === " ") cursor += 1;
1251
+ if (line[cursor] !== "(") return null;
1252
+ const kindOpen = cursor;
1253
+ const kindClose = line.indexOf(")", kindOpen + 1);
1254
+ if (kindClose === -1) return null;
1255
+ const kind = line.slice(kindOpen + 1, kindClose).trim();
1256
+ if (kind === "") return null;
1257
+ cursor = kindClose + 1;
1258
+ let sessionId: string | undefined;
1259
+ // Optional session id token. We tolerate any leading whitespace
1260
+ // count; `formatLogEntry` always emits exactly one space, but
1261
+ // operator-edited logs may diverge.
1262
+ while (cursor < line.length && line[cursor] === " ") cursor += 1;
1263
+ // Canonical form (PR #736): `[session=<id>]`. Unambiguous because
1264
+ // a sanitized summary cannot start with `[session=` followed by a
1265
+ // closing bracket and a space — sanitization preserves user content
1266
+ // verbatim except for newlines/CR/tab, so a summary that literally
1267
+ // begins `[session=foo]` would imply the OPERATOR wrote a real
1268
+ // session marker, which is acceptable as session attribution.
1269
+ if (line.startsWith("[session=", cursor)) {
1270
+ const close = line.indexOf("]", cursor + "[session=".length);
1271
+ // A `[session=...]` with no closing bracket on the same line is
1272
+ // malformed metadata; treat the whole tail as summary instead of
1273
+ // misclaiming a session id.
1274
+ if (close > -1) {
1275
+ const sid = line.slice(cursor + "[session=".length, close).trim();
1276
+ if (sid.length > 0) sessionId = sid;
1277
+ cursor = close + 1;
1278
+ }
1279
+ }
1280
+ // NOTE: the legacy unbracketed `session=<id>` form is intentionally
1281
+ // NOT parsed. The cursor #736 finding is fundamentally a format
1282
+ // ambiguity: `- [TS] (KIND) session=foo bar` is indistinguishable
1283
+ // from a real session token vs. a summary that begins with the
1284
+ // literal text `session=foo bar`. Since #679 PR 2/5 is the first
1285
+ // PR that writes `sessionId` to the log AND ships the bracketed
1286
+ // canonical form simultaneously, there is no production legacy
1287
+ // data to support. Old `session=`-style summaries — both real
1288
+ // operator notes and hypothetical legacy entries — round-trip
1289
+ // verbatim into `summary` rather than being silently mis-claimed
1290
+ // as a session id.
1291
+ // Remaining tail is the summary (possibly empty if the log was
1292
+ // hand-edited; we still accept "" because `formatLogEntry` itself
1293
+ // accepts an empty summary string).
1294
+ while (cursor < line.length && line[cursor] === " ") cursor += 1;
1295
+ const summary = line.slice(cursor);
1296
+ return sessionId === undefined
1297
+ ? { timestamp, kind, summary }
1298
+ : { timestamp, kind, sessionId, summary };
1299
+ }
1300
+
1301
+ /**
1302
+ * Read the structured interaction log for a peer.
1303
+ *
1304
+ * Returns an empty array when the log file does not exist. Each line
1305
+ * is parsed via `parseLogLine`; malformed lines are silently skipped
1306
+ * so the reasoner can still derive profile fields from a partially
1307
+ * corrupt log rather than aborting the whole pass.
1308
+ *
1309
+ * `options.limit` (when > 0) restricts the result to the most recent
1310
+ * N entries. `options.afterTimestamp` (ISO-8601) filters out entries
1311
+ * with `timestamp < afterTimestamp` (string compare is safe for
1312
+ * canonical ISO-8601 form). Both filters are applied in order:
1313
+ * timestamp first, then limit.
1314
+ *
1315
+ * Order: oldest → newest, matching the append-only on-disk order so
1316
+ * downstream consumers can reason about temporal evolution without
1317
+ * re-sorting.
1318
+ */
1319
+ export async function readPeerInteractionLog(
1320
+ memoryDir: string,
1321
+ peerId: string,
1322
+ options: { limit?: number; afterTimestamp?: string } = {},
1323
+ ): Promise<PeerInteractionLogEntry[]> {
1324
+ const raw = await readInteractionLogRaw(memoryDir, peerId);
1325
+ if (raw === "") return [];
1326
+ const entries: PeerInteractionLogEntry[] = [];
1327
+ // Split on bare newlines; logs are written with a trailing `\n` per
1328
+ // entry by `appendInteractionLog`, so the final element after split
1329
+ // is typically the empty string. Both `\r\n` and `\n` are tolerated.
1330
+ for (const lineRaw of raw.split(/\r?\n/)) {
1331
+ const line = lineRaw.trimEnd();
1332
+ if (line === "") continue;
1333
+ const parsed = parseLogLine(line);
1334
+ if (parsed === null) continue;
1335
+ entries.push(parsed);
1336
+ }
1337
+ let filtered = entries;
1338
+ if (typeof options.afterTimestamp === "string" && options.afterTimestamp.length > 0) {
1339
+ const cutoff = options.afterTimestamp;
1340
+ filtered = filtered.filter((e) => e.timestamp > cutoff);
1341
+ }
1342
+ // Gotcha #27: guard slice(-n) against n === 0 — `slice(-0)` returns
1343
+ // the entire array. Treat 0 and negatives as "no limit applied".
1344
+ if (typeof options.limit === "number" && options.limit > 0) {
1345
+ if (filtered.length > options.limit) {
1346
+ filtered = filtered.slice(filtered.length - options.limit);
1347
+ }
1348
+ }
1349
+ return filtered;
1350
+ }