@remnic/core 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (489) hide show
  1. package/dist/abort-error.js +1 -0
  2. package/dist/abstraction-nodes.js +1 -0
  3. package/dist/access-audit.js +1 -0
  4. package/dist/access-cli.js +72 -47
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-http.d.ts +50 -5
  7. package/dist/access-http.js +39 -16
  8. package/dist/access-idempotency.js +1 -0
  9. package/dist/access-mcp.d.ts +10 -5
  10. package/dist/access-mcp.js +38 -14
  11. package/dist/access-schema.d.ts +133 -13
  12. package/dist/access-schema.js +20 -1
  13. package/dist/access-service-CtXFnprR.d.ts +2033 -0
  14. package/dist/access-service.d.ts +11 -6
  15. package/dist/access-service.js +40 -15
  16. package/dist/active-memory-bridge.js +1 -0
  17. package/dist/active-recall.js +1 -0
  18. package/dist/active-recall.js.map +1 -1
  19. package/dist/behavior-learner.js +1 -0
  20. package/dist/behavior-learner.js.map +1 -1
  21. package/dist/behavior-signals.js +1 -0
  22. package/dist/bootstrap.d.ts +6 -4
  23. package/dist/bootstrap.js +1 -0
  24. package/dist/boxes.js +1 -0
  25. package/dist/briefing.d.ts +9 -5
  26. package/dist/briefing.js +10 -7
  27. package/dist/buffer-surprise-report.js +1 -0
  28. package/dist/buffer-surprise.js +1 -0
  29. package/dist/buffer.d.ts +1 -1
  30. package/dist/buffer.js +1 -0
  31. package/dist/calibration.d.ts +8 -1
  32. package/dist/calibration.js +10 -2
  33. package/dist/calibration.js.map +1 -1
  34. package/dist/capsule-cli.d.ts +137 -0
  35. package/dist/capsule-cli.js +34 -0
  36. package/dist/capsule-crypto-5CYAGVC5.js +18 -0
  37. package/dist/capsule-export-NZQPOTQ4.js +17 -0
  38. package/dist/capsule-export-NZQPOTQ4.js.map +1 -0
  39. package/dist/capsule-import-SDCUXLEV.js +16 -0
  40. package/dist/capsule-import-SDCUXLEV.js.map +1 -0
  41. package/dist/capsule-merge-DI7PNQ2H.js +189 -0
  42. package/dist/capsule-merge-DI7PNQ2H.js.map +1 -0
  43. package/dist/causal-behavior.js +1 -0
  44. package/dist/causal-behavior.js.map +1 -1
  45. package/dist/causal-chain.js +1 -0
  46. package/dist/causal-consolidation.js +12 -9
  47. package/dist/causal-consolidation.js.map +1 -1
  48. package/dist/causal-retrieval.js +2 -1
  49. package/dist/causal-retrieval.js.map +1 -1
  50. package/dist/causal-trajectory-graph.js +4 -1
  51. package/dist/causal-trajectory-graph.js.map +1 -1
  52. package/dist/causal-trajectory.js +2 -1
  53. package/dist/chunk-2LSZVONP.js +67 -0
  54. package/dist/chunk-2LSZVONP.js.map +1 -0
  55. package/dist/chunk-32KD5IHZ.js +245 -0
  56. package/dist/chunk-32KD5IHZ.js.map +1 -0
  57. package/dist/chunk-3KIS4VGT.js +228 -0
  58. package/dist/chunk-3KIS4VGT.js.map +1 -0
  59. package/dist/chunk-3LCWFNVS.js +350 -0
  60. package/dist/chunk-3LCWFNVS.js.map +1 -0
  61. package/dist/chunk-43EKP2UK.js +26 -0
  62. package/dist/chunk-43EKP2UK.js.map +1 -0
  63. package/dist/chunk-457A4P3L.js +119 -0
  64. package/dist/chunk-457A4P3L.js.map +1 -0
  65. package/dist/{chunk-TMYO7B5P.js → chunk-47WOM4YW.js} +2 -2
  66. package/dist/{chunk-FVA6TGI3.js → chunk-52PDY6GD.js} +42 -2
  67. package/dist/chunk-52PDY6GD.js.map +1 -0
  68. package/dist/{chunk-ULYOGL6R.js → chunk-5HRY2WRF.js} +7 -3
  69. package/dist/chunk-5HRY2WRF.js.map +1 -0
  70. package/dist/{chunk-BOUYNNYD.js → chunk-67YLUWLG.js} +32 -13
  71. package/dist/{chunk-BOUYNNYD.js.map → chunk-67YLUWLG.js.map} +1 -1
  72. package/dist/chunk-6TBWYBJ3.js +236 -0
  73. package/dist/chunk-6TBWYBJ3.js.map +1 -0
  74. package/dist/chunk-74EMIVE4.js +329 -0
  75. package/dist/chunk-74EMIVE4.js.map +1 -0
  76. package/dist/chunk-74WWN7ZW.js +82 -0
  77. package/dist/chunk-74WWN7ZW.js.map +1 -0
  78. package/dist/chunk-A6XUJE5D.js +126 -0
  79. package/dist/chunk-A6XUJE5D.js.map +1 -0
  80. package/dist/{chunk-STGWEHYR.js → chunk-AEMBDV7M.js} +1187 -62
  81. package/dist/chunk-AEMBDV7M.js.map +1 -0
  82. package/dist/{chunk-PVICZTKG.js → chunk-AGZHRWPT.js} +5 -5
  83. package/dist/{chunk-PVICZTKG.js.map → chunk-AGZHRWPT.js.map} +1 -1
  84. package/dist/chunk-AJA46VX5.js +393 -0
  85. package/dist/chunk-AJA46VX5.js.map +1 -0
  86. package/dist/chunk-ASIQZXYO.js +277 -0
  87. package/dist/chunk-ASIQZXYO.js.map +1 -0
  88. package/dist/{chunk-DG6YMRDC.js → chunk-B2TL6GA2.js} +2 -2
  89. package/dist/chunk-BJMBJZ2Y.js +290 -0
  90. package/dist/chunk-BJMBJZ2Y.js.map +1 -0
  91. package/dist/chunk-BT7NVCML.js +79 -0
  92. package/dist/chunk-BT7NVCML.js.map +1 -0
  93. package/dist/chunk-CK5NTM2S.js +454 -0
  94. package/dist/chunk-CK5NTM2S.js.map +1 -0
  95. package/dist/{chunk-AYXIPSZO.js → chunk-CRU27Q4J.js} +2 -2
  96. package/dist/{chunk-UWB5LMWY.js → chunk-CUI2STX6.js} +526 -24
  97. package/dist/chunk-CUI2STX6.js.map +1 -0
  98. package/dist/{chunk-CUPFXL3J.js → chunk-EGEPUGN4.js} +4 -4
  99. package/dist/chunk-EGEPUGN4.js.map +1 -0
  100. package/dist/{chunk-3OGMS3PE.js → chunk-F5VQOQ2E.js} +3 -2
  101. package/dist/chunk-F5VQOQ2E.js.map +1 -0
  102. package/dist/chunk-FP2373TW.js +149 -0
  103. package/dist/chunk-FP2373TW.js.map +1 -0
  104. package/dist/{chunk-RBBWYEFJ.js → chunk-G2WADRQ3.js} +1 -1
  105. package/dist/chunk-G7D6GZ5J.js +48 -0
  106. package/dist/chunk-G7D6GZ5J.js.map +1 -0
  107. package/dist/chunk-H7XKCNR6.js +60 -0
  108. package/dist/chunk-H7XKCNR6.js.map +1 -0
  109. package/dist/{chunk-LOIMBRDE.js → chunk-HIRKCQGF.js} +1994 -412
  110. package/dist/chunk-HIRKCQGF.js.map +1 -0
  111. package/dist/chunk-IXEJRKCZ.js +18 -0
  112. package/dist/chunk-IXEJRKCZ.js.map +1 -0
  113. package/dist/chunk-IYY4MCPG.js +275 -0
  114. package/dist/chunk-IYY4MCPG.js.map +1 -0
  115. package/dist/{chunk-BECYBZLX.js → chunk-JWSENLQI.js} +502 -22
  116. package/dist/chunk-JWSENLQI.js.map +1 -0
  117. package/dist/chunk-KNKUID7G.js +183 -0
  118. package/dist/chunk-KNKUID7G.js.map +1 -0
  119. package/dist/chunk-L2IO2QPY.js +2036 -0
  120. package/dist/chunk-L2IO2QPY.js.map +1 -0
  121. package/dist/{chunk-ZAIM4TUE.js → chunk-LW2NMHDW.js} +46 -1
  122. package/dist/chunk-LW2NMHDW.js.map +1 -0
  123. package/dist/chunk-MDYG7VI7.js +48 -0
  124. package/dist/chunk-MDYG7VI7.js.map +1 -0
  125. package/dist/{chunk-VDX363PS.js → chunk-MUELDH4F.js} +10 -3
  126. package/dist/chunk-MUELDH4F.js.map +1 -0
  127. package/dist/chunk-MXC3AP5I.js +74 -0
  128. package/dist/chunk-MXC3AP5I.js.map +1 -0
  129. package/dist/chunk-NN3TS5BM.js +147 -0
  130. package/dist/chunk-NN3TS5BM.js.map +1 -0
  131. package/dist/{chunk-3YGHKTBF.js → chunk-NZS2BLTP.js} +963 -326
  132. package/dist/chunk-NZS2BLTP.js.map +1 -0
  133. package/dist/chunk-OA3L7BFR.js +183 -0
  134. package/dist/chunk-OA3L7BFR.js.map +1 -0
  135. package/dist/chunk-OZHRDTDX.js +240 -0
  136. package/dist/chunk-OZHRDTDX.js.map +1 -0
  137. package/dist/chunk-PCUKNJAZ.js +165 -0
  138. package/dist/chunk-PCUKNJAZ.js.map +1 -0
  139. package/dist/{chunk-6PFRXT4K.js → chunk-PFV5C235.js} +11 -6
  140. package/dist/chunk-PFV5C235.js.map +1 -0
  141. package/dist/chunk-PZ5AY32C.js +10 -0
  142. package/dist/chunk-PZ5AY32C.js.map +1 -0
  143. package/dist/{chunk-Y7R2XJ5Q.js → chunk-Q7FJ5ZHM.js} +6 -2
  144. package/dist/chunk-Q7FJ5ZHM.js.map +1 -0
  145. package/dist/{chunk-WCLICCGB.js → chunk-RILIVK4O.js} +91 -4
  146. package/dist/chunk-RILIVK4O.js.map +1 -0
  147. package/dist/{chunk-C2EFFULQ.js → chunk-RK2Y4XOM.js} +163 -20
  148. package/dist/chunk-RK2Y4XOM.js.map +1 -0
  149. package/dist/{chunk-TP4FZJIZ.js → chunk-RULE4VG5.js} +5 -1
  150. package/dist/chunk-RULE4VG5.js.map +1 -0
  151. package/dist/{chunk-PVPWZSSI.js → chunk-SMA4IMHV.js} +19 -3
  152. package/dist/chunk-SMA4IMHV.js.map +1 -0
  153. package/dist/{chunk-6YJHX2DL.js → chunk-TIFRGAKO.js} +242 -22
  154. package/dist/chunk-TIFRGAKO.js.map +1 -0
  155. package/dist/chunk-TUFG6VXY.js +875 -0
  156. package/dist/chunk-TUFG6VXY.js.map +1 -0
  157. package/dist/chunk-TYEOAFH3.js +251 -0
  158. package/dist/chunk-TYEOAFH3.js.map +1 -0
  159. package/dist/chunk-UKJAGEXH.js +260 -0
  160. package/dist/chunk-UKJAGEXH.js.map +1 -0
  161. package/dist/{chunk-KVBLZUKV.js → chunk-USFPPRAF.js} +93 -3
  162. package/dist/chunk-USFPPRAF.js.map +1 -0
  163. package/dist/{chunk-NBVAS5MT.js → chunk-V7TEH5I2.js} +6 -6
  164. package/dist/{chunk-GA5P7RST.js → chunk-VTJVUHRK.js} +22 -36
  165. package/dist/chunk-VTJVUHRK.js.map +1 -0
  166. package/dist/{chunk-SPI27QT6.js → chunk-W7WWT4FJ.js} +9 -4
  167. package/dist/chunk-W7WWT4FJ.js.map +1 -0
  168. package/dist/chunk-WIICJPET.js +45 -0
  169. package/dist/chunk-WIICJPET.js.map +1 -0
  170. package/dist/{chunk-VBVG2M5G.js → chunk-WPGJYVUH.js} +6 -2
  171. package/dist/chunk-WPGJYVUH.js.map +1 -0
  172. package/dist/{chunk-4HQS2HPX.js → chunk-WSZIHQBK.js} +29 -9
  173. package/dist/{chunk-4HQS2HPX.js.map → chunk-WSZIHQBK.js.map} +1 -1
  174. package/dist/{chunk-NZLQTHS5.js → chunk-WW3QQF4H.js} +4 -1
  175. package/dist/chunk-WW3QQF4H.js.map +1 -0
  176. package/dist/{chunk-DIXB44VE.js → chunk-X6VBWOVZ.js} +28 -13
  177. package/dist/chunk-X6VBWOVZ.js.map +1 -0
  178. package/dist/{chunk-XXVWLXSG.js → chunk-XQ4EJLUD.js} +64 -92
  179. package/dist/chunk-XQ4EJLUD.js.map +1 -0
  180. package/dist/{chunk-OC5OXUQ4.js → chunk-XRCYKJ3V.js} +780 -17
  181. package/dist/chunk-XRCYKJ3V.js.map +1 -0
  182. package/dist/{chunk-F5VP6YCB.js → chunk-Y4A6M3B6.js} +573 -156
  183. package/dist/chunk-Y4A6M3B6.js.map +1 -0
  184. package/dist/chunk-YNJHCGDT.js +309 -0
  185. package/dist/chunk-YNJHCGDT.js.map +1 -0
  186. package/dist/{chunk-L7IXWRYE.js → chunk-ZIBOQULP.js} +22 -13
  187. package/dist/chunk-ZIBOQULP.js.map +1 -0
  188. package/dist/{chunk-W6SL7OFG.js → chunk-ZTSE2ZJ6.js} +12 -2
  189. package/dist/{chunk-W6SL7OFG.js.map → chunk-ZTSE2ZJ6.js.map} +1 -1
  190. package/dist/chunking.js +1 -0
  191. package/dist/cipher-GVE2GQ5H.js +28 -0
  192. package/dist/cipher-GVE2GQ5H.js.map +1 -0
  193. package/dist/citations.js +1 -0
  194. package/dist/{cli-BkeRaYfk.d.ts → cli-lMql2FCr.d.ts} +26 -7
  195. package/dist/cli.d.ts +11 -6
  196. package/dist/cli.js +69 -34
  197. package/dist/codex-thread-key.js +1 -0
  198. package/dist/commitment-ledger.js +1 -0
  199. package/dist/compression-optimizer.js +1 -0
  200. package/dist/config.d.ts +2 -1
  201. package/dist/config.js +4 -1
  202. package/dist/connectors-cli-DFGtY2DB.d.ts +257 -0
  203. package/dist/connectors-cli.d.ts +2 -0
  204. package/dist/connectors-cli.js +22 -0
  205. package/dist/connectors-cli.js.map +1 -0
  206. package/dist/consolidation-operator.d.ts +65 -5
  207. package/dist/consolidation-operator.js +6 -1
  208. package/dist/consolidation-provenance-check.d.ts +1 -1
  209. package/dist/consolidation-provenance-check.js +3 -2
  210. package/dist/consolidation-undo.d.ts +1 -1
  211. package/dist/consolidation-undo.js +1 -0
  212. package/dist/consolidation-undo.js.map +1 -1
  213. package/dist/{contradiction-review-WIUBAR52.js → contradiction-review-5LTTVDQV.js} +2 -1
  214. package/dist/contradiction-review-5LTTVDQV.js.map +1 -0
  215. package/dist/{contradiction-scan-E3GJTI4F.js → contradiction-scan-3Z6YW7YA.js} +2 -1
  216. package/dist/{contradiction-scan-E3GJTI4F.js.map → contradiction-scan-3Z6YW7YA.js.map} +1 -1
  217. package/dist/cross-namespace-budget.js +1 -0
  218. package/dist/cue-anchors.js +1 -0
  219. package/dist/dashboard-runtime.js +1 -0
  220. package/dist/day-summary.js +1 -0
  221. package/dist/delinearize.js +1 -0
  222. package/dist/direct-answer-wiring.js +1 -0
  223. package/dist/direct-answer.js +1 -0
  224. package/dist/dreams-ledger-LR2NBAZE.js +286 -0
  225. package/dist/dreams-ledger-LR2NBAZE.js.map +1 -0
  226. package/dist/embedding-fallback.js +1 -0
  227. package/dist/engine-O6YWKQM3.js +28 -0
  228. package/dist/engine-O6YWKQM3.js.map +1 -0
  229. package/dist/entity-retrieval.d.ts +1 -1
  230. package/dist/entity-retrieval.js +10 -7
  231. package/dist/entity-schema.js +1 -0
  232. package/dist/evals.js +1 -0
  233. package/dist/evidence-pack.d.ts +16 -0
  234. package/dist/evidence-pack.js +8 -0
  235. package/dist/evidence-pack.js.map +1 -0
  236. package/dist/explicit-capture.d.ts +6 -4
  237. package/dist/explicit-capture.js +1 -0
  238. package/dist/extraction-judge-telemetry.js +1 -0
  239. package/dist/extraction-judge-training.js +1 -0
  240. package/dist/extraction-judge.js +1 -0
  241. package/dist/extraction.js +8 -7
  242. package/dist/fallback-llm.js +3 -2
  243. package/dist/first-start-migration-4MHQEOSD.js +263 -0
  244. package/dist/first-start-migration-4MHQEOSD.js.map +1 -0
  245. package/dist/forget-PLR6J5DN.js +69 -0
  246. package/dist/forget-PLR6J5DN.js.map +1 -0
  247. package/dist/framework-CyHYDcri.d.ts +153 -0
  248. package/dist/fs-utils-IRVUFB6G.js +30 -0
  249. package/dist/fs-utils-IRVUFB6G.js.map +1 -0
  250. package/dist/graph-dashboard-diff.js +1 -0
  251. package/dist/graph-dashboard-key.js +1 -0
  252. package/dist/graph-dashboard-parser.js +1 -0
  253. package/dist/graph-edge-decay-PWB63GRE.js +207 -0
  254. package/dist/graph-edge-decay-PWB63GRE.js.map +1 -0
  255. package/dist/graph-edge-reinforcement.d.ts +81 -0
  256. package/dist/graph-edge-reinforcement.js +24 -0
  257. package/dist/graph-edge-reinforcement.js.map +1 -0
  258. package/dist/graph-events.d.ts +87 -0
  259. package/dist/graph-events.js +14 -0
  260. package/dist/graph-events.js.map +1 -0
  261. package/dist/graph-recall.js +1 -0
  262. package/dist/graph-retrieval.js +1 -0
  263. package/dist/graph-snapshot.d.ts +112 -0
  264. package/dist/graph-snapshot.js +19 -0
  265. package/dist/graph-snapshot.js.map +1 -0
  266. package/dist/graph.d.ts +105 -7
  267. package/dist/graph.js +20 -3
  268. package/dist/harmonic-retrieval.js +1 -0
  269. package/dist/himem.js +1 -0
  270. package/dist/hygiene.js +1 -0
  271. package/dist/identity-continuity.js +1 -0
  272. package/dist/importance.js +1 -0
  273. package/dist/index.d.ts +574 -13
  274. package/dist/index.js +337 -69
  275. package/dist/index.js.map +1 -1
  276. package/dist/intent.js +1 -0
  277. package/dist/json-extract.js +1 -0
  278. package/dist/json-store.js +1 -0
  279. package/dist/kdf-7S6RWKLZ.js +26 -0
  280. package/dist/kdf-7S6RWKLZ.js.map +1 -0
  281. package/dist/legacy-hook-compat.js +1 -0
  282. package/dist/legacy-hook-compat.js.map +1 -1
  283. package/dist/lifecycle.js +1 -0
  284. package/dist/live-connectors-runner.d.ts +48 -0
  285. package/dist/live-connectors-runner.js +17 -0
  286. package/dist/live-connectors-runner.js.map +1 -0
  287. package/dist/local-llm.js +1 -0
  288. package/dist/logger.js +1 -0
  289. package/dist/memory-action-policy.js +1 -0
  290. package/dist/memory-cache.d.ts +2 -1
  291. package/dist/memory-cache.js +4 -1
  292. package/dist/memory-governance-JZHZDOLN.js +37 -0
  293. package/dist/memory-governance-JZHZDOLN.js.map +1 -0
  294. package/dist/memory-lifecycle-ledger-utils.d.ts +2 -1
  295. package/dist/memory-lifecycle-ledger-utils.js +4 -1
  296. package/dist/memory-projection-format.js +1 -0
  297. package/dist/{memory-projection-store-DeSXPh1j.d.ts → memory-projection-store-CY8TU40w.d.ts} +2 -1
  298. package/dist/memory-projection-store.d.ts +1 -1
  299. package/dist/memory-projection-store.js +2 -1
  300. package/dist/memory-worth-bench.js +1 -0
  301. package/dist/memory-worth-bench.js.map +1 -1
  302. package/dist/memory-worth-filter.js +1 -0
  303. package/dist/memory-worth-outcomes.d.ts +1 -1
  304. package/dist/memory-worth-outcomes.js +1 -0
  305. package/dist/memory-worth.js +1 -0
  306. package/dist/metadata-FC3XPDRQ.js +21 -0
  307. package/dist/metadata-FC3XPDRQ.js.map +1 -0
  308. package/dist/migrate-from-identity-anchor-TTEDEJGX.js +8 -0
  309. package/dist/migrate-from-identity-anchor-TTEDEJGX.js.map +1 -0
  310. package/dist/model-registry.js +1 -0
  311. package/dist/models-json.js +1 -0
  312. package/dist/native-knowledge.js +1 -0
  313. package/dist/negative.js +1 -0
  314. package/dist/objective-state-writers.js +1 -0
  315. package/dist/objective-state-writers.js.map +1 -1
  316. package/dist/objective-state.js +1 -0
  317. package/dist/openai-chat-compat.js +1 -0
  318. package/dist/operator-toolkit.d.ts +46 -2
  319. package/dist/operator-toolkit.js +29 -17
  320. package/dist/opik-exporter.js +1 -0
  321. package/dist/opik-exporter.js.map +1 -1
  322. package/dist/{orchestrator-CmJ-NTdJ.d.ts → orchestrator-ChkesB8U.d.ts} +177 -13
  323. package/dist/orchestrator.d.ts +6 -4
  324. package/dist/orchestrator.js +57 -41
  325. package/dist/page-versioning.js +1 -0
  326. package/dist/path-RMTY5Y5A.js +9 -0
  327. package/dist/path-RMTY5Y5A.js.map +1 -0
  328. package/dist/patterns-cli.d.ts +160 -0
  329. package/dist/patterns-cli.js +29 -0
  330. package/dist/patterns-cli.js.map +1 -0
  331. package/dist/peers-6OSQ3NK6.js +44 -0
  332. package/dist/peers-6OSQ3NK6.js.map +1 -0
  333. package/dist/plugin-id.js +1 -0
  334. package/dist/policy-runtime.js +1 -0
  335. package/dist/{port-BADbLZU5.d.ts → port-hqGnoStS.d.ts} +6 -0
  336. package/dist/profiling.js +1 -0
  337. package/dist/purge-6ATBGT77.js +205 -0
  338. package/dist/purge-6ATBGT77.js.map +1 -0
  339. package/dist/qmd-recall-cache.d.ts +1 -1
  340. package/dist/qmd-recall-cache.js +1 -0
  341. package/dist/qmd.d.ts +2 -1
  342. package/dist/qmd.js +4 -3
  343. package/dist/reasoning-trace-recall.js +1 -0
  344. package/dist/reasoning-trace-types.js +1 -0
  345. package/dist/recall-audit-anomaly.js +1 -0
  346. package/dist/recall-audit.js +1 -0
  347. package/dist/recall-disclosure-escalation.d.ts +84 -0
  348. package/dist/recall-disclosure-escalation.js +14 -0
  349. package/dist/recall-disclosure-escalation.js.map +1 -0
  350. package/dist/recall-explain-renderer.js +4 -1
  351. package/dist/recall-mmr.js +1 -0
  352. package/dist/recall-qos.js +1 -0
  353. package/dist/recall-query-policy.js +1 -0
  354. package/dist/recall-state.d.ts +7 -0
  355. package/dist/recall-state.js +2 -1
  356. package/dist/recall-tag-filter.d.ts +56 -0
  357. package/dist/recall-tag-filter.js +14 -0
  358. package/dist/recall-tag-filter.js.map +1 -0
  359. package/dist/recall-tokenization.js +1 -0
  360. package/dist/recall-xray-cli.d.ts +9 -2
  361. package/dist/recall-xray-cli.js +9 -4
  362. package/dist/recall-xray-renderer.js +4 -1
  363. package/dist/recall-xray.d.ts +116 -2
  364. package/dist/recall-xray.js +9 -3
  365. package/dist/reconstruct.js +1 -0
  366. package/dist/release-changelog.js +2 -0
  367. package/dist/release-changelog.js.map +1 -1
  368. package/dist/relevance.js +1 -0
  369. package/dist/rerank.js +1 -0
  370. package/dist/{resolution-QBTDHTG7.js → resolution-YGIBORXI.js} +2 -1
  371. package/dist/{resolution-QBTDHTG7.js.map → resolution-YGIBORXI.js.map} +1 -1
  372. package/dist/resolve-auth-token.d.ts +51 -0
  373. package/dist/resolve-auth-token.js +12 -0
  374. package/dist/resolve-auth-token.js.map +1 -0
  375. package/dist/resolve-provider-secret.d.ts +9 -1
  376. package/dist/resolve-provider-secret.js +4 -1
  377. package/dist/resume-bundles.js +4 -3
  378. package/dist/retrieval-agents.d.ts +1 -1
  379. package/dist/retrieval-agents.js +1 -0
  380. package/dist/retrieval-tiers.js +1 -0
  381. package/dist/retrieval.js +1 -0
  382. package/dist/sanitize.js +1 -0
  383. package/dist/schemas.d.ts +15 -2
  384. package/dist/schemas.js +2 -1
  385. package/dist/sdk-compat.js +1 -0
  386. package/dist/sdk-compat.js.map +1 -1
  387. package/dist/secure-store-4R2GSO7S.js +156 -0
  388. package/dist/secure-store-4R2GSO7S.js.map +1 -0
  389. package/dist/semantic-chunking.js +1 -0
  390. package/dist/{semantic-consolidation-CxJU6MJk.d.ts → semantic-consolidation-ByBXb-sf.d.ts} +3 -3
  391. package/dist/semantic-consolidation.d.ts +2 -2
  392. package/dist/semantic-consolidation.js +12 -7
  393. package/dist/semantic-rule-promotion.d.ts +1 -1
  394. package/dist/semantic-rule-promotion.js +10 -7
  395. package/dist/semantic-rule-verifier.d.ts +1 -1
  396. package/dist/semantic-rule-verifier.js +10 -7
  397. package/dist/session-integrity.js +1 -0
  398. package/dist/session-observer-bands.js +1 -0
  399. package/dist/session-observer-state.js +1 -0
  400. package/dist/session-toggles.js +2 -0
  401. package/dist/session-toggles.js.map +1 -1
  402. package/dist/signal.js +1 -0
  403. package/dist/skills-registry.js +2 -0
  404. package/dist/skills-registry.js.map +1 -1
  405. package/dist/source-attribution.js +1 -0
  406. package/dist/state-NCHQ4TRG.js +8 -0
  407. package/dist/state-NCHQ4TRG.js.map +1 -0
  408. package/dist/state-store-3EH7HYIN.js +16 -0
  409. package/dist/state-store-3EH7HYIN.js.map +1 -0
  410. package/dist/storage.d.ts +76 -2
  411. package/dist/storage.js +9 -6
  412. package/dist/store-contract.js +1 -0
  413. package/dist/summarizer.js +5 -4
  414. package/dist/summary-snapshot.js +1 -0
  415. package/dist/temporal-index.js +1 -0
  416. package/dist/temporal-supersession.d.ts +1 -1
  417. package/dist/temporal-supersession.js +2 -1
  418. package/dist/temporal-validity.d.ts +52 -0
  419. package/dist/temporal-validity.js +14 -0
  420. package/dist/temporal-validity.js.map +1 -0
  421. package/dist/threading.js +1 -0
  422. package/dist/tier-migration.d.ts +2 -2
  423. package/dist/tier-migration.js +1 -0
  424. package/dist/tier-routing.js +1 -0
  425. package/dist/tier-stats-62ZVDFKS.js +152 -0
  426. package/dist/tier-stats-62ZVDFKS.js.map +1 -0
  427. package/dist/tmt.js +1 -0
  428. package/dist/tokens.js +1 -0
  429. package/dist/topics.js +1 -0
  430. package/dist/trace-C5ETWBEF.js +290 -0
  431. package/dist/trace-C5ETWBEF.js.map +1 -0
  432. package/dist/transcript.js +1 -0
  433. package/dist/trust-zones.js +1 -0
  434. package/dist/tui-RI7P6PBS.js +13 -0
  435. package/dist/tui-RI7P6PBS.js.map +1 -0
  436. package/dist/types-V3FJ26TF.js +30 -0
  437. package/dist/types-V3FJ26TF.js.map +1 -0
  438. package/dist/types.d.ts +634 -9
  439. package/dist/types.js +10 -3
  440. package/dist/utility-learner.js +1 -0
  441. package/dist/utility-runtime.js +1 -0
  442. package/dist/utility-telemetry.js +1 -0
  443. package/dist/verified-recall.js +10 -7
  444. package/dist/version-utils.js +1 -0
  445. package/dist/whitespace.js +1 -0
  446. package/dist/work-product-ledger.js +1 -0
  447. package/package.json +7 -3
  448. package/scripts/ensure-better-sqlite3.mjs +124 -0
  449. package/dist/access-service-Br8ZydTK.d.ts +0 -827
  450. package/dist/chunk-3OGMS3PE.js.map +0 -1
  451. package/dist/chunk-3YGHKTBF.js.map +0 -1
  452. package/dist/chunk-6PFRXT4K.js.map +0 -1
  453. package/dist/chunk-6YJHX2DL.js.map +0 -1
  454. package/dist/chunk-BECYBZLX.js.map +0 -1
  455. package/dist/chunk-C2EFFULQ.js.map +0 -1
  456. package/dist/chunk-CUPFXL3J.js.map +0 -1
  457. package/dist/chunk-DIXB44VE.js.map +0 -1
  458. package/dist/chunk-F5VP6YCB.js.map +0 -1
  459. package/dist/chunk-FVA6TGI3.js.map +0 -1
  460. package/dist/chunk-GA5P7RST.js.map +0 -1
  461. package/dist/chunk-KVBLZUKV.js.map +0 -1
  462. package/dist/chunk-L7IXWRYE.js.map +0 -1
  463. package/dist/chunk-LOIMBRDE.js.map +0 -1
  464. package/dist/chunk-LTCGGW2D.js +0 -14
  465. package/dist/chunk-LTCGGW2D.js.map +0 -1
  466. package/dist/chunk-NZLQTHS5.js.map +0 -1
  467. package/dist/chunk-OC5OXUQ4.js.map +0 -1
  468. package/dist/chunk-PVPWZSSI.js.map +0 -1
  469. package/dist/chunk-SPI27QT6.js.map +0 -1
  470. package/dist/chunk-STGWEHYR.js.map +0 -1
  471. package/dist/chunk-TP4FZJIZ.js.map +0 -1
  472. package/dist/chunk-ULYOGL6R.js.map +0 -1
  473. package/dist/chunk-UWB5LMWY.js.map +0 -1
  474. package/dist/chunk-VBVG2M5G.js.map +0 -1
  475. package/dist/chunk-VDX363PS.js.map +0 -1
  476. package/dist/chunk-WCLICCGB.js.map +0 -1
  477. package/dist/chunk-X6GF3FX2.js +0 -26
  478. package/dist/chunk-X6GF3FX2.js.map +0 -1
  479. package/dist/chunk-XXVWLXSG.js.map +0 -1
  480. package/dist/chunk-Y7R2XJ5Q.js.map +0 -1
  481. package/dist/chunk-ZAIM4TUE.js.map +0 -1
  482. package/dist/engine-72LSIWQP.js +0 -23
  483. /package/dist/{contradiction-review-WIUBAR52.js.map → capsule-cli.js.map} +0 -0
  484. /package/dist/{engine-72LSIWQP.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
  485. /package/dist/{chunk-TMYO7B5P.js.map → chunk-47WOM4YW.js.map} +0 -0
  486. /package/dist/{chunk-DG6YMRDC.js.map → chunk-B2TL6GA2.js.map} +0 -0
  487. /package/dist/{chunk-AYXIPSZO.js.map → chunk-CRU27Q4J.js.map} +0 -0
  488. /package/dist/{chunk-RBBWYEFJ.js.map → chunk-G2WADRQ3.js.map} +0 -0
  489. /package/dist/{chunk-NBVAS5MT.js.map → chunk-V7TEH5I2.js.map} +0 -0
@@ -0,0 +1,245 @@
1
+ import {
2
+ gatherConsoleState
3
+ } from "./chunk-TYEOAFH3.js";
4
+
5
+ // src/console/tui.ts
6
+ var ANSI_CLEAR_HOME = "\x1B[2J\x1B[H";
7
+ var ANSI_HIDE_CURSOR = "\x1B[?25l";
8
+ var ANSI_SHOW_CURSOR = "\x1B[?25h";
9
+ var FRAME_INNER_WIDTH = 70;
10
+ var DEFAULT_REFRESH_INTERVAL_MS = 2e3;
11
+ function runConsoleTui(orchestrator, options = {}) {
12
+ const refreshIntervalMs = Math.max(
13
+ 50,
14
+ options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS
15
+ );
16
+ const output = options.output ?? process.stdout;
17
+ const now = options.now ?? (() => Date.now());
18
+ const installSigintHandler = options.installSigintHandler ?? true;
19
+ let stopped = false;
20
+ let inFlight = false;
21
+ let inFlightTickPromise = null;
22
+ let traceWritePending = false;
23
+ let traceFramesDropped = 0;
24
+ let resolveDone;
25
+ const done = new Promise((resolve) => {
26
+ resolveDone = resolve;
27
+ });
28
+ safeWrite(output, ANSI_HIDE_CURSOR);
29
+ const tick = async () => {
30
+ if (stopped || inFlight) return;
31
+ inFlight = true;
32
+ try {
33
+ let snapshot = null;
34
+ let renderError = null;
35
+ try {
36
+ snapshot = await gatherConsoleState(orchestrator);
37
+ } catch (err) {
38
+ renderError = describeError(err);
39
+ }
40
+ if (stopped) return;
41
+ let frame;
42
+ try {
43
+ frame = renderFrame({ snapshot, renderError, now });
44
+ } catch (err) {
45
+ frame = `remnic console: render failed: ${describeError(err)}
46
+ `;
47
+ }
48
+ safeWrite(output, ANSI_CLEAR_HOME);
49
+ safeWrite(output, frame);
50
+ if (snapshot && options.traceRecorder && !traceWritePending) {
51
+ traceWritePending = true;
52
+ void options.traceRecorder.append(snapshot).catch(() => {
53
+ }).finally(() => {
54
+ traceWritePending = false;
55
+ });
56
+ } else if (snapshot && options.traceRecorder) {
57
+ traceFramesDropped += 1;
58
+ }
59
+ } finally {
60
+ inFlight = false;
61
+ }
62
+ };
63
+ const launchTick = () => {
64
+ const p = runTickSafely(tick);
65
+ inFlightTickPromise = p;
66
+ void p.then(() => {
67
+ if (inFlightTickPromise === p) {
68
+ inFlightTickPromise = null;
69
+ }
70
+ });
71
+ };
72
+ launchTick();
73
+ const handle = setInterval(launchTick, refreshIntervalMs);
74
+ const sigintHandler = () => {
75
+ stop();
76
+ };
77
+ if (installSigintHandler) {
78
+ process.on("SIGINT", sigintHandler);
79
+ }
80
+ const stop = () => {
81
+ if (stopped) return;
82
+ stopped = true;
83
+ clearInterval(handle);
84
+ if (installSigintHandler) {
85
+ try {
86
+ process.removeListener("SIGINT", sigintHandler);
87
+ } catch {
88
+ }
89
+ }
90
+ safeWrite(output, ANSI_SHOW_CURSOR);
91
+ void (async () => {
92
+ try {
93
+ const pendingTick = inFlightTickPromise;
94
+ if (pendingTick) {
95
+ await pendingTick;
96
+ }
97
+ } finally {
98
+ resolveDone();
99
+ }
100
+ })();
101
+ };
102
+ return {
103
+ stop,
104
+ done,
105
+ getDroppedTraceFrames: () => traceFramesDropped
106
+ };
107
+ }
108
+ function renderFrame(input) {
109
+ const ts = new Date(input.now()).toISOString();
110
+ const lines = [];
111
+ lines.push(renderHeader(ts));
112
+ for (const panel of renderPanels(input)) {
113
+ lines.push(panel);
114
+ }
115
+ lines.push(renderFooter());
116
+ return lines.join("\n") + "\n";
117
+ }
118
+ function renderHeader(ts) {
119
+ const title = " remnic console ";
120
+ const trailing = ` ${ts} `;
121
+ const fillerLen = Math.max(
122
+ 1,
123
+ FRAME_INNER_WIDTH - title.length - trailing.length
124
+ );
125
+ const filler = "\u2550".repeat(fillerLen);
126
+ return `\u2554${title}${filler}${trailing}\u2557`;
127
+ }
128
+ function renderFooter() {
129
+ return `\u255A${"\u2550".repeat(FRAME_INNER_WIDTH)}\u255D`;
130
+ }
131
+ function renderPanels(input) {
132
+ const lines = [];
133
+ const snap = input.snapshot;
134
+ if (input.renderError !== null) {
135
+ lines.push(panelLine("Error", `refresh failed: ${input.renderError}`));
136
+ lines.push(panelLine("Buffer", "(unavailable)"));
137
+ lines.push(panelLine("Extraction", "(unavailable)"));
138
+ lines.push(panelLine("Dedup", "(unavailable)"));
139
+ lines.push(panelLine("Maintenance", "(unavailable)"));
140
+ lines.push(panelLine("QMD", "(unavailable)"));
141
+ return lines;
142
+ }
143
+ if (snap) {
144
+ lines.push(
145
+ panelLine(
146
+ "Buffer",
147
+ `turns=${snap.bufferState.turnsCount} bytes=${snap.bufferState.byteCount}`
148
+ )
149
+ );
150
+ const verdicts = snap.extractionQueue.recentVerdicts;
151
+ const accepts = verdicts.filter((v) => v.kind === "accept").length;
152
+ const rejects = verdicts.filter((v) => v.kind === "reject").length;
153
+ lines.push(
154
+ panelLine(
155
+ "Extraction",
156
+ `queue=${snap.extractionQueue.depth} recent verdicts: accept(${accepts})/reject(${rejects})`
157
+ )
158
+ );
159
+ const dedupSummary = formatDedupSummary(snap.dedupRecent, input.now);
160
+ lines.push(panelLine("Dedup", dedupSummary));
161
+ const maintSummary = formatMaintenanceTail(snap.maintenanceLedgerTail);
162
+ lines.push(panelLine("Maintenance", maintSummary));
163
+ lines.push(panelLine("QMD", formatQmdSummary(snap)));
164
+ }
165
+ if (snap && snap.errors.length > 0) {
166
+ const head = snap.errors[0];
167
+ lines.push(panelLine("Errors", head ?? ""));
168
+ }
169
+ return lines;
170
+ }
171
+ function formatDedupSummary(decisions, now) {
172
+ if (decisions.length === 0) return "no recent decisions";
173
+ const last = decisions[decisions.length - 1];
174
+ if (!last) return "no recent decisions";
175
+ const ageMs = ageMsFromIso(last.ts, now);
176
+ const ageStr = ageMs === null ? "T-?" : `T-${Math.round(ageMs / 1e3)}s`;
177
+ const fp = last.fingerprint ? `hash=${last.fingerprint}` : "hash=?";
178
+ return `recent: ${fp} decision=${last.decision} (${ageStr})`;
179
+ }
180
+ function formatMaintenanceTail(events) {
181
+ if (events.length === 0) return "no events";
182
+ const last = events[events.length - 1];
183
+ if (!last) return "no events";
184
+ return `n=${events.length} last: ${last.category} ${truncate(last.summary, 40)}`;
185
+ }
186
+ function formatQmdSummary(snap) {
187
+ const probe = snap.qmdProbe.available ? "ok" : "down";
188
+ const uptimeH = snap.daemon.uptimeMs / 36e5;
189
+ const uptimeStr = uptimeH < 1 ? `${Math.round(snap.daemon.uptimeMs / 1e3)}s` : `${uptimeH.toFixed(1)}h`;
190
+ const mode = snap.qmdProbe.daemonMode ? "daemon" : "cli";
191
+ return `probe=${probe} mode=${mode} uptime=${uptimeStr}`;
192
+ }
193
+ function panelLine(label, value) {
194
+ const LABEL_WIDTH = 13;
195
+ const labelCol = padRight(label, LABEL_WIDTH);
196
+ const remaining = FRAME_INNER_WIDTH - LABEL_WIDTH - 2;
197
+ const valueCol = padRight(truncate(value, remaining), remaining);
198
+ return `\u2551 ${labelCol}${valueCol} \u2551`;
199
+ }
200
+ function padRight(s, width) {
201
+ if (s.length >= width) return s;
202
+ return s + " ".repeat(width - s.length);
203
+ }
204
+ function truncate(s, max) {
205
+ if (max <= 0) return "";
206
+ if (s.length <= max) return s;
207
+ if (max <= 1) return s.slice(0, max);
208
+ return s.slice(0, max - 1) + "\u2026";
209
+ }
210
+ function ageMsFromIso(iso, now) {
211
+ const ms = Date.parse(iso);
212
+ if (!Number.isFinite(ms)) return null;
213
+ const delta = now() - ms;
214
+ return delta < 0 ? 0 : delta;
215
+ }
216
+ async function runTickSafely(tick) {
217
+ try {
218
+ await tick();
219
+ } catch {
220
+ }
221
+ }
222
+ function safeWrite(output, chunk) {
223
+ try {
224
+ output.write(chunk);
225
+ } catch {
226
+ }
227
+ }
228
+ function describeError(err) {
229
+ if (err instanceof Error) return err.message;
230
+ try {
231
+ return String(err);
232
+ } catch {
233
+ return "unknown error";
234
+ }
235
+ }
236
+ function stripAnsi(s) {
237
+ return s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
238
+ }
239
+
240
+ export {
241
+ runConsoleTui,
242
+ renderFrame,
243
+ stripAnsi
244
+ };
245
+ //# sourceMappingURL=chunk-32KD5IHZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/console/tui.ts"],"sourcesContent":["/**\n * Operator console TUI for issue #688 (PR 2/3).\n *\n * Renders a five-panel one-screen layout that periodically polls\n * `gatherConsoleState` and repaints the terminal. Deliberately uses\n * the minimal-deps \"clear + repaint\" approach (no curses / blessed /\n * ink) — we just emit ANSI control sequences directly to the output\n * stream. This keeps the surface portable and dependency-free.\n *\n * PR 1/3 (#721) shipped `gatherConsoleState` plus the CLI's\n * `--state-only` flag (one-shot JSON snapshot). PR 2/3 wires the\n * interactive surface. Trace replay (`--trace <session-id>`) is\n * deferred to PR 3/3.\n *\n * Design contract:\n * - Read-only: never mutates orchestrator state.\n * - Resilient: a thrown error in one refresh cycle must NOT crash\n * the loop. Errors are surfaced inside the rendered frame.\n * - Cleanly stoppable: `runConsoleTui` returns a `stop()` that\n * clears the interval, restores the cursor, and resolves the\n * pending exit promise. SIGINT triggers the same cleanup.\n * - No external deps: only ANSI control sequences.\n */\n\nimport type { Writable } from \"node:stream\";\n\nimport {\n gatherConsoleState,\n type ConsoleStateOrchestratorLike,\n type ConsoleStateSnapshot,\n} from \"./state.js\";\n\n/** ANSI: clear screen + move cursor to home (top-left). */\nconst ANSI_CLEAR_HOME = \"\\x1b[2J\\x1b[H\";\n/** ANSI: hide / show the cursor (we hide during the loop, restore on exit). */\nconst ANSI_HIDE_CURSOR = \"\\x1b[?25l\";\nconst ANSI_SHOW_CURSOR = \"\\x1b[?25h\";\n\n/** Total inner width of the rendered frame (between the box borders). */\nconst FRAME_INNER_WIDTH = 70;\n\n/** Default refresh interval in milliseconds. Chosen to match the spec. */\nconst DEFAULT_REFRESH_INTERVAL_MS = 2000;\n\nexport interface RunConsoleTuiOptions {\n /** Polling interval in milliseconds. Defaults to 2000ms. */\n refreshIntervalMs?: number;\n /** Output stream. Defaults to `process.stdout`. */\n output?: Writable;\n /**\n * Optional clock injection — primarily for tests so the rendered\n * timestamp is deterministic. Defaults to `Date.now`.\n */\n now?: () => number;\n /**\n * If true, install a SIGINT handler that calls `stop()` and resolves\n * the returned promise. Defaults to true. Tests typically pass false.\n */\n installSigintHandler?: boolean;\n /**\n * Optional trace recorder. When provided, every successfully\n * gathered snapshot is appended to the recorder for later replay\n * via `replayTrace`. Failed appends are surfaced through the\n * recorder's `getLastError()` and never crash the loop (issue\n * #688 PR 3/3).\n */\n traceRecorder?: {\n append: (snapshot: ConsoleStateSnapshot) => Promise<void>;\n };\n}\n\nexport interface RunConsoleTuiHandle {\n /**\n * Stop the refresh loop and restore the cursor. Returns immediately;\n * callers should `await handle.done` to wait for any in-flight tick\n * (and its trace append) to finish. Codex P2 (#732 round 4): without\n * the in-flight await on the `done` path, a Ctrl-C that fires while\n * a tick is past its `stopped` check could still call\n * `traceRecorder.append` AFTER the CLI begins closing the recorder.\n */\n stop: () => void;\n /**\n * Resolves once `stop()` has been invoked, the interval is cleared,\n * AND any in-flight tick (including its fire-and-forget trace\n * append) has resolved. Callers that close a trace recorder after\n * the TUI exits MUST await this promise before initiating close,\n * otherwise the close path can race a still-running `append` call\n * and surface phantom \"post-close\" appends to the recorder. Codex\n * P2 (#732 round 4).\n */\n done: Promise<void>;\n /**\n * Number of trace frames that were dropped due to backpressure (i.e.\n * the previous `traceRecorder.append()` had not yet resolved when\n * the tick fired). Non-zero values indicate the trace recorder is\n * falling behind — typically caused by a slow or network-backed\n * filesystem. Codex P2 (PR #732 round 5): exposed so operators can\n * detect recording lag via `--state-only` snapshots or future\n * diagnostic surfaces.\n */\n getDroppedTraceFrames: () => number;\n}\n\n/**\n * Start the operator console TUI. Returns immediately with a handle\n * exposing `stop()` and a `done` promise that resolves once the loop\n * has been torn down. The caller can `await handle.done` to block\n * until the user (or a SIGINT) exits.\n */\nexport function runConsoleTui(\n orchestrator: ConsoleStateOrchestratorLike,\n options: RunConsoleTuiOptions = {},\n): RunConsoleTuiHandle {\n const refreshIntervalMs = Math.max(\n 50,\n options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS,\n );\n const output: Writable = options.output ?? process.stdout;\n const now = options.now ?? (() => Date.now());\n const installSigintHandler = options.installSigintHandler ?? true;\n\n let stopped = false;\n let inFlight = false;\n // Codex P2 (PR #732 round 4): track the in-flight tick promise so\n // `stop()` can await it before resolving `done`. Without this, a\n // Ctrl-C that fires while a tick is past its `if (stopped) return`\n // check can still execute `traceRecorder.append(snapshot)` AFTER\n // `done` resolves and the CLI shutdown path begins closing the\n // recorder. By awaiting the tick body, we ensure\n // `recorder.append()` has at least been called (synchronously\n // enqueueing the line onto the recorder's internal writeChain)\n // before `done` resolves; the recorder's `close()` then drains\n // that writeChain. Without the await, `close()` can begin draining\n // while the tick is still about to call `append()`, producing a\n // post-close append.\n //\n // We deliberately do NOT also await the resulting fire-and-forget\n // trace promise. The append is fire-and-forget on purpose\n // (backpressure: see comment further below); a wedged disk that\n // stalls the append must not also stall shutdown. The recorder's\n // own `close()` path (combined with `flushWithTimeout` in the CLI)\n // is the right place to bound the disk-drain wait.\n let inFlightTickPromise: Promise<void> | null = null;\n // Codex P2 (PR #732): backpressure for trace recording. If the\n // previous fire-and-forget `append()` has not resolved yet, we drop\n // the current frame instead of enqueuing it onto the recorder's\n // internal writeChain. Without this guard, a wedged filesystem\n // would let memory grow unboundedly — every tick adds a JSON line\n // to a serialized chain that never drains. Dropping is preferable\n // to OOM; the operator can inspect `getLastError()` to learn that\n // tracing fell behind.\n let traceWritePending = false;\n let traceFramesDropped = 0;\n let resolveDone!: () => void;\n const done = new Promise<void>((resolve) => {\n resolveDone = resolve;\n });\n\n // Hide the cursor while the loop runs so the repaint doesn't flicker.\n safeWrite(output, ANSI_HIDE_CURSOR);\n\n const tick = async () => {\n if (stopped || inFlight) return;\n inFlight = true;\n // Cursor Low: wrap the entire body in try/finally so a thrown\n // `renderFrame` (or any other unexpected sync failure after the\n // gatherConsoleState try/catch) still releases the `inFlight`\n // latch. Without this guard, a single render-time exception\n // permanently freezes the loop because every subsequent tick\n // hits `if (inFlight) return`. Honors the file's design contract\n // (\"a thrown error in one refresh cycle must NOT crash the loop\").\n try {\n let snapshot: ConsoleStateSnapshot | null = null;\n let renderError: string | null = null;\n try {\n snapshot = await gatherConsoleState(orchestrator);\n } catch (err) {\n renderError = describeError(err);\n }\n if (stopped) return;\n let frame: string;\n try {\n frame = renderFrame({ snapshot, renderError, now });\n } catch (err) {\n // If the renderer itself throws (e.g. an invalid clock\n // injection produced `NaN`), fall back to a minimal error\n // frame instead of letting the exception escape and reject\n // the void-floated promise (which would also be unhandled).\n frame = `remnic console: render failed: ${describeError(err)}\\n`;\n }\n safeWrite(output, ANSI_CLEAR_HOME);\n safeWrite(output, frame);\n // Codex P2: do NOT await trace writes inside the render tick.\n // Awaiting holds `inFlight = true` for the duration of the disk\n // write; on a slow / network-backed filesystem that stretches\n // the visual refresh interval and skips ticks. Fire-and-forget\n // preserves the paint cadence; errors are captured via\n // `getLastError()`.\n //\n // Codex P2 (PR #732 follow-up): apply backpressure. If the\n // previous append is still pending (writeChain not yet\n // drained), drop this frame instead of queuing another line.\n // Without this gate, a wedged FS lets the chain grow\n // unboundedly — one JSON line per tick — until the process\n // OOMs. Dropping is preferable; the operator can inspect\n // `getLastError()` to see tracing fell behind.\n if (snapshot && options.traceRecorder && !traceWritePending) {\n traceWritePending = true;\n void options.traceRecorder\n .append(snapshot)\n .catch(() => {\n // already recorded in lastError; defense-in-depth.\n })\n .finally(() => {\n traceWritePending = false;\n });\n } else if (snapshot && options.traceRecorder) {\n traceFramesDropped += 1;\n }\n } finally {\n inFlight = false;\n }\n };\n\n // Kick off an immediate first paint, then schedule the interval.\n // Codex P1: do NOT call `handle.unref()`. The CLI path keeps the\n // process alive by `await`-ing `handle.done`, but a pending promise\n // alone is not enough to keep Node.js running — the interval timer\n // is the only ref'd handle that holds the event loop open between\n // ticks. Unref'ing it caused `remnic console` to render once and\n // exit immediately. Tests that don't want the process held open\n // can call `handle.stop()` directly when they're done.\n // Codex P2 (PR #732 round 4): wrap each tick launch so we hold a\n // reference to the in-flight tick promise. `stop()` awaits this\n // promise before resolving `done`, ensuring no late\n // `traceRecorder.append` call races the CLI shutdown path that\n // closes the recorder once `done` resolves.\n const launchTick = (): void => {\n const p = runTickSafely(tick);\n inFlightTickPromise = p;\n void p.then(() => {\n if (inFlightTickPromise === p) {\n inFlightTickPromise = null;\n }\n });\n };\n launchTick();\n const handle = setInterval(launchTick, refreshIntervalMs);\n\n const sigintHandler = () => {\n stop();\n };\n if (installSigintHandler) {\n process.on(\"SIGINT\", sigintHandler);\n }\n\n const stop = () => {\n if (stopped) return;\n stopped = true;\n clearInterval(handle);\n if (installSigintHandler) {\n try {\n process.removeListener(\"SIGINT\", sigintHandler);\n } catch {\n // ignore\n }\n }\n safeWrite(output, ANSI_SHOW_CURSOR);\n // Codex P2 (PR #732 round 4): wait for any in-flight tick before\n // resolving `done`. Without this, a Ctrl-C that fires while a\n // tick is past its `if (stopped) return` check can still execute\n // `traceRecorder.append(snapshot)` AFTER `done` resolves and the\n // CLI shutdown path begins closing the recorder, producing a\n // phantom post-close append. By awaiting the tick body, we\n // ensure `recorder.append()` has at least been *called*\n // (synchronously enqueueing the line onto the recorder's\n // writeChain) before `done` resolves; the recorder's `close()`\n // then drains that writeChain.\n //\n // We do NOT also await the fire-and-forget trace append promise\n // itself. A wedged disk that stalls the append must not also\n // stall shutdown — the recorder's `close()` path (bounded by\n // `flushWithTimeout` in the CLI) is the right place to bound\n // the disk-drain wait.\n void (async () => {\n try {\n const pendingTick = inFlightTickPromise;\n if (pendingTick) {\n await pendingTick;\n }\n } finally {\n resolveDone();\n }\n })();\n };\n\n return {\n stop,\n done,\n getDroppedTraceFrames: () => traceFramesDropped,\n };\n}\n\ninterface RenderFrameInput {\n snapshot: ConsoleStateSnapshot | null;\n renderError: string | null;\n now: () => number;\n}\n\n/**\n * Build the full rendered frame as a single string. Pure — exposed\n * for testing.\n */\nexport function renderFrame(input: RenderFrameInput): string {\n const ts = new Date(input.now()).toISOString();\n const lines: string[] = [];\n lines.push(renderHeader(ts));\n for (const panel of renderPanels(input)) {\n lines.push(panel);\n }\n lines.push(renderFooter());\n return lines.join(\"\\n\") + \"\\n\";\n}\n\nfunction renderHeader(ts: string): string {\n // ╔═ remnic console ════════════════════════════════ <ts> ═╗\n const title = \" remnic console \";\n const trailing = ` ${ts} `;\n // 2 corner chars + title + ts + filler. Filler at minimum 1 char.\n const fillerLen = Math.max(\n 1,\n FRAME_INNER_WIDTH - title.length - trailing.length,\n );\n const filler = \"═\".repeat(fillerLen);\n return `╔${title}${filler}${trailing}╗`;\n}\n\nfunction renderFooter(): string {\n return `╚${\"═\".repeat(FRAME_INNER_WIDTH)}╝`;\n}\n\nfunction renderPanels(input: RenderFrameInput): string[] {\n const lines: string[] = [];\n const snap = input.snapshot;\n\n if (input.renderError !== null) {\n lines.push(panelLine(\"Error\", `refresh failed: ${input.renderError}`));\n // Even on error we still print the section headers so the layout\n // stays stable for the operator and tests can locate them.\n lines.push(panelLine(\"Buffer\", \"(unavailable)\"));\n lines.push(panelLine(\"Extraction\", \"(unavailable)\"));\n lines.push(panelLine(\"Dedup\", \"(unavailable)\"));\n lines.push(panelLine(\"Maintenance\", \"(unavailable)\"));\n lines.push(panelLine(\"QMD\", \"(unavailable)\"));\n return lines;\n }\n\n // Buffer panel.\n if (snap) {\n lines.push(\n panelLine(\n \"Buffer\",\n `turns=${snap.bufferState.turnsCount} bytes=${snap.bufferState.byteCount}`,\n ),\n );\n\n // Extraction panel.\n const verdicts = snap.extractionQueue.recentVerdicts;\n const accepts = verdicts.filter((v) => v.kind === \"accept\").length;\n const rejects = verdicts.filter((v) => v.kind === \"reject\").length;\n lines.push(\n panelLine(\n \"Extraction\",\n `queue=${snap.extractionQueue.depth} recent verdicts: accept(${accepts})/reject(${rejects})`,\n ),\n );\n\n // Dedup panel.\n const dedupSummary = formatDedupSummary(snap.dedupRecent, input.now);\n lines.push(panelLine(\"Dedup\", dedupSummary));\n\n // Maintenance panel.\n const maintSummary = formatMaintenanceTail(snap.maintenanceLedgerTail);\n lines.push(panelLine(\"Maintenance\", maintSummary));\n\n // QMD panel.\n lines.push(panelLine(\"QMD\", formatQmdSummary(snap)));\n }\n\n // Surface aggregator-level read errors (e.g. ledger I/O failures)\n // without crashing the loop. These are not fatal — the snapshot is\n // still partially usable.\n if (snap && snap.errors.length > 0) {\n const head = snap.errors[0];\n lines.push(panelLine(\"Errors\", head ?? \"\"));\n }\n\n return lines;\n}\n\nfunction formatDedupSummary(\n decisions: ConsoleStateSnapshot[\"dedupRecent\"],\n now: () => number,\n): string {\n if (decisions.length === 0) return \"no recent decisions\";\n const last = decisions[decisions.length - 1];\n if (!last) return \"no recent decisions\";\n const ageMs = ageMsFromIso(last.ts, now);\n const ageStr = ageMs === null ? \"T-?\" : `T-${Math.round(ageMs / 1000)}s`;\n const fp = last.fingerprint ? `hash=${last.fingerprint}` : \"hash=?\";\n return `recent: ${fp} decision=${last.decision} (${ageStr})`;\n}\n\nfunction formatMaintenanceTail(\n events: ConsoleStateSnapshot[\"maintenanceLedgerTail\"],\n): string {\n if (events.length === 0) return \"no events\";\n const last = events[events.length - 1];\n if (!last) return \"no events\";\n // Show count + most-recent event line.\n return `n=${events.length} last: ${last.category} ${truncate(last.summary, 40)}`;\n}\n\nfunction formatQmdSummary(snap: ConsoleStateSnapshot): string {\n const probe = snap.qmdProbe.available ? \"ok\" : \"down\";\n const uptimeH = snap.daemon.uptimeMs / 3_600_000;\n const uptimeStr = uptimeH < 1\n ? `${Math.round(snap.daemon.uptimeMs / 1000)}s`\n : `${uptimeH.toFixed(1)}h`;\n const mode = snap.qmdProbe.daemonMode ? \"daemon\" : \"cli\";\n return `probe=${probe} mode=${mode} uptime=${uptimeStr}`;\n}\n\nfunction panelLine(label: string, value: string): string {\n // Layout: ║<space><label padded to LABEL_WIDTH><value padded to fill><space>║\n // Cursor Medium: the closing ║ must be preceded by a trailing space\n // so the rendered line width matches the header/footer borders.\n // Without it, every panel line was 71 chars while the borders were\n // 72, misaligning the right-hand box edge.\n const LABEL_WIDTH = 13;\n const labelCol = padRight(label, LABEL_WIDTH);\n // Two spaces (leading + trailing) inside the box, consuming 2 cols.\n const remaining = FRAME_INNER_WIDTH - LABEL_WIDTH - 2;\n const valueCol = padRight(truncate(value, remaining), remaining);\n return `║ ${labelCol}${valueCol} ║`;\n}\n\nfunction padRight(s: string, width: number): string {\n if (s.length >= width) return s;\n return s + \" \".repeat(width - s.length);\n}\n\nfunction truncate(s: string, max: number): string {\n if (max <= 0) return \"\";\n if (s.length <= max) return s;\n if (max <= 1) return s.slice(0, max);\n return s.slice(0, max - 1) + \"…\";\n}\n\nfunction ageMsFromIso(iso: string, now: () => number): number | null {\n const ms = Date.parse(iso);\n if (!Number.isFinite(ms)) return null;\n const delta = now() - ms;\n return delta < 0 ? 0 : delta;\n}\n\n/**\n * Run an async tick function and swallow any rejection so the floated\n * `void runTickSafely(tick)` call never produces an unhandled-rejection\n * crash (which Node ≥15 escalates to a process exit). The tick body\n * itself already wraps everything in try/finally, but this is the\n * outermost belt-and-suspenders guard.\n */\nasync function runTickSafely(tick: () => Promise<void>): Promise<void> {\n try {\n await tick();\n } catch {\n // Any error has already been (or will be) surfaced inside the\n // rendered frame; never let it escape the timer callback.\n }\n}\n\nfunction safeWrite(output: Writable, chunk: string): void {\n try {\n output.write(chunk);\n } catch {\n // Writing to a closed / broken stream is non-fatal for the TUI;\n // the next tick will retry.\n }\n}\n\nfunction describeError(err: unknown): string {\n if (err instanceof Error) return err.message;\n try {\n return String(err);\n } catch {\n return \"unknown error\";\n }\n}\n\n/**\n * Strip ANSI escape sequences from a string. Exposed for tests so the\n * rendered frame can be asserted against plain text.\n */\nexport function stripAnsi(s: string): string {\n // eslint-disable-next-line no-control-regex\n return s.replace(/\\x1b\\[[0-9;?]*[A-Za-z]/g, \"\");\n}\n"],"mappings":";;;;;AAiCA,IAAM,kBAAkB;AAExB,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AAGzB,IAAM,oBAAoB;AAG1B,IAAM,8BAA8B;AAmE7B,SAAS,cACd,cACA,UAAgC,CAAC,GACZ;AACrB,QAAM,oBAAoB,KAAK;AAAA,IAC7B;AAAA,IACA,QAAQ,qBAAqB;AAAA,EAC/B;AACA,QAAM,SAAmB,QAAQ,UAAU,QAAQ;AACnD,QAAM,MAAM,QAAQ,QAAQ,MAAM,KAAK,IAAI;AAC3C,QAAM,uBAAuB,QAAQ,wBAAwB;AAE7D,MAAI,UAAU;AACd,MAAI,WAAW;AAoBf,MAAI,sBAA4C;AAShD,MAAI,oBAAoB;AACxB,MAAI,qBAAqB;AACzB,MAAI;AACJ,QAAM,OAAO,IAAI,QAAc,CAAC,YAAY;AAC1C,kBAAc;AAAA,EAChB,CAAC;AAGD,YAAU,QAAQ,gBAAgB;AAElC,QAAM,OAAO,YAAY;AACvB,QAAI,WAAW,SAAU;AACzB,eAAW;AAQX,QAAI;AACF,UAAI,WAAwC;AAC5C,UAAI,cAA6B;AACjC,UAAI;AACF,mBAAW,MAAM,mBAAmB,YAAY;AAAA,MAClD,SAAS,KAAK;AACZ,sBAAc,cAAc,GAAG;AAAA,MACjC;AACA,UAAI,QAAS;AACb,UAAI;AACJ,UAAI;AACF,gBAAQ,YAAY,EAAE,UAAU,aAAa,IAAI,CAAC;AAAA,MACpD,SAAS,KAAK;AAKZ,gBAAQ,kCAAkC,cAAc,GAAG,CAAC;AAAA;AAAA,MAC9D;AACA,gBAAU,QAAQ,eAAe;AACjC,gBAAU,QAAQ,KAAK;AAevB,UAAI,YAAY,QAAQ,iBAAiB,CAAC,mBAAmB;AAC3D,4BAAoB;AACpB,aAAK,QAAQ,cACV,OAAO,QAAQ,EACf,MAAM,MAAM;AAAA,QAEb,CAAC,EACA,QAAQ,MAAM;AACb,8BAAoB;AAAA,QACtB,CAAC;AAAA,MACL,WAAW,YAAY,QAAQ,eAAe;AAC5C,8BAAsB;AAAA,MACxB;AAAA,IACF,UAAE;AACA,iBAAW;AAAA,IACb;AAAA,EACF;AAeA,QAAM,aAAa,MAAY;AAC7B,UAAM,IAAI,cAAc,IAAI;AAC5B,0BAAsB;AACtB,SAAK,EAAE,KAAK,MAAM;AAChB,UAAI,wBAAwB,GAAG;AAC7B,8BAAsB;AAAA,MACxB;AAAA,IACF,CAAC;AAAA,EACH;AACA,aAAW;AACX,QAAM,SAAS,YAAY,YAAY,iBAAiB;AAExD,QAAM,gBAAgB,MAAM;AAC1B,SAAK;AAAA,EACP;AACA,MAAI,sBAAsB;AACxB,YAAQ,GAAG,UAAU,aAAa;AAAA,EACpC;AAEA,QAAM,OAAO,MAAM;AACjB,QAAI,QAAS;AACb,cAAU;AACV,kBAAc,MAAM;AACpB,QAAI,sBAAsB;AACxB,UAAI;AACF,gBAAQ,eAAe,UAAU,aAAa;AAAA,MAChD,QAAQ;AAAA,MAER;AAAA,IACF;AACA,cAAU,QAAQ,gBAAgB;AAiBlC,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,cAAc;AACpB,YAAI,aAAa;AACf,gBAAM;AAAA,QACR;AAAA,MACF,UAAE;AACA,oBAAY;AAAA,MACd;AAAA,IACF,GAAG;AAAA,EACL;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,uBAAuB,MAAM;AAAA,EAC/B;AACF;AAYO,SAAS,YAAY,OAAiC;AAC3D,QAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC,EAAE,YAAY;AAC7C,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,aAAa,EAAE,CAAC;AAC3B,aAAW,SAAS,aAAa,KAAK,GAAG;AACvC,UAAM,KAAK,KAAK;AAAA,EAClB;AACA,QAAM,KAAK,aAAa,CAAC;AACzB,SAAO,MAAM,KAAK,IAAI,IAAI;AAC5B;AAEA,SAAS,aAAa,IAAoB;AAExC,QAAM,QAAQ;AACd,QAAM,WAAW,IAAI,EAAE;AAEvB,QAAM,YAAY,KAAK;AAAA,IACrB;AAAA,IACA,oBAAoB,MAAM,SAAS,SAAS;AAAA,EAC9C;AACA,QAAM,SAAS,SAAI,OAAO,SAAS;AACnC,SAAO,SAAI,KAAK,GAAG,MAAM,GAAG,QAAQ;AACtC;AAEA,SAAS,eAAuB;AAC9B,SAAO,SAAI,SAAI,OAAO,iBAAiB,CAAC;AAC1C;AAEA,SAAS,aAAa,OAAmC;AACvD,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAO,MAAM;AAEnB,MAAI,MAAM,gBAAgB,MAAM;AAC9B,UAAM,KAAK,UAAU,SAAS,mBAAmB,MAAM,WAAW,EAAE,CAAC;AAGrE,UAAM,KAAK,UAAU,UAAU,eAAe,CAAC;AAC/C,UAAM,KAAK,UAAU,cAAc,eAAe,CAAC;AACnD,UAAM,KAAK,UAAU,SAAS,eAAe,CAAC;AAC9C,UAAM,KAAK,UAAU,eAAe,eAAe,CAAC;AACpD,UAAM,KAAK,UAAU,OAAO,eAAe,CAAC;AAC5C,WAAO;AAAA,EACT;AAGA,MAAI,MAAM;AACR,UAAM;AAAA,MACJ;AAAA,QACE;AAAA,QACA,SAAS,KAAK,YAAY,UAAU,UAAU,KAAK,YAAY,SAAS;AAAA,MAC1E;AAAA,IACF;AAGA,UAAM,WAAW,KAAK,gBAAgB;AACtC,UAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE;AAC5D,UAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE;AAC5D,UAAM;AAAA,MACJ;AAAA,QACE;AAAA,QACA,SAAS,KAAK,gBAAgB,KAAK,6BAA6B,OAAO,YAAY,OAAO;AAAA,MAC5F;AAAA,IACF;AAGA,UAAM,eAAe,mBAAmB,KAAK,aAAa,MAAM,GAAG;AACnE,UAAM,KAAK,UAAU,SAAS,YAAY,CAAC;AAG3C,UAAM,eAAe,sBAAsB,KAAK,qBAAqB;AACrE,UAAM,KAAK,UAAU,eAAe,YAAY,CAAC;AAGjD,UAAM,KAAK,UAAU,OAAO,iBAAiB,IAAI,CAAC,CAAC;AAAA,EACrD;AAKA,MAAI,QAAQ,KAAK,OAAO,SAAS,GAAG;AAClC,UAAM,OAAO,KAAK,OAAO,CAAC;AAC1B,UAAM,KAAK,UAAU,UAAU,QAAQ,EAAE,CAAC;AAAA,EAC5C;AAEA,SAAO;AACT;AAEA,SAAS,mBACP,WACA,KACQ;AACR,MAAI,UAAU,WAAW,EAAG,QAAO;AACnC,QAAM,OAAO,UAAU,UAAU,SAAS,CAAC;AAC3C,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,aAAa,KAAK,IAAI,GAAG;AACvC,QAAM,SAAS,UAAU,OAAO,QAAQ,KAAK,KAAK,MAAM,QAAQ,GAAI,CAAC;AACrE,QAAM,KAAK,KAAK,cAAc,QAAQ,KAAK,WAAW,KAAK;AAC3D,SAAO,WAAW,EAAE,aAAa,KAAK,QAAQ,KAAK,MAAM;AAC3D;AAEA,SAAS,sBACP,QACQ;AACR,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,QAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,MAAI,CAAC,KAAM,QAAO;AAElB,SAAO,KAAK,OAAO,MAAM,WAAW,KAAK,QAAQ,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;AACjF;AAEA,SAAS,iBAAiB,MAAoC;AAC5D,QAAM,QAAQ,KAAK,SAAS,YAAY,OAAO;AAC/C,QAAM,UAAU,KAAK,OAAO,WAAW;AACvC,QAAM,YAAY,UAAU,IACxB,GAAG,KAAK,MAAM,KAAK,OAAO,WAAW,GAAI,CAAC,MAC1C,GAAG,QAAQ,QAAQ,CAAC,CAAC;AACzB,QAAM,OAAO,KAAK,SAAS,aAAa,WAAW;AACnD,SAAO,SAAS,KAAK,UAAU,IAAI,YAAY,SAAS;AAC1D;AAEA,SAAS,UAAU,OAAe,OAAuB;AAMvD,QAAM,cAAc;AACpB,QAAM,WAAW,SAAS,OAAO,WAAW;AAE5C,QAAM,YAAY,oBAAoB,cAAc;AACpD,QAAM,WAAW,SAAS,SAAS,OAAO,SAAS,GAAG,SAAS;AAC/D,SAAO,UAAK,QAAQ,GAAG,QAAQ;AACjC;AAEA,SAAS,SAAS,GAAW,OAAuB;AAClD,MAAI,EAAE,UAAU,MAAO,QAAO;AAC9B,SAAO,IAAI,IAAI,OAAO,QAAQ,EAAE,MAAM;AACxC;AAEA,SAAS,SAAS,GAAW,KAAqB;AAChD,MAAI,OAAO,EAAG,QAAO;AACrB,MAAI,EAAE,UAAU,IAAK,QAAO;AAC5B,MAAI,OAAO,EAAG,QAAO,EAAE,MAAM,GAAG,GAAG;AACnC,SAAO,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI;AAC/B;AAEA,SAAS,aAAa,KAAa,KAAkC;AACnE,QAAM,KAAK,KAAK,MAAM,GAAG;AACzB,MAAI,CAAC,OAAO,SAAS,EAAE,EAAG,QAAO;AACjC,QAAM,QAAQ,IAAI,IAAI;AACtB,SAAO,QAAQ,IAAI,IAAI;AACzB;AASA,eAAe,cAAc,MAA0C;AACrE,MAAI;AACF,UAAM,KAAK;AAAA,EACb,QAAQ;AAAA,EAGR;AACF;AAEA,SAAS,UAAU,QAAkB,OAAqB;AACxD,MAAI;AACF,WAAO,MAAM,KAAK;AAAA,EACpB,QAAQ;AAAA,EAGR;AACF;AAEA,SAAS,cAAc,KAAsB;AAC3C,MAAI,eAAe,MAAO,QAAO,IAAI;AACrC,MAAI;AACF,WAAO,OAAO,GAAG;AAAA,EACnB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,UAAU,GAAmB;AAE3C,SAAO,EAAE,QAAQ,2BAA2B,EAAE;AAChD;","names":[]}
@@ -0,0 +1,228 @@
1
+ import {
2
+ createVersion
3
+ } from "./chunk-FAAFWE4G.js";
4
+ import {
5
+ assertIsDirectoryNotSymlink,
6
+ assertRealpathInsideRoot,
7
+ fromPosixRelPath,
8
+ isPathInsideRoot,
9
+ sha256String
10
+ } from "./chunk-457A4P3L.js";
11
+ import {
12
+ parseExportBundle
13
+ } from "./chunk-OA3L7BFR.js";
14
+ import {
15
+ decryptCapsuleFileInMemory,
16
+ isEncryptedCapsuleFile
17
+ } from "./chunk-KNKUID7G.js";
18
+
19
+ // src/transfer/capsule-import.ts
20
+ import { lstat, mkdir, readFile, realpath, stat, writeFile } from "fs/promises";
21
+ import path from "path";
22
+ import { createHash, randomUUID } from "crypto";
23
+ import { gunzipSync } from "zlib";
24
+ async function importCapsule(opts) {
25
+ const archiveAbs = path.resolve(opts.archivePath);
26
+ const rootAbs = path.resolve(opts.root);
27
+ await assertIsDirectoryNotSymlink(rootAbs, "importCapsule", "root");
28
+ const mode = opts.mode ?? "skip";
29
+ if (mode !== "skip" && mode !== "overwrite" && mode !== "fork") {
30
+ throw new Error(
31
+ `importCapsule: unknown mode ${JSON.stringify(mode)}; expected "skip", "overwrite", or "fork"`
32
+ );
33
+ }
34
+ const encrypted = await isEncryptedCapsuleFile(archiveAbs);
35
+ let raw;
36
+ if (encrypted) {
37
+ if (!opts.memoryDir) {
38
+ throw new Error(
39
+ `importCapsule: archive is encrypted but 'memoryDir' was not provided. Pass the memory directory so the secure-store key can be retrieved, or run \`remnic secure-store unlock\` before importing.`
40
+ );
41
+ }
42
+ raw = await decryptCapsuleFileInMemory(archiveAbs, opts.memoryDir);
43
+ } else {
44
+ raw = await readFile(archiveAbs);
45
+ }
46
+ const json = gunzipSync(raw).toString("utf-8");
47
+ let parsedJson;
48
+ try {
49
+ parsedJson = JSON.parse(json);
50
+ } catch (cause) {
51
+ throw new Error(
52
+ `importCapsule: archive is not valid JSON after gunzip: ${archiveAbs}`,
53
+ { cause }
54
+ );
55
+ }
56
+ const parsed = parseExportBundle(parsedJson);
57
+ if (parsed.capsuleVersion !== 2) {
58
+ throw new Error(
59
+ "importCapsule: archive is V1; only V2 capsule archives are supported"
60
+ );
61
+ }
62
+ const bundle = parsed.bundle;
63
+ const manifest = bundle.manifest;
64
+ const capsule = manifest.capsule;
65
+ const manifestIndex = /* @__PURE__ */ new Map();
66
+ for (const f of manifest.files) {
67
+ manifestIndex.set(f.path, f);
68
+ }
69
+ if (manifestIndex.size !== manifest.files.length) {
70
+ throw new Error(
71
+ "importCapsule: manifest contains duplicate file paths"
72
+ );
73
+ }
74
+ const recordPaths = /* @__PURE__ */ new Set();
75
+ for (const rec of bundle.records) {
76
+ if (recordPaths.has(rec.path)) {
77
+ throw new Error(
78
+ `importCapsule: bundle contains duplicate record path: ${rec.path}`
79
+ );
80
+ }
81
+ recordPaths.add(rec.path);
82
+ }
83
+ const rootReal = await realpath(rootAbs).catch(() => rootAbs);
84
+ const seenTargetPaths = /* @__PURE__ */ new Map();
85
+ for (const rec of bundle.records) {
86
+ const entry = manifestIndex.get(rec.path);
87
+ if (!entry) {
88
+ throw new Error(
89
+ `importCapsule: archive checksum mismatch (record without manifest entry: ${rec.path})`
90
+ );
91
+ }
92
+ const { sha256, bytes } = sha256String(rec.content);
93
+ if (sha256 !== entry.sha256 || bytes !== entry.bytes) {
94
+ throw new Error(
95
+ `importCapsule: archive checksum mismatch for ${rec.path}: expected sha256=${entry.sha256} bytes=${entry.bytes}, got sha256=${sha256} bytes=${bytes}`
96
+ );
97
+ }
98
+ if (rec.path.includes("\\")) {
99
+ throw new Error(
100
+ `importCapsule: record path contains backslash separators (Windows-style paths are not allowed): ${rec.path}`
101
+ );
102
+ }
103
+ const posixNormalized = path.posix.normalize(rec.path);
104
+ if (rec.path.startsWith("/") || rec.path.split("/").some((seg) => seg === "..") || posixNormalized.startsWith("..") || posixNormalized.startsWith("/")) {
105
+ throw new Error(
106
+ `importCapsule: record path escapes target root: ${rec.path}`
107
+ );
108
+ }
109
+ const targetRel = computeTargetPath(rec.path, mode, capsule.id);
110
+ const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));
111
+ if (!isPathInsideRoot(rootReal, targetAbs)) {
112
+ throw new Error(
113
+ `importCapsule: record path escapes target root: ${rec.path}`
114
+ );
115
+ }
116
+ await assertRealpathInsideRoot(rootReal, targetAbs, rec.path, "importCapsule");
117
+ const targetLstat = await lstat(targetAbs).catch(() => null);
118
+ if (targetLstat !== null && targetLstat.isSymbolicLink()) {
119
+ throw new Error(
120
+ `importCapsule: record target is a symlink and cannot be written to safely: ${rec.path}`
121
+ );
122
+ }
123
+ const dedupKey = targetAbs.toLowerCase();
124
+ const firstSourcePath = seenTargetPaths.get(dedupKey);
125
+ if (firstSourcePath !== void 0) {
126
+ throw new Error(
127
+ `importCapsule: manifest contains two entries that resolve to the same target path: "${firstSourcePath}" and "${rec.path}" both map to "${targetRel}"`
128
+ );
129
+ }
130
+ seenTargetPaths.set(dedupKey, rec.path);
131
+ }
132
+ for (const f of manifest.files) {
133
+ if (!recordPaths.has(f.path)) {
134
+ throw new Error(
135
+ `importCapsule: archive checksum mismatch (manifest entry without record: ${f.path})`
136
+ );
137
+ }
138
+ }
139
+ const imported = [];
140
+ const skipped = [];
141
+ const sortedRecords = [...bundle.records].sort(
142
+ (a, b) => a.path.localeCompare(b.path)
143
+ );
144
+ for (const rec of sortedRecords) {
145
+ const targetRel = computeTargetPath(rec.path, mode, capsule.id);
146
+ const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));
147
+ const exists = await fileExistsAt(targetAbs);
148
+ if ((mode === "skip" || mode === "fork") && exists) {
149
+ skipped.push({ path: rec.path, reason: "exists" });
150
+ continue;
151
+ }
152
+ let snapshotted = false;
153
+ if (mode === "overwrite" && exists) {
154
+ if (opts.versioning && opts.versioning.enabled) {
155
+ const prior = await readFile(targetAbs, "utf-8").catch(() => "");
156
+ await createVersion(
157
+ targetAbs,
158
+ prior,
159
+ "manual",
160
+ opts.versioning,
161
+ opts.log,
162
+ `capsule-import: ${capsule.id}`,
163
+ rootReal
164
+ );
165
+ snapshotted = true;
166
+ }
167
+ }
168
+ let contentToWrite = rec.content;
169
+ let rewroteId = false;
170
+ if (mode === "fork") {
171
+ const forked = rewriteFrontmatterIdForFork(rec.content, capsule.id, opts.now);
172
+ contentToWrite = forked.content;
173
+ rewroteId = forked.rewrote;
174
+ }
175
+ await mkdir(path.dirname(targetAbs), { recursive: true });
176
+ await writeFile(targetAbs, contentToWrite, "utf-8");
177
+ imported.push({
178
+ sourcePath: rec.path,
179
+ targetPath: targetRel,
180
+ snapshotted,
181
+ rewroteId
182
+ });
183
+ }
184
+ skipped.sort((a, b) => a.path.localeCompare(b.path));
185
+ return { imported, skipped, manifest };
186
+ }
187
+ function computeTargetPath(sourcePosix, mode, capsuleId) {
188
+ if (mode === "fork") return `forks/${capsuleId}/${sourcePosix}`;
189
+ return sourcePosix;
190
+ }
191
+ async function fileExistsAt(absPath) {
192
+ const st = await stat(absPath).catch(() => null);
193
+ return st !== null && st.isFile();
194
+ }
195
+ function rewriteFrontmatterIdForFork(content, capsuleId, now) {
196
+ const crlfMatch = /^---(\r\n|\r|\n)/.exec(content);
197
+ const eol = crlfMatch ? crlfMatch[1] : "\n";
198
+ const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/.exec(content);
199
+ if (!fmMatch) return { content, rewrote: false };
200
+ const fmBody = fmMatch[1];
201
+ const fmTrailer = fmMatch[2];
202
+ const fmStart = fmMatch.index;
203
+ const fmEnd = fmStart + fmMatch[0].length;
204
+ const idLineRe = /^id:[ \t]*([^\r\n]*)$/m;
205
+ const idMatch = idLineRe.exec(fmBody);
206
+ if (!idMatch) return { content, rewrote: false };
207
+ const newId = mintForkId(capsuleId, idMatch[1]?.trim() ?? "", now);
208
+ const replacedBody = fmBody.replace(idLineRe, `id: ${newId}`);
209
+ const prefix = content.slice(0, fmStart);
210
+ const tail = content.slice(fmEnd);
211
+ const rebuilt = `${prefix}---${eol}${replacedBody}${eol}---${fmTrailer}${tail}`;
212
+ return { content: rebuilt, rewrote: true };
213
+ }
214
+ function mintForkId(capsuleId, originalId, now) {
215
+ const base = originalId.length > 0 ? originalId : "fork";
216
+ if (now === void 0) {
217
+ const suffix2 = randomUUID().slice(0, 8);
218
+ return `${base}-fork-${capsuleId}-${suffix2}`;
219
+ }
220
+ const payload = JSON.stringify({ c: capsuleId, i: originalId, n: now });
221
+ const suffix = createHash("sha256").update(payload, "utf-8").digest("hex").slice(0, 8);
222
+ return `${base}-fork-${capsuleId}-${suffix}`;
223
+ }
224
+
225
+ export {
226
+ importCapsule
227
+ };
228
+ //# sourceMappingURL=chunk-3KIS4VGT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transfer/capsule-import.ts"],"sourcesContent":["import { lstat, mkdir, readFile, realpath, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { gunzipSync } from \"node:zlib\";\nimport {\n createVersion,\n type VersioningConfig,\n type VersioningLogger,\n} from \"../page-versioning.js\";\nimport {\n assertIsDirectoryNotSymlink,\n assertRealpathInsideRoot,\n fromPosixRelPath,\n isPathInsideRoot,\n sha256String,\n} from \"./fs-utils.js\";\nimport {\n parseExportBundle,\n type CapsuleBlock,\n type ExportManifestV2,\n type ExportMemoryRecordV1,\n} from \"./types.js\";\nimport { isEncryptedCapsuleFile, decryptCapsuleFileInMemory } from \"./capsule-crypto.js\";\n\n/**\n * Conflict-resolution mode for {@link importCapsule}. Inverse of the\n * `mode` selector in PR 4/6's CLI surface.\n *\n * - `\"skip\"` (default) — when a target file already exists at the same\n * relative path, leave it untouched. The corresponding record is reported\n * via {@link ImportCapsuleResult.skipped}.\n * - `\"overwrite\"` — when a target file exists, snapshot the prior content\n * via {@link createVersion} (gotcha #54: write-before-delete; gotcha #25:\n * don't destroy old state until new state is confirmed), then write the\n * incoming content. The snapshot's `note` includes the source capsule id.\n * - `\"fork\"` — never overwrite. Every record is rebased under\n * `forks/<capsule-id>/<original-path>` and any YAML-frontmatter `id:`\n * field is rewritten to a new fork-scoped value. The original tree is\n * not touched.\n *\n * The mode is selected once per call and applied uniformly to every record\n * in the bundle. Mixed-mode imports are not supported by design — that\n * would let a single corrupted manifest silently land partial state.\n */\nexport type ImportCapsuleMode = \"skip\" | \"overwrite\" | \"fork\";\n\n/**\n * Options accepted by {@link importCapsule}.\n *\n * `archivePath` — absolute or cwd-relative path to a `.capsule.json.gz`\n * archive produced by `exportCapsule`. The archive must contain a V2\n * bundle (`schemaVersion: 2`); V1 archives are rejected.\n *\n * `root` — absolute or cwd-relative path to the memory directory that\n * will receive the records. Must be an existing directory.\n *\n * `mode` — see {@link ImportCapsuleMode}. Defaults to `\"skip\"`.\n *\n * `versioning` — optional page-versioning config used by `mode: \"overwrite\"`\n * to snapshot prior content before replacing it. Snapshots are skipped when\n * not provided or when `enabled === false`. Tests pass an enabled config\n * to assert snapshot creation; production callers thread the\n * orchestrator's resolved versioning config.\n *\n * `log` — optional logger forwarded to {@link createVersion}.\n *\n * `now` — optional clock override (ms epoch) used to derive deterministic\n * fork ids in tests. Production callers omit this.\n */\nexport interface ImportCapsuleOptions {\n archivePath: string;\n root: string;\n mode?: ImportCapsuleMode;\n versioning?: VersioningConfig;\n log?: VersioningLogger;\n now?: number;\n /**\n * Memory directory whose secure-store keyring is used when the archive is\n * encrypted. Required when `archivePath` ends with `.enc` or when the file\n * starts with the REMNIC-ENC magic header. If `memoryDir` is omitted and the\n * archive turns out to be encrypted, `importCapsule` throws a clear error\n * rather than silently failing.\n *\n * When provided and the archive is NOT encrypted, the value is ignored.\n */\n memoryDir?: string;\n}\n\nexport interface ImportCapsuleSkippedRecord {\n /** Original record path (capsule-relative, posix). */\n path: string;\n /**\n * Why this record was not written.\n *\n * `\"exists\"` — the target path already existed and the mode was `\"skip\"`,\n * OR the computed fork path already existed in fork mode.\n *\n * `\"checksum_mismatch\"` is intentionally absent: a checksum failure aborts\n * the import entirely (fail-closed) rather than skipping the offending\n * record, so no `ImportCapsuleSkippedRecord` is ever produced for it.\n */\n reason: \"exists\";\n}\n\nexport interface ImportCapsuleImportedRecord {\n /** Capsule-relative posix path the record carried. */\n sourcePath: string;\n /** Memory-dir-relative posix path the file was written to. */\n targetPath: string;\n /** Whether a prior version snapshot was taken (overwrite-mode only). */\n snapshotted: boolean;\n /** Whether the frontmatter `id:` field was rewritten (fork-mode only). */\n rewroteId: boolean;\n}\n\nexport interface ImportCapsuleResult {\n /** Records that landed on disk. */\n imported: ImportCapsuleImportedRecord[];\n /** Records that were not written. */\n skipped: ImportCapsuleSkippedRecord[];\n /** The manifest decoded from the archive. */\n manifest: ExportManifestV2;\n}\n\n/**\n * Pure async function that imports a capsule archive into a memory\n * directory. Inverse of {@link import(\"./capsule-export.js\").exportCapsule}.\n *\n * Sequence:\n * 1. Read + gunzip + JSON.parse the archive.\n * 2. Validate the bundle through `parseExportBundle` (V1 rejected).\n * 3. Verify each record's content sha256 against the manifest entry.\n * Any mismatch aborts the import BEFORE any file is written —\n * partial-write recovery would require a full rollback we cannot\n * offer cheaply, so we fail closed (gotcha #25).\n * 4. Apply the selected {@link ImportCapsuleMode} to every record.\n *\n * Determinism guarantees:\n * - Imported records are returned sorted by `sourcePath`.\n * - Skipped records are returned sorted by `path`.\n * - Fork-mode rebases under a stable `forks/<capsule-id>/` prefix so\n * repeated fork imports of the same capsule shape land in predictable\n * locations (subsequent fork imports still skip-on-exist because the\n * target tree is computed from the source path).\n */\nexport async function importCapsule(\n opts: ImportCapsuleOptions,\n): Promise<ImportCapsuleResult> {\n const archiveAbs = path.resolve(opts.archivePath);\n const rootAbs = path.resolve(opts.root);\n await assertIsDirectoryNotSymlink(rootAbs, \"importCapsule\", \"root\");\n\n const mode: ImportCapsuleMode = opts.mode ?? \"skip\";\n // Reject unknown mode values up-front (rule 51). TypeScript callers get a\n // compile-time check via the union type; JS/CLI callers get a runtime error\n // here before any write begins — not a silent destructive fallback.\n if (mode !== \"skip\" && mode !== \"overwrite\" && mode !== \"fork\") {\n throw new Error(\n `importCapsule: unknown mode ${JSON.stringify(mode)}; expected \"skip\", \"overwrite\", or \"fork\"`,\n );\n }\n\n // Auto-detect encrypted archives. If the file ends with `.enc` or starts\n // with the REMNIC-ENC magic header, decrypt it first before gunzip+parse.\n // Rule 48: default is \"not encrypted\"; we only attempt decryption when the\n // header is definitively present, never on ambiguous content.\n const encrypted = await isEncryptedCapsuleFile(archiveAbs);\n let raw: Buffer;\n if (encrypted) {\n if (!opts.memoryDir) {\n throw new Error(\n `importCapsule: archive is encrypted but 'memoryDir' was not provided. ` +\n `Pass the memory directory so the secure-store key can be retrieved, ` +\n `or run \\`remnic secure-store unlock\\` before importing.`,\n );\n }\n raw = await decryptCapsuleFileInMemory(archiveAbs, opts.memoryDir);\n } else {\n raw = await readFile(archiveAbs);\n }\n const json = gunzipSync(raw).toString(\"utf-8\");\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(json);\n } catch (cause) {\n throw new Error(\n `importCapsule: archive is not valid JSON after gunzip: ${archiveAbs}`,\n { cause: cause as Error },\n );\n }\n\n const parsed = parseExportBundle(parsedJson);\n if (parsed.capsuleVersion !== 2) {\n // PR 1/6 ships a V1 reader; capsule import is V2-only by design. A V1\n // bundle does not carry a capsule block, so fork-mode (which depends on\n // `capsule.id` for the rebase prefix) cannot work. Reject up-front per\n // gotcha #51 instead of silently routing to a different code path.\n throw new Error(\n \"importCapsule: archive is V1; only V2 capsule archives are supported\",\n );\n }\n const bundle = parsed.bundle as { manifest: ExportManifestV2; records: ExportMemoryRecordV1[] };\n const manifest = bundle.manifest;\n const capsule = manifest.capsule;\n\n // Build a path → manifest-entry index for O(1) checksum lookup. The\n // manifest is the source of truth; records-without-manifest-entries and\n // manifest-entries-without-records are both treated as corruption.\n const manifestIndex = new Map<string, ExportManifestV2[\"files\"][number]>();\n for (const f of manifest.files) {\n manifestIndex.set(f.path, f);\n }\n if (manifestIndex.size !== manifest.files.length) {\n throw new Error(\n \"importCapsule: manifest contains duplicate file paths\",\n );\n }\n const recordPaths = new Set<string>();\n for (const rec of bundle.records) {\n if (recordPaths.has(rec.path)) {\n throw new Error(\n `importCapsule: bundle contains duplicate record path: ${rec.path}`,\n );\n }\n recordPaths.add(rec.path);\n }\n\n // Phase 1: verify every record matches its manifest entry AND validate all\n // target paths before any filesystem mutation. We do both in a single pass\n // so a corrupted or malicious archive cannot leave the memory dir partially\n // written (fail-closed per gotcha #25).\n //\n // The real root is resolved once via realpath so the subsequent per-record\n // inside-root checks are symlink-aware: a record path like `facts/a.md`\n // cannot write outside the intended sandbox via a symlinked subdirectory\n // (Codex P1 feedback). If realpath fails (root does not exist) the earlier\n // assertIsDirectoryNotSymlink call already threw, so this should always succeed.\n const rootReal = await realpath(rootAbs).catch(() => rootAbs);\n\n // Tracks normalized, case-folded target paths seen so far in phase 1. Maps\n // targetAbs.toLowerCase() → first source path so the collision error can name\n // both offending entries. Two manifest entries whose computed target paths\n // normalize to the same absolute path (e.g. `subdir/file.md` and\n // `subdir/./file.md`, or differing case on case-insensitive filesystems such\n // as macOS and Windows) would both try to write the same inode — the second\n // would silently overwrite the first. We reject the import up-front before\n // any write (Codex P2 thread on PR #741, line 283).\n const seenTargetPaths = new Map<string, string>();\n\n for (const rec of bundle.records) {\n // Checksum validation.\n const entry = manifestIndex.get(rec.path);\n if (!entry) {\n throw new Error(\n `importCapsule: archive checksum mismatch (record without manifest entry: ${rec.path})`,\n );\n }\n const { sha256, bytes } = sha256String(rec.content);\n if (sha256 !== entry.sha256 || bytes !== entry.bytes) {\n throw new Error(\n `importCapsule: archive checksum mismatch for ${rec.path}: ` +\n `expected sha256=${entry.sha256} bytes=${entry.bytes}, ` +\n `got sha256=${sha256} bytes=${bytes}`,\n );\n }\n\n // Source path validation: reject any record whose posix source path\n // contains a `..` segment, an absolute prefix, or any component that would\n // let fork-mode bypass its isolation invariant. This check runs before\n // `computeTargetPath` so that fork mode's `forks/<id>/` rebase cannot be\n // bypassed by a path like `../../profile.md` (which `path.join` would\n // silently collapse, landing the file inside root but outside the fork\n // prefix — Cursor medium thread #741). Rejecting `..` up-front is simpler\n // and more robust than normalising after the fact.\n //\n // Extended guard (Codex P1 #741 round 5): also reject paths containing\n // backslash separators (`\\`), which are Windows path separators. On Windows\n // a path like `..\\\\escape.md` or `subdir\\\\..\\\\..\\\\escape.md` would resolve\n // to a parent traversal but would bypass a purely posix-split check.\n // Additionally, after posix-normalizing the path we re-check for a leading\n // `..` or `/` — this catches edge-cases such as `subdir/../..` where the\n // individual split segments are not `..` but the normalized result is.\n if (rec.path.includes(\"\\\\\")) {\n throw new Error(\n `importCapsule: record path contains backslash separators (Windows-style paths are not allowed): ${rec.path}`,\n );\n }\n const posixNormalized = path.posix.normalize(rec.path);\n if (\n rec.path.startsWith(\"/\") ||\n rec.path.split(\"/\").some((seg) => seg === \"..\") ||\n posixNormalized.startsWith(\"..\") ||\n posixNormalized.startsWith(\"/\")\n ) {\n throw new Error(\n `importCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Path-traversal validation (moved here from phase 2 per Cursor feedback:\n // the traversal check must run before ANY write so a malicious archive\n // with an escaping path that sorts last cannot land partial writes).\n //\n // We build targetAbs relative to the real-resolved root (rootReal) so\n // that the inside-root check is symlink-aware: if `rootReal !== rootAbs`\n // (the import root is itself behind a symlink), we still compare against\n // the canonical path (Codex P1).\n const targetRel = computeTargetPath(rec.path, mode, capsule.id);\n const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));\n if (!isPathInsideRoot(rootReal, targetAbs)) {\n throw new Error(\n `importCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Symlink-aware containment check (Cursor thread #741 line 247/264).\n // The lexical check above handles `..`-traversal but cannot detect\n // symlinked subdirectories that point outside the root. We resolve the\n // nearest existing ancestor of the target via realpath to catch symlinks\n // anywhere in the path components. If any resolved prefix escapes rootReal,\n // the import is rejected before any write. This applies to ALL modes,\n // including fork mode whose rebase prefix may itself traverse a symlink.\n await assertRealpathInsideRoot(rootReal, targetAbs, rec.path, \"importCapsule\");\n\n // Target-file symlink check (Codex P1 #741 round 4, line 260).\n // `assertRealpathInsideRoot` resolves the nearest existing *ancestor* to\n // catch symlinked parent directories, but if the TARGET FILE itself already\n // exists and is a symlink, that check passes (the parent is real) while the\n // write would silently follow the symlink to a path outside root. We\n // therefore lstat the target directly: if it exists and is a symlink we\n // reject the record up-front. For new files the parent-canonicalization\n // performed above is sufficient; no realpath of a non-existent file is\n // needed.\n const targetLstat = await lstat(targetAbs).catch(() => null);\n if (targetLstat !== null && targetLstat.isSymbolicLink()) {\n throw new Error(\n `importCapsule: record target is a symlink and cannot be written to safely: ${rec.path}`,\n );\n }\n\n // Duplicate normalized target path detection (Codex P2 #741, line 283).\n // `path.join` already normalises `.` segments (e.g. `subdir/./file.md` →\n // `subdir/file.md`). On case-insensitive filesystems (macOS default,\n // Windows), two paths that differ only in case would resolve to the same\n // inode. We fold the dedup key to lowercase so that `subdir/File.md` and\n // `subdir/file.md` are detected as duplicates before any write occurs.\n // This is intentionally unconditional: the cost of an extra `.toLowerCase()`\n // on case-sensitive filesystems is negligible, and a defensive lowercase\n // is far simpler than probing filesystem case-sensitivity at runtime.\n const dedupKey = targetAbs.toLowerCase();\n const firstSourcePath = seenTargetPaths.get(dedupKey);\n if (firstSourcePath !== undefined) {\n throw new Error(\n `importCapsule: manifest contains two entries that resolve to the same target path: ` +\n `\"${firstSourcePath}\" and \"${rec.path}\" both map to \"${targetRel}\"`,\n );\n }\n seenTargetPaths.set(dedupKey, rec.path);\n }\n // Detect manifest-only entries (missing record). Treat as corruption.\n for (const f of manifest.files) {\n if (!recordPaths.has(f.path)) {\n throw new Error(\n `importCapsule: archive checksum mismatch (manifest entry without record: ${f.path})`,\n );\n }\n }\n\n // Phase 2: apply the mode. All records were validated in phase 1; we now\n // write to disk. Per-record errors propagate; each individual write is\n // atomic (mkdir + writeFile pair).\n const imported: ImportCapsuleImportedRecord[] = [];\n const skipped: ImportCapsuleSkippedRecord[] = [];\n\n // Sort by source path so the imported/skipped lists are deterministic\n // regardless of bundle order. (`exportCapsule` already sorts; we re-sort\n // defensively in case a hand-edited archive ships records in another order.)\n const sortedRecords = [...bundle.records].sort((a, b) =>\n a.path.localeCompare(b.path),\n );\n\n for (const rec of sortedRecords) {\n const targetRel = computeTargetPath(rec.path, mode, capsule.id);\n // Use rootReal (realpath-resolved) to stay consistent with phase 1's\n // traversal validation and avoid writing through stale symlinks.\n const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));\n\n const exists = await fileExistsAt(targetAbs);\n\n // Skip logic applies to both `skip` mode (explicit) and `fork` mode\n // (Cursor medium: fork mode must also be skip-on-exist for the computed\n // fork path, because production imports generate non-deterministic IDs —\n // so re-importing the same capsule should not overwrite user edits in the\n // fork tree or silently change file identities by generating new IDs).\n if ((mode === \"skip\" || mode === \"fork\") && exists) {\n skipped.push({ path: rec.path, reason: \"exists\" });\n continue;\n }\n\n let snapshotted = false;\n if (mode === \"overwrite\" && exists) {\n // Gotcha #54: snapshot BEFORE overwriting. If snapshot creation fails\n // we abort this record's import rather than silently destroying\n // history. The page-versioning module is responsible for atomic\n // sidecar writes; we just call it.\n if (opts.versioning && opts.versioning.enabled) {\n const prior = await readFile(targetAbs, \"utf-8\").catch(() => \"\");\n await createVersion(\n targetAbs,\n prior,\n \"manual\",\n opts.versioning,\n opts.log,\n `capsule-import: ${capsule.id}`,\n rootReal,\n );\n snapshotted = true;\n }\n }\n\n let contentToWrite = rec.content;\n let rewroteId = false;\n if (mode === \"fork\") {\n const forked = rewriteFrontmatterIdForFork(rec.content, capsule.id, opts.now);\n contentToWrite = forked.content;\n rewroteId = forked.rewrote;\n }\n\n await mkdir(path.dirname(targetAbs), { recursive: true });\n await writeFile(targetAbs, contentToWrite, \"utf-8\");\n imported.push({\n sourcePath: rec.path,\n targetPath: targetRel,\n snapshotted,\n rewroteId,\n });\n }\n\n // Sort skipped for stable output (see comment above).\n skipped.sort((a, b) => a.path.localeCompare(b.path));\n\n return { imported, skipped, manifest };\n}\n\n// ---------------------------------------------------------------------------\n// Path helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the destination relative path for a record under the chosen\n * mode.\n *\n * - `skip` / `overwrite` — identity: the record's posix path is used as-is.\n * - `fork` — rebased under `forks/<capsule-id>/<original-path>` so the\n * original tree is never modified.\n */\nfunction computeTargetPath(\n sourcePosix: string,\n mode: ImportCapsuleMode,\n capsuleId: string,\n): string {\n if (mode === \"fork\") return `forks/${capsuleId}/${sourcePosix}`;\n return sourcePosix;\n}\n\nasync function fileExistsAt(absPath: string): Promise<boolean> {\n const st = await stat(absPath).catch(() => null);\n return st !== null && st.isFile();\n}\n\n// ---------------------------------------------------------------------------\n// Fork-mode id rewriting\n// ---------------------------------------------------------------------------\n\n/**\n * Rewrite a YAML-frontmatter `id:` field to a fork-scoped value.\n *\n * Strategy: we deliberately do NOT parse the entire YAML — memory files\n * use a deterministic top-of-file frontmatter delimited by `---` lines,\n * and the orchestrator's `parseFrontmatter` reads `id` as a top-level\n * key with a single-line scalar value. We mirror that contract:\n *\n * - Detect a leading `---\\n` … `\\n---` block at the top of the file.\n * - Within that block, locate the first `id:` line that is not nested\n * under another key (cheap heuristic: line starts at column 0).\n * - Replace its value with a new fork-id derived from `capsuleId` and\n * a short random suffix (or {@link opts.now} when provided for tests).\n *\n * Files without a frontmatter block, or without an `id:` key inside one,\n * are returned unchanged with `rewrote: false`. This keeps non-memory\n * artifacts (READMEs, transcripts) byte-identical to the source.\n */\nfunction rewriteFrontmatterIdForFork(\n content: string,\n capsuleId: string,\n now: number | undefined,\n): { content: string; rewrote: boolean } {\n // Detect the dominant line ending in the file so we can preserve it when\n // rebuilding the frontmatter delimiters. Splitting/joining on `\\n` silently\n // drops `\\r` from CRLF files, changing the byte content (Cursor thread #741\n // line 467). We detect the ending of the very first line (the opening `---`)\n // and use that as the canonical separator for the reconstructed delimiters.\n // All three cases are covered: CRLF (\\r\\n), LF (\\n), and bare CR (\\r).\n const crlfMatch = /^---(\\r\\n|\\r|\\n)/.exec(content);\n // Fall back to LF if the file starts with `---` but has no newline (EOF).\n const eol = crlfMatch ? crlfMatch[1] : \"\\n\";\n\n // Frontmatter block detection: `---` on its own line at the very start,\n // followed by content, followed by `---` on its own line. The closing\n // delimiter must be at column 0. We capture the trailing terminator\n // (`\\r?\\n` or end-of-string) as a separate group so we can re-emit it\n // verbatim, preserving CRLF/LF/EOF shape from the original file.\n const fmMatch = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(\\r?\\n|$)/.exec(content);\n if (!fmMatch) return { content, rewrote: false };\n const fmBody = fmMatch[1];\n const fmTrailer = fmMatch[2];\n const fmStart = fmMatch.index;\n const fmEnd = fmStart + fmMatch[0].length;\n\n // Find a top-level `id:` line. Top-level means the line begins at\n // column 0 inside the frontmatter body (no leading whitespace) and the\n // key is exactly `id`. We deliberately accept `id:` and `id : ` shapes\n // but reject `nested_id:` to avoid touching unrelated keys.\n const idLineRe = /^id:[ \\t]*([^\\r\\n]*)$/m;\n const idMatch = idLineRe.exec(fmBody);\n if (!idMatch) return { content, rewrote: false };\n\n const newId = mintForkId(capsuleId, idMatch[1]?.trim() ?? \"\", now);\n const replacedBody = fmBody.replace(idLineRe, `id: ${newId}`);\n // Reconstruct the file: original prefix (always empty here, fmStart === 0\n // by the `^` anchor) + frontmatter delimiters around the rewritten body.\n // Use the detected `eol` for the opening/closing `---` lines so that CRLF\n // files remain CRLF and LF files remain LF, preserving byte-content fidelity\n // (Cursor thread #741 line 467). The captured `fmTrailer` (the newline\n // immediately after the closing `---`, or end-of-string) is re-emitted\n // verbatim so the byte immediately after the closing fence is unchanged.\n const prefix = content.slice(0, fmStart);\n const tail = content.slice(fmEnd);\n const rebuilt = `${prefix}---${eol}${replacedBody}${eol}---${fmTrailer}${tail}`;\n return { content: rebuilt, rewrote: true };\n}\n\n/**\n * Derive a deterministic-when-`now`-is-set fork id from the capsule id and\n * the original record id. Production callers omit `now` and get a UUID\n * suffix; tests pass `now` for byte-stable assertions.\n */\nfunction mintForkId(capsuleId: string, originalId: string, now: number | undefined): string {\n const base = originalId.length > 0 ? originalId : \"fork\";\n if (now === undefined) {\n const suffix = randomUUID().slice(0, 8);\n return `${base}-fork-${capsuleId}-${suffix}`;\n }\n // Deterministic suffix: short hash of (capsuleId, originalId, now). Using\n // sha256 over a sorted-key serialization (gotcha #38) so the suffix does\n // not depend on Object.entries order.\n const payload = JSON.stringify({ c: capsuleId, i: originalId, n: now });\n const suffix = createHash(\"sha256\").update(payload, \"utf-8\").digest(\"hex\").slice(0, 8);\n return `${base}-fork-${capsuleId}-${suffix}`;\n}\n\n// Note: `CapsuleBlock` is re-exported here purely so callers that want to\n// inspect the manifest block don't have to deep-import from `./types.js`.\nexport type { CapsuleBlock };\n"],"mappings":"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","names":["suffix"]}