@remnic/core 9.65.3 → 9.65.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 (429) hide show
  1. package/dist/access-admin-ops-surface.d.ts +11 -11
  2. package/dist/access-admin-ops-surface.js +20 -18
  3. package/dist/access-authorization-probe.d.ts +11 -11
  4. package/dist/access-authorization-probe.js +21 -19
  5. package/dist/access-boundary.d.ts +11 -11
  6. package/dist/access-boundary.js +20 -18
  7. package/dist/access-cli.js +55 -53
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-coding-context-resolution.d.ts +1 -1
  10. package/dist/access-extraction-force-flush.d.ts +11 -11
  11. package/dist/access-extraction-force-flush.js +20 -18
  12. package/dist/access-health-types.d.ts +1 -1
  13. package/dist/access-http-lcm-compaction.d.ts +11 -11
  14. package/dist/access-http-lifecycle-flush.d.ts +11 -11
  15. package/dist/access-http-offline-stream.d.ts +11 -11
  16. package/dist/access-http-query.d.ts +2 -2
  17. package/dist/access-http-query.js +22 -20
  18. package/dist/access-http.d.ts +12 -12
  19. package/dist/access-http.js +31 -29
  20. package/dist/access-identity-continuity-surface.d.ts +9 -9
  21. package/dist/access-identity-continuity-surface.js +20 -18
  22. package/dist/access-lcm-surface.d.ts +11 -11
  23. package/dist/access-lcm-surface.js +20 -18
  24. package/dist/access-mcp.d.ts +11 -11
  25. package/dist/access-mcp.js +25 -23
  26. package/dist/access-memory-search-fanout.d.ts +2 -2
  27. package/dist/{access-namespace-preflight-B7Usyz-K.d.ts → access-namespace-preflight-D_6mo8ms.d.ts} +1 -1
  28. package/dist/access-namespace-preflight.d.ts +3 -3
  29. package/dist/access-namespace-preflight.js +20 -18
  30. package/dist/access-observe-write-surface.d.ts +11 -11
  31. package/dist/access-observe-write-surface.js +20 -18
  32. package/dist/access-offline-manifest.d.ts +9 -9
  33. package/dist/access-offline-manifest.js +3 -3
  34. package/dist/access-operations-batch.js +21 -19
  35. package/dist/access-operations.d.ts +11 -11
  36. package/dist/access-operations.js +24 -22
  37. package/dist/access-recall-concurrency.d.ts +11 -11
  38. package/dist/access-recall-concurrency.js +20 -18
  39. package/dist/access-recall-response.d.ts +11 -11
  40. package/dist/access-recall-response.js +20 -18
  41. package/dist/access-recall-surface.d.ts +11 -11
  42. package/dist/access-recall-surface.js +20 -18
  43. package/dist/{access-service-DkaIG3n8.d.ts → access-service-DqPa3hy4.d.ts} +7 -7
  44. package/dist/access-service-helpers.d.ts +11 -11
  45. package/dist/access-service.d.ts +11 -11
  46. package/dist/access-service.js +20 -18
  47. package/dist/access-surface-catalog.d.ts +11 -11
  48. package/dist/access-surface-catalog.js +22 -20
  49. package/dist/access-surface-catalog.js.map +1 -1
  50. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  51. package/dist/action-confidence.d.ts +1 -1
  52. package/dist/active-memory-bridge.d.ts +1 -1
  53. package/dist/active-recall.d.ts +1 -1
  54. package/dist/active-recall.js +5 -1
  55. package/dist/active-recall.js.map +1 -1
  56. package/dist/ambient-provenance.d.ts +1 -1
  57. package/dist/artifact-search.d.ts +1 -1
  58. package/dist/{auto-sync-H64ZCNIR.js → auto-sync-XVMLL2RH.js} +5 -5
  59. package/dist/behavior-learner.d.ts +1 -1
  60. package/dist/behavior-signals.d.ts +1 -1
  61. package/dist/bootstrap.d.ts +9 -9
  62. package/dist/briefing-window.d.ts +2 -2
  63. package/dist/briefing.d.ts +2 -2
  64. package/dist/briefing.js +4 -4
  65. package/dist/buffer-surprise-report.d.ts +1 -1
  66. package/dist/buffer-turn-helpers.d.ts +1 -1
  67. package/dist/buffer.d.ts +2 -2
  68. package/dist/bulk-import/index.d.ts +3 -3
  69. package/dist/calibration.d.ts +1 -1
  70. package/dist/capabilities.d.ts +1 -1
  71. package/dist/{catalog-CX2BZEJh.d.ts → catalog-DO_7zfkv.d.ts} +1 -1
  72. package/dist/causal-behavior.d.ts +1 -1
  73. package/dist/causal-consolidation.d.ts +1 -1
  74. package/dist/causal-consolidation.js +9 -6
  75. package/dist/causal-consolidation.js.map +1 -1
  76. package/dist/causal-trajectory-graph.d.ts +1 -1
  77. package/dist/chunk-236PXYG7.js +232 -0
  78. package/dist/chunk-236PXYG7.js.map +1 -0
  79. package/dist/{chunk-IQSC3V2E.js → chunk-2IXQSGCU.js} +2 -2
  80. package/dist/{chunk-GUXS322H.js → chunk-2MVTAIL3.js} +2 -2
  81. package/dist/{chunk-7QOO4LGC.js → chunk-3JZBPTLD.js} +331 -73
  82. package/dist/chunk-3JZBPTLD.js.map +1 -0
  83. package/dist/{chunk-TDKSCV62.js → chunk-3NH3VP3U.js} +2 -2
  84. package/dist/{chunk-URHCUXKI.js → chunk-AXGFOFNY.js} +2 -2
  85. package/dist/{chunk-WN5FSGLE.js → chunk-AZLLH6YA.js} +19 -19
  86. package/dist/{chunk-D27J626P.js → chunk-BK5G2MHO.js} +2 -2
  87. package/dist/{chunk-DHTQP334.js → chunk-CQBIN77D.js} +2 -2
  88. package/dist/{chunk-6VALIZKG.js → chunk-DORFF5N5.js} +2 -2
  89. package/dist/{chunk-76PL4XLG.js → chunk-DPNXADEI.js} +7 -2
  90. package/dist/chunk-DPNXADEI.js.map +1 -0
  91. package/dist/{chunk-MRTUN665.js → chunk-DUEDOYZD.js} +2 -2
  92. package/dist/{chunk-QFMO32TO.js → chunk-DUZARALI.js} +3 -3
  93. package/dist/{chunk-7KFIMGMP.js → chunk-ECLJ4LA2.js} +2 -2
  94. package/dist/{chunk-PDVQ5JU3.js → chunk-GHD2LLG4.js} +2 -2
  95. package/dist/{chunk-RK4WXUI2.js → chunk-HF5BUTI6.js} +2 -2
  96. package/dist/{chunk-3DVBKTSO.js → chunk-HSZMX3ZJ.js} +4 -4
  97. package/dist/{chunk-EV3YFLCB.js → chunk-I23ZHYSL.js} +2 -2
  98. package/dist/{chunk-ODZG724P.js → chunk-I2HTKOOP.js} +2 -2
  99. package/dist/{chunk-AYYGRVRP.js → chunk-IURMJBGP.js} +2 -2
  100. package/dist/{chunk-TYUS5TLG.js → chunk-IWKGOCL6.js} +3 -3
  101. package/dist/{chunk-4XDGYCZI.js → chunk-J3ATBB7M.js} +2 -2
  102. package/dist/{chunk-E4MVQQVX.js → chunk-JPAKBNTM.js} +2 -2
  103. package/dist/{chunk-NZWEEALH.js → chunk-KFIOPZ25.js} +2 -2
  104. package/dist/{chunk-EIVIB73I.js → chunk-KOO37E4Y.js} +2 -2
  105. package/dist/{chunk-57HVIG5U.js → chunk-KPB2QHJZ.js} +2 -2
  106. package/dist/{chunk-NXKJHSDN.js → chunk-LBFFNDFM.js} +1 -1
  107. package/dist/chunk-LBFFNDFM.js.map +1 -0
  108. package/dist/{chunk-Z4RPXHJH.js → chunk-LYBIHM4N.js} +4 -4
  109. package/dist/{chunk-G57LQRP6.js → chunk-MFOSZUIT.js} +2 -2
  110. package/dist/{chunk-AUO2LJ3K.js → chunk-NLEH4RTB.js} +2 -2
  111. package/dist/{chunk-AYDDB3XI.js → chunk-OCCHUEZB.js} +2 -2
  112. package/dist/{chunk-6TAX66IA.js → chunk-OU34IQXV.js} +2 -2
  113. package/dist/{chunk-OJD4WS2E.js → chunk-OZDFB22S.js} +2 -2
  114. package/dist/{chunk-GN55KU2P.js → chunk-PTZQYCJ4.js} +2 -2
  115. package/dist/{chunk-BQUEAVNW.js → chunk-QJW4MZSW.js} +2 -2
  116. package/dist/{chunk-2VXWFYJE.js → chunk-R2MZCYA6.js} +3 -3
  117. package/dist/{chunk-VA5KMSAE.js → chunk-SR5LNI6Q.js} +3 -3
  118. package/dist/{chunk-LBWQ65SX.js → chunk-T42L3S6A.js} +4 -4
  119. package/dist/{chunk-PAYGC3BD.js → chunk-THVI4Y7X.js} +7 -7
  120. package/dist/{chunk-S7Q5CM7E.js → chunk-TJFI3K2O.js} +15 -3
  121. package/dist/chunk-TJFI3K2O.js.map +1 -0
  122. package/dist/{chunk-R47WINCY.js → chunk-UCKG36HO.js} +2 -2
  123. package/dist/{chunk-QXIQPCCN.js → chunk-UFR6HX7A.js} +1 -1
  124. package/dist/{chunk-CDUUDBWJ.js → chunk-VFYRYFLL.js} +2 -2
  125. package/dist/{chunk-P4DHENRE.js → chunk-WDXD6ITO.js} +2 -2
  126. package/dist/{chunk-BKYPJUQL.js → chunk-XGM3VQWG.js} +10 -10
  127. package/dist/{chunk-MBDJJTAT.js → chunk-YDKF4MSJ.js} +3 -3
  128. package/dist/{chunk-YF6UHWOV.js → chunk-YHGCT462.js} +3 -3
  129. package/dist/{chunk-AZAA5DNP.js → chunk-YOXVZJFM.js} +2 -2
  130. package/dist/{chunk-OLWTCWQ2.js → chunk-YZJM6CLF.js} +8 -8
  131. package/dist/chunk-ZCUTSMR2.js +61 -0
  132. package/dist/chunk-ZCUTSMR2.js.map +1 -0
  133. package/dist/{chunk-LUNJ3WFO.js → chunk-ZK4BFABK.js} +4 -4
  134. package/dist/{chunk-H5FW4S5Q.js → chunk-ZPOHEB5Z.js} +31 -31
  135. package/dist/{chunk-RDFF2NAU.js → chunk-ZXBTTMGZ.js} +184 -88
  136. package/dist/chunk-ZXBTTMGZ.js.map +1 -0
  137. package/dist/{cli-ChKeYMZv.d.ts → cli-DO3i14pM.d.ts} +5 -5
  138. package/dist/cli.d.ts +13 -13
  139. package/dist/cli.js +44 -42
  140. package/dist/coding/pre-action-gate.d.ts +1 -1
  141. package/dist/compounding/engine.d.ts +2 -2
  142. package/dist/compounding/engine.js +4 -4
  143. package/dist/compounding/preference-consolidator.d.ts +1 -1
  144. package/dist/compression-optimizer.d.ts +1 -1
  145. package/dist/config.d.ts +1 -1
  146. package/dist/config.js +5 -1
  147. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  148. package/dist/connectors/codex-materialize-runner.js +8 -5
  149. package/dist/connectors/codex-materialize.d.ts +14 -1
  150. package/dist/connectors/codex-materialize.js +10 -1
  151. package/dist/connectors/index.d.ts +1 -1
  152. package/dist/connectors/index.js +8 -5
  153. package/dist/consolidation-provenance-check.d.ts +2 -2
  154. package/dist/consolidation-undo.d.ts +2 -2
  155. package/dist/contradiction/index.d.ts +3 -3
  156. package/dist/contradiction/index.js +1 -1
  157. package/dist/converge-config.d.ts +1 -1
  158. package/dist/convergence-refresh.d.ts +2 -2
  159. package/dist/convergence-refresh.js +6 -6
  160. package/dist/conversation-index/backend.d.ts +1 -1
  161. package/dist/conversation-index/chunker.d.ts +1 -1
  162. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  163. package/dist/conversation-index/indexer.d.ts +1 -1
  164. package/dist/conversation-index/search.d.ts +1 -1
  165. package/dist/corpus-watermark.d.ts +1 -1
  166. package/dist/corpus-watermark.js +6 -6
  167. package/dist/cross-lingual-recall.d.ts +1 -1
  168. package/dist/day-summary.d.ts +1 -1
  169. package/dist/delinearize.d.ts +1 -1
  170. package/dist/dependency-propagation-config.d.ts +1 -1
  171. package/dist/{dependency-propagation-delivery-B8ZMBBCY.d.ts → dependency-propagation-delivery-D23_x-s5.d.ts} +1 -1
  172. package/dist/direct-answer-wiring.d.ts +1 -1
  173. package/dist/direct-answer.d.ts +1 -1
  174. package/dist/embedding-fallback.d.ts +1 -1
  175. package/dist/enrichment/index.d.ts +1 -1
  176. package/dist/entity-origin-fields.d.ts +1 -1
  177. package/dist/entity-retrieval.d.ts +2 -2
  178. package/dist/entity-retrieval.js +4 -4
  179. package/dist/entity-schema.d.ts +1 -1
  180. package/dist/episodic-context.d.ts +1 -1
  181. package/dist/explicit-capture.d.ts +9 -9
  182. package/dist/explicit-capture.js +1 -1
  183. package/dist/external-wiki-access.d.ts +11 -11
  184. package/dist/external-wiki-access.js +21 -19
  185. package/dist/external-wiki-collection-registration.d.ts +1 -1
  186. package/dist/external-wiki-collection.d.ts +1 -1
  187. package/dist/external-wiki-mcp-tools.d.ts +11 -11
  188. package/dist/extraction-error-classification.d.ts +1 -1
  189. package/dist/extraction-faithfulness.d.ts +1 -1
  190. package/dist/extraction-judge-telemetry.d.ts +1 -1
  191. package/dist/extraction-judge-training.d.ts +1 -1
  192. package/dist/extraction-judge.d.ts +1 -1
  193. package/dist/extraction-judge.js +2 -2
  194. package/dist/extraction-liveness.d.ts +1 -1
  195. package/dist/extraction-normalization.d.ts +1 -1
  196. package/dist/extraction-prompt.d.ts +1 -1
  197. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  198. package/dist/extraction-source-grounding.d.ts +1 -1
  199. package/dist/extraction.d.ts +1 -1
  200. package/dist/extraction.js +9 -7
  201. package/dist/fallback-llm.d.ts +1 -1
  202. package/dist/graph-dashboard-diff.d.ts +1 -1
  203. package/dist/graph-dashboard-key.d.ts +1 -1
  204. package/dist/graph-dashboard-parser.d.ts +1 -1
  205. package/dist/graph-edge-reinforcement.d.ts +1 -1
  206. package/dist/graph-path-reconstruction.d.ts +1 -1
  207. package/dist/graph-path-scoring.d.ts +1 -1
  208. package/dist/graph-snapshot.d.ts +1 -1
  209. package/dist/graph.d.ts +1 -1
  210. package/dist/harmonic-retrieval.js +1 -1
  211. package/dist/importance.d.ts +1 -1
  212. package/dist/importers/index.d.ts +1 -1
  213. package/dist/in-flight-reads.d.ts +1 -1
  214. package/dist/{index-CexDrJzC.d.ts → index--QhGIXF2.d.ts} +2 -2
  215. package/dist/index.d.ts +87 -40
  216. package/dist/index.js +79 -58
  217. package/dist/intent.d.ts +1 -1
  218. package/dist/lcm/engine.d.ts +1 -1
  219. package/dist/lcm/index.d.ts +1 -1
  220. package/dist/lcm/tools.d.ts +1 -1
  221. package/dist/lifecycle.d.ts +1 -1
  222. package/dist/live-connectors-runner.d.ts +1 -1
  223. package/dist/local-llm.d.ts +1 -1
  224. package/dist/local-model-endpoint.d.ts +1 -1
  225. package/dist/maintenance/memory-governance.d.ts +1 -1
  226. package/dist/maintenance/memory-governance.js +4 -4
  227. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  228. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
  229. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  230. package/dist/maintenance/rebuild-memory-projection.js +5 -5
  231. package/dist/{maintenance-nkdtSrVr.d.ts → maintenance-PFvY1Pw_.d.ts} +3 -3
  232. package/dist/mcp-memory-inspector-app.d.ts +11 -11
  233. package/dist/memory-action-policy.d.ts +1 -1
  234. package/dist/memory-cache.d.ts +1 -1
  235. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  236. package/dist/memory-projection-store.d.ts +1 -1
  237. package/dist/memory-provenance.d.ts +1 -1
  238. package/dist/memory-snapshot.d.ts +1 -1
  239. package/dist/memory-subject-cli.d.ts +11 -11
  240. package/dist/memory-subject.d.ts +11 -11
  241. package/dist/memory-worth-outcomes.d.ts +2 -2
  242. package/dist/models-json.d.ts +1 -1
  243. package/dist/namespaces/migrate.d.ts +3 -3
  244. package/dist/namespaces/migrate.js +5 -5
  245. package/dist/namespaces/principal.d.ts +1 -1
  246. package/dist/namespaces/search.d.ts +1 -1
  247. package/dist/namespaces/storage.d.ts +3 -3
  248. package/dist/namespaces/storage.js +4 -4
  249. package/dist/native-knowledge.d.ts +1 -1
  250. package/dist/offline-sync-impression-drain.d.ts +1 -1
  251. package/dist/operator-doctor-corpus.d.ts +1 -1
  252. package/dist/operator-doctor-corpus.js +7 -7
  253. package/dist/operator-doctor-replica.d.ts +1 -1
  254. package/dist/operator-toolkit.d.ts +3 -3
  255. package/dist/operator-toolkit.js +16 -13
  256. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  257. package/dist/orchestration/maintenance.d.ts +4 -4
  258. package/dist/orchestration/maintenance.js +10 -10
  259. package/dist/{orchestrator-Bvq1-RQs.d.ts → orchestrator-4wzJrPVm.d.ts} +8 -8
  260. package/dist/orchestrator.d.ts +9 -9
  261. package/dist/orchestrator.js +55 -53
  262. package/dist/patterns-cli.d.ts +1 -1
  263. package/dist/{pipeline-C8vfIiTX.d.ts → pipeline-CgXgdIrF.d.ts} +1 -1
  264. package/dist/policy-runtime.d.ts +1 -1
  265. package/dist/proactive-contention.d.ts +1 -1
  266. package/dist/provenance.d.ts +1 -1
  267. package/dist/{public-http-Cy0aYbys.d.ts → public-http-DhIXYBYI.d.ts} +1 -1
  268. package/dist/{qmd-B9p1SZTZ.d.ts → qmd-BppGGxsi.d.ts} +1 -1
  269. package/dist/qmd-preflight.d.ts +2 -2
  270. package/dist/qmd-recall-cache.d.ts +1 -1
  271. package/dist/qmd.d.ts +2 -2
  272. package/dist/recall-concurrency-config.d.ts +1 -1
  273. package/dist/recall-disclosure-escalation.d.ts +1 -1
  274. package/dist/recall-explain-renderer.d.ts +1 -1
  275. package/dist/recall-explain-renderer.js +3 -3
  276. package/dist/recall-memory-map.d.ts +1 -1
  277. package/dist/recall-planner-llm.d.ts +1 -1
  278. package/dist/recall-state.d.ts +1 -1
  279. package/dist/recall-tag-filter.d.ts +1 -1
  280. package/dist/recall-timings.d.ts +1 -1
  281. package/dist/recall-xray-cli.d.ts +1 -1
  282. package/dist/recall-xray-cli.js +4 -4
  283. package/dist/recall-xray-renderer.d.ts +1 -1
  284. package/dist/recall-xray-renderer.js +3 -3
  285. package/dist/recall-xray.d.ts +1 -1
  286. package/dist/recall-xray.js +2 -2
  287. package/dist/reconcile/cursor.d.ts +1 -1
  288. package/dist/reconcile/manifest.d.ts +1 -1
  289. package/dist/reconcile/manifest.js +2 -2
  290. package/dist/reconcile/plan.d.ts +1 -1
  291. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  292. package/dist/replay/normalizers/claude.d.ts +1 -1
  293. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  294. package/dist/replay/normalizers/shared.d.ts +1 -1
  295. package/dist/replay/runner.d.ts +1 -1
  296. package/dist/replay/types.d.ts +1 -1
  297. package/dist/replica-divergence.d.ts +1 -1
  298. package/dist/replica-peers-config.d.ts +1 -1
  299. package/dist/resolution-4552BNRV.js +1 -1
  300. package/dist/resolve-auth-token.d.ts +1 -1
  301. package/dist/resume-bundles.js +6 -2
  302. package/dist/retrieval-agents.d.ts +2 -2
  303. package/dist/retrieval-tiers.d.ts +1 -1
  304. package/dist/routing/engine.d.ts +1 -1
  305. package/dist/routing/store.d.ts +1 -1
  306. package/dist/salvage-envelope.d.ts +1 -1
  307. package/dist/salvage-envelope.js +1 -1
  308. package/dist/schemas.d.ts +12 -12
  309. package/dist/{scope-profiles-DLrRxFWR.d.ts → scope-profiles-ChZUtxDU.d.ts} +1 -1
  310. package/dist/search/embed-helper.d.ts +1 -1
  311. package/dist/search/factory.d.ts +1 -1
  312. package/dist/search/index.d.ts +1 -1
  313. package/dist/search/lancedb-backend.d.ts +1 -1
  314. package/dist/search/meilisearch-backend.d.ts +1 -1
  315. package/dist/search/noop-backend.d.ts +1 -1
  316. package/dist/search/orama-backend.d.ts +1 -1
  317. package/dist/search/port.d.ts +1 -1
  318. package/dist/search/remote-backend.d.ts +1 -1
  319. package/dist/{semantic-consolidation-DlN6xe20.d.ts → semantic-consolidation-mSPwQSmF.d.ts} +1 -1
  320. package/dist/semantic-consolidation.d.ts +2 -2
  321. package/dist/semantic-consolidation.js +9 -6
  322. package/dist/semantic-rule-promotion.js +4 -4
  323. package/dist/semantic-rule-verifier.d.ts +1 -1
  324. package/dist/semantic-rule-verifier.js +4 -4
  325. package/dist/{service-GkYyVfm9.d.ts → service-BICWLwSP.d.ts} +2 -2
  326. package/dist/session-observer-bands.d.ts +1 -1
  327. package/dist/session-observer-state.d.ts +1 -1
  328. package/dist/shared-context/manager.d.ts +1 -1
  329. package/dist/signal.d.ts +1 -1
  330. package/dist/skills-registry.js +4 -56
  331. package/dist/skills-registry.js.map +1 -1
  332. package/dist/source-agent-qualifier.d.ts +1 -1
  333. package/dist/source-agent-qualifier.js +8 -5
  334. package/dist/{storage-BIDzuKIn.d.ts → storage-B7urx1K3.d.ts} +1 -1
  335. package/dist/storage.d.ts +2 -2
  336. package/dist/storage.js +6 -6
  337. package/dist/subject-config.d.ts +1 -1
  338. package/dist/summarizer.d.ts +1 -1
  339. package/dist/summary-snapshot.d.ts +1 -1
  340. package/dist/support-passport/index.d.ts +14 -14
  341. package/dist/support-passport/index.js +23 -21
  342. package/dist/temporal-supersession.d.ts +2 -2
  343. package/dist/temporal-timeline-recall.d.ts +1 -1
  344. package/dist/temporal-validity.d.ts +1 -1
  345. package/dist/threading.d.ts +1 -1
  346. package/dist/tier-migration.d.ts +2 -2
  347. package/dist/tier-routing.d.ts +1 -1
  348. package/dist/topics.d.ts +1 -1
  349. package/dist/transcript.d.ts +1 -1
  350. package/dist/trust-score-stage.d.ts +1 -1
  351. package/dist/trust-score.d.ts +1 -1
  352. package/dist/{types-BxAcOl5Q.d.ts → types-ETU5N6nB.d.ts} +111 -4
  353. package/dist/types.d.ts +1 -1
  354. package/dist/types.js +1 -1
  355. package/dist/utility-runtime.d.ts +1 -1
  356. package/dist/verified-recall.js +4 -4
  357. package/dist/who-knows-cli.d.ts +11 -11
  358. package/dist/who-knows-cli.js +5 -5
  359. package/dist/who-knows.d.ts +11 -11
  360. package/dist/who-knows.js +4 -4
  361. package/dist/write-envelope.d.ts +1 -1
  362. package/dist/write-envelope.js +1 -1
  363. package/package.json +2 -2
  364. package/src/cli/skills-commands.ts +150 -0
  365. package/src/cli.ts +4 -2
  366. package/src/config.ts +4 -2
  367. package/src/connectors/codex-materialize-runner.ts +16 -0
  368. package/src/connectors/codex-materialize-skills.test.ts +141 -0
  369. package/src/connectors/codex-materialize.ts +58 -40
  370. package/src/connectors/materialize-paths.ts +50 -0
  371. package/src/connectors/materialize-skills.ts +127 -0
  372. package/src/index.ts +5 -10
  373. package/src/procedural/skill-io.test.ts +190 -0
  374. package/src/procedural/skill-io.ts +220 -0
  375. package/src/procedural/skill-projection.test.ts +214 -0
  376. package/src/procedural/skill-projection.ts +356 -0
  377. package/src/types.ts +2 -3
  378. package/dist/chunk-76PL4XLG.js.map +0 -1
  379. package/dist/chunk-7QOO4LGC.js.map +0 -1
  380. package/dist/chunk-NXKJHSDN.js.map +0 -1
  381. package/dist/chunk-RDFF2NAU.js.map +0 -1
  382. package/dist/chunk-S7Q5CM7E.js.map +0 -1
  383. /package/dist/{auto-sync-H64ZCNIR.js.map → auto-sync-XVMLL2RH.js.map} +0 -0
  384. /package/dist/{chunk-IQSC3V2E.js.map → chunk-2IXQSGCU.js.map} +0 -0
  385. /package/dist/{chunk-GUXS322H.js.map → chunk-2MVTAIL3.js.map} +0 -0
  386. /package/dist/{chunk-TDKSCV62.js.map → chunk-3NH3VP3U.js.map} +0 -0
  387. /package/dist/{chunk-URHCUXKI.js.map → chunk-AXGFOFNY.js.map} +0 -0
  388. /package/dist/{chunk-WN5FSGLE.js.map → chunk-AZLLH6YA.js.map} +0 -0
  389. /package/dist/{chunk-D27J626P.js.map → chunk-BK5G2MHO.js.map} +0 -0
  390. /package/dist/{chunk-DHTQP334.js.map → chunk-CQBIN77D.js.map} +0 -0
  391. /package/dist/{chunk-6VALIZKG.js.map → chunk-DORFF5N5.js.map} +0 -0
  392. /package/dist/{chunk-MRTUN665.js.map → chunk-DUEDOYZD.js.map} +0 -0
  393. /package/dist/{chunk-QFMO32TO.js.map → chunk-DUZARALI.js.map} +0 -0
  394. /package/dist/{chunk-7KFIMGMP.js.map → chunk-ECLJ4LA2.js.map} +0 -0
  395. /package/dist/{chunk-PDVQ5JU3.js.map → chunk-GHD2LLG4.js.map} +0 -0
  396. /package/dist/{chunk-RK4WXUI2.js.map → chunk-HF5BUTI6.js.map} +0 -0
  397. /package/dist/{chunk-3DVBKTSO.js.map → chunk-HSZMX3ZJ.js.map} +0 -0
  398. /package/dist/{chunk-EV3YFLCB.js.map → chunk-I23ZHYSL.js.map} +0 -0
  399. /package/dist/{chunk-ODZG724P.js.map → chunk-I2HTKOOP.js.map} +0 -0
  400. /package/dist/{chunk-AYYGRVRP.js.map → chunk-IURMJBGP.js.map} +0 -0
  401. /package/dist/{chunk-TYUS5TLG.js.map → chunk-IWKGOCL6.js.map} +0 -0
  402. /package/dist/{chunk-4XDGYCZI.js.map → chunk-J3ATBB7M.js.map} +0 -0
  403. /package/dist/{chunk-E4MVQQVX.js.map → chunk-JPAKBNTM.js.map} +0 -0
  404. /package/dist/{chunk-NZWEEALH.js.map → chunk-KFIOPZ25.js.map} +0 -0
  405. /package/dist/{chunk-EIVIB73I.js.map → chunk-KOO37E4Y.js.map} +0 -0
  406. /package/dist/{chunk-57HVIG5U.js.map → chunk-KPB2QHJZ.js.map} +0 -0
  407. /package/dist/{chunk-Z4RPXHJH.js.map → chunk-LYBIHM4N.js.map} +0 -0
  408. /package/dist/{chunk-G57LQRP6.js.map → chunk-MFOSZUIT.js.map} +0 -0
  409. /package/dist/{chunk-AUO2LJ3K.js.map → chunk-NLEH4RTB.js.map} +0 -0
  410. /package/dist/{chunk-AYDDB3XI.js.map → chunk-OCCHUEZB.js.map} +0 -0
  411. /package/dist/{chunk-6TAX66IA.js.map → chunk-OU34IQXV.js.map} +0 -0
  412. /package/dist/{chunk-OJD4WS2E.js.map → chunk-OZDFB22S.js.map} +0 -0
  413. /package/dist/{chunk-GN55KU2P.js.map → chunk-PTZQYCJ4.js.map} +0 -0
  414. /package/dist/{chunk-BQUEAVNW.js.map → chunk-QJW4MZSW.js.map} +0 -0
  415. /package/dist/{chunk-2VXWFYJE.js.map → chunk-R2MZCYA6.js.map} +0 -0
  416. /package/dist/{chunk-VA5KMSAE.js.map → chunk-SR5LNI6Q.js.map} +0 -0
  417. /package/dist/{chunk-LBWQ65SX.js.map → chunk-T42L3S6A.js.map} +0 -0
  418. /package/dist/{chunk-PAYGC3BD.js.map → chunk-THVI4Y7X.js.map} +0 -0
  419. /package/dist/{chunk-R47WINCY.js.map → chunk-UCKG36HO.js.map} +0 -0
  420. /package/dist/{chunk-QXIQPCCN.js.map → chunk-UFR6HX7A.js.map} +0 -0
  421. /package/dist/{chunk-CDUUDBWJ.js.map → chunk-VFYRYFLL.js.map} +0 -0
  422. /package/dist/{chunk-P4DHENRE.js.map → chunk-WDXD6ITO.js.map} +0 -0
  423. /package/dist/{chunk-BKYPJUQL.js.map → chunk-XGM3VQWG.js.map} +0 -0
  424. /package/dist/{chunk-MBDJJTAT.js.map → chunk-YDKF4MSJ.js.map} +0 -0
  425. /package/dist/{chunk-YF6UHWOV.js.map → chunk-YHGCT462.js.map} +0 -0
  426. /package/dist/{chunk-AZAA5DNP.js.map → chunk-YOXVZJFM.js.map} +0 -0
  427. /package/dist/{chunk-OLWTCWQ2.js.map → chunk-YZJM6CLF.js.map} +0 -0
  428. /package/dist/{chunk-LUNJ3WFO.js.map → chunk-ZK4BFABK.js.map} +0 -0
  429. /package/dist/{chunk-H5FW4S5Q.js.map → chunk-ZPOHEB5Z.js.map} +0 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * materialize-skills.ts — the `skills/` section of the Codex materializer
3
+ * (issue #2369).
4
+ *
5
+ * Extracted from codex-materialize.ts (which sits at its structural LOC cap)
6
+ * so the skill staging / commit / prune rules live in one small module. Format
7
+ * and slug rules stay in the host-agnostic `procedural/skill-projection.ts`;
8
+ * this module only decides what lands on disk.
9
+ *
10
+ * Ownership rule: Remnic removes ONLY the slugs a previous run recorded in the
11
+ * sentinel's `projected_skills`. A skill folder a user (or another tool)
12
+ * created is never in that list, so it is never a removal candidate.
13
+ */
14
+
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+
18
+ import { renderSkillBundle, SKILL_FILE_NAME, type SkillBundle } from "../procedural/skill-projection.js";
19
+ import { isValidSkillSlug } from "../skills-registry.js";
20
+ import { ensureSafeManagedSubdir } from "./materialize-paths.js";
21
+
22
+ /** Sub-directory for projected procedural skill bundles. */
23
+ export const SKILLS_SUBDIR = "skills";
24
+
25
+ export interface SkillFile {
26
+ slug: string;
27
+ body: string;
28
+ }
29
+
30
+ export interface SkillPlan {
31
+ /** False when the caller omitted `skills` — leave `skills/` untouched. */
32
+ supplied: boolean;
33
+ /** Bundles to write, deduplicated on slug. */
34
+ files: SkillFile[];
35
+ /** Previously projected slugs this run no longer projects. */
36
+ retiredSlugs: string[];
37
+ }
38
+
39
+ /**
40
+ * Decide what this run writes and removes. `undefined` bundles mean "not our
41
+ * business this run"; an empty array is authoritative and retires everything
42
+ * previously projected.
43
+ */
44
+ export function planSkillFiles(
45
+ bundles: SkillBundle[] | undefined,
46
+ previouslyProjectedSlugs: readonly string[],
47
+ ): SkillPlan {
48
+ const files: SkillFile[] = [];
49
+ const seen = new Set<string>();
50
+ for (const bundle of bundles ?? []) {
51
+ // Invalid or duplicate slugs are dropped so two bundles can never target
52
+ // one folder and leave the second rename with no source (§37).
53
+ if (!isValidSkillSlug(bundle.slug) || seen.has(bundle.slug)) continue;
54
+ seen.add(bundle.slug);
55
+ files.push({ slug: bundle.slug, body: renderSkillBundle(bundle) });
56
+ }
57
+ const previous = previouslyProjectedSlugs.filter((slug) => isValidSkillSlug(slug));
58
+ return {
59
+ supplied: bundles !== undefined,
60
+ files,
61
+ retiredSlugs: bundles === undefined ? [] : previous.filter((slug) => !seen.has(slug)),
62
+ };
63
+ }
64
+
65
+ /** Absolute path of a projected bundle's SKILL.md under `memoriesDir`. */
66
+ export function skillFilePath(memoriesDir: string, slug: string): string {
67
+ return path.join(memoriesDir, SKILLS_SUBDIR, slug, SKILL_FILE_NAME);
68
+ }
69
+
70
+ /** Relative path recorded in `MaterializeResult.filesWritten`. */
71
+ export function skillFileRelPath(slug: string): string {
72
+ return path.join(SKILLS_SUBDIR, slug, SKILL_FILE_NAME);
73
+ }
74
+
75
+ /**
76
+ * True when no retired folder is still on disk. Guards the idempotent
77
+ * early-return: a hash match is only a safe no-op if the removals also landed.
78
+ */
79
+ export function retiredSkillsAlreadyRemoved(memoriesDir: string, retiredSlugs: readonly string[]): boolean {
80
+ return retiredSlugs.every((slug) => !fs.existsSync(path.join(memoriesDir, SKILLS_SUBDIR, slug)));
81
+ }
82
+
83
+ /** Render bundles into the per-run staging directory. */
84
+ export function stageSkillFiles(tmpDir: string, files: readonly SkillFile[]): void {
85
+ for (const file of files) {
86
+ const stagedDir = path.join(tmpDir, SKILLS_SUBDIR, file.slug);
87
+ fs.mkdirSync(stagedDir, { recursive: true });
88
+ fs.writeFileSync(path.join(stagedDir, SKILL_FILE_NAME), file.body);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Remove retired folders, then rename each staged SKILL.md into place. Per-file
94
+ * renames (never directory renames) so an existing folder cannot cause
95
+ * ENOTEMPTY and so Codex never observes a half-written file.
96
+ */
97
+ export function commitSkillFiles(memoriesDir: string, tmpDir: string, plan: SkillPlan): void {
98
+ if (!plan.supplied) return;
99
+ if (plan.files.length === 0 && plan.retiredSlugs.length === 0) return;
100
+
101
+ const skillsDir = ensureSafeManagedSubdir(memoriesDir, path.join(memoriesDir, SKILLS_SUBDIR), SKILLS_SUBDIR);
102
+ for (const slug of plan.retiredSlugs) {
103
+ const retiredDir = path.join(skillsDir, slug);
104
+ try {
105
+ // Never follow a symlink out of the managed tree while pruning.
106
+ if (fs.lstatSync(retiredDir).isSymbolicLink()) continue;
107
+ } catch (err) {
108
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") continue;
109
+ throw err;
110
+ }
111
+ try {
112
+ fs.rmSync(retiredDir, { recursive: true, force: true });
113
+ } catch (err) {
114
+ throw new Error(
115
+ `codex-materialize: failed to remove retired skill ${slug}: ${err instanceof Error ? err.message : String(err)}`,
116
+ );
117
+ }
118
+ }
119
+ for (const file of plan.files) {
120
+ const destDir = path.join(skillsDir, file.slug);
121
+ fs.mkdirSync(destDir, { recursive: true });
122
+ fs.renameSync(
123
+ path.join(tmpDir, SKILLS_SUBDIR, file.slug, SKILL_FILE_NAME),
124
+ path.join(destDir, SKILL_FILE_NAME),
125
+ );
126
+ }
127
+ }
package/src/index.ts CHANGED
@@ -219,17 +219,12 @@ export {
219
219
  } from "./procedural/procedure-types.js";
220
220
 
221
221
  // Procedural stats surface (issue #567 PR 5/5).
222
- export {
223
- computeProcedureStats,
224
- formatProcedureStatsText,
225
- } from "./procedural/procedure-stats.js";
226
- export type {
227
- ProcedureStatsReport,
228
- ProcedureStatusCounts,
229
- ProcedureStatsConfigSnapshot,
230
- ProcedureStatsRecent,
231
- } from "./procedural/procedure-stats.js";
222
+ export { computeProcedureStats, formatProcedureStatsText } from "./procedural/procedure-stats.js";
223
+ export type { ProcedureStatsReport, ProcedureStatusCounts, ProcedureStatsConfigSnapshot, ProcedureStatsRecent } from "./procedural/procedure-stats.js";
232
224
  export { runProcedureLibraryMaintenance, type ProcedureLibraryMaintenanceReport, type ProcedureMaintenanceAction, type ProcedureMaintenanceActionKind, type ProcedureMaintenanceReasonCode, type ProcedureMaintenanceEvidence, type ProcedureMaintenanceMarker } from "./procedural/library-maintenance.js";
225
+ // Portable procedural skill bundles (issue #2369).
226
+ export { parseSkillProjectionConfig, projectProceduresToSkills, renderSkillBundle, parseSkillBundle, sanitizeSkillSlug, SKILL_FILE_NAME, type SkillBundle, type SkillProjectionConfig, type ParsedSkillBundle } from "./procedural/skill-projection.js";
227
+ export { exportSkillBundles, readSkillBundlesFromDir, persistImportedSkills, type ExportSkillsResult, type ImportSkillsResult, type ReadSkillBundle } from "./procedural/skill-io.js";
233
228
  export * from "./preferences/index.js";
234
229
  // ---------------------------------------------------------------------------
235
230
  // Direct-answer retrieval tier (issue #518)
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Skill bundle export / import I/O (issue #2369).
3
+ *
4
+ * Covers the acceptance matrix:
5
+ * - export → import into a FRESH memory dir reproduces the procedure steps
6
+ * with import provenance;
7
+ * - imported procedures land as `pending_review`, never active;
8
+ * - a bundle carrying `scripts/` imports the SKILL.md text only and flags
9
+ * `hasUnimportedResources`;
10
+ * - symlinked bundle dirs / SKILL.md files and escaping entries are skipped
11
+ * with a reason instead of being walked.
12
+ */
13
+
14
+ import test from "node:test";
15
+ import assert from "node:assert/strict";
16
+ import os from "node:os";
17
+ import path from "node:path";
18
+ import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
19
+
20
+ import { StorageManager } from "../storage.js";
21
+ import { stripAttributesSuffix } from "../structured-attributes.js";
22
+ import { buildProcedurePersistBody, parseProcedureStepsFromBody } from "./procedure-types.js";
23
+ import { exportSkillBundles, persistImportedSkills, readSkillBundlesFromDir } from "./skill-io.js";
24
+ import { projectProceduresToSkills, SKILL_FILE_NAME } from "./skill-projection.js";
25
+ import type { MemoryFile } from "../types.js";
26
+
27
+ const STEPS = [
28
+ { order: 1, intent: "Read the failing output." },
29
+ { order: 2, intent: "Fix the smallest cause.", expectedOutcome: "The suite passes." },
30
+ ];
31
+
32
+ function makeProcedure(id: string, title: string): MemoryFile {
33
+ return {
34
+ path: `procedures/2026-08-18/${id}.md`,
35
+ content: buildProcedurePersistBody(title, STEPS),
36
+ frontmatter: {
37
+ id,
38
+ category: "procedure",
39
+ created: "2026-08-18T00:00:00.000Z",
40
+ updated: "2026-08-18T00:00:00.000Z",
41
+ source: "procedure-miner",
42
+ confidence: 0.8,
43
+ confidenceTier: "implied",
44
+ tags: [],
45
+ } as MemoryFile["frontmatter"],
46
+ };
47
+ }
48
+
49
+ async function tempDir(prefix: string): Promise<string> {
50
+ return mkdtemp(path.join(os.tmpdir(), prefix));
51
+ }
52
+
53
+ test("export then import into a fresh memory dir reproduces the steps as pending_review", async () => {
54
+ const outDir = await tempDir("remnic-skill-export-");
55
+ const memoryDir = await tempDir("remnic-skill-memory-");
56
+ try {
57
+ const source = makeProcedure("procedure-1", "Fix a failing test");
58
+ const written = await exportSkillBundles({
59
+ bundles: projectProceduresToSkills([source]),
60
+ outDir,
61
+ });
62
+ assert.deepEqual(written.slugs, ["fix-a-failing-test"]);
63
+
64
+ const read = await readSkillBundlesFromDir(outDir);
65
+ assert.equal(read.bundles.length, 1);
66
+ assert.equal(read.bundles[0].hasUnimportedResources, false);
67
+
68
+ const storage = new StorageManager(memoryDir);
69
+ const result = await persistImportedSkills({ storage, bundles: read.bundles });
70
+ assert.equal(result.imported.length, 1);
71
+ assert.equal(result.imported[0].steps, 2);
72
+ assert.deepEqual(result.rejected, []);
73
+
74
+ const memories = await storage.readAllMemories();
75
+ const imported = memories.filter((m) => m.frontmatter.category === "procedure");
76
+ assert.equal(imported.length, 1);
77
+ assert.equal(imported[0].frontmatter.status, "pending_review");
78
+ assert.equal(imported[0].frontmatter.source, "skill-import");
79
+ assert.equal(imported[0].frontmatter.structuredAttributes?.skill_slug, "fix-a-failing-test");
80
+ assert.equal(imported[0].frontmatter.structuredAttributes?.skill_origin_memory_id, "procedure-1");
81
+ assert.equal(imported[0].frontmatter.structuredAttributes?.hasUnimportedResources, undefined);
82
+
83
+ // storage.writeMemory appends a "[Attributes: ...]" footer to any memory
84
+ // carrying structuredAttributes; it is machine provenance, not a step.
85
+ assert.deepEqual(
86
+ parseProcedureStepsFromBody(stripAttributesSuffix(imported[0].content)),
87
+ parseProcedureStepsFromBody(source.content),
88
+ );
89
+ } finally {
90
+ await rm(outDir, { recursive: true, force: true });
91
+ await rm(memoryDir, { recursive: true, force: true });
92
+ }
93
+ });
94
+
95
+ test("a bundle with scripts/ imports text only and flags hasUnimportedResources", async () => {
96
+ const dir = await tempDir("remnic-skill-scripts-");
97
+ const memoryDir = await tempDir("remnic-skill-memory-");
98
+ try {
99
+ const bundleDir = path.join(dir, "risky-skill");
100
+ await mkdir(path.join(bundleDir, "scripts"), { recursive: true });
101
+ await writeFile(
102
+ path.join(bundleDir, SKILL_FILE_NAME),
103
+ ["---", "name: risky-skill", "description: Do the risky thing", "---", "", "Do the risky thing", "", "## Step 1", "", "Run the checklist.", ""].join("\n"),
104
+ );
105
+ await writeFile(path.join(bundleDir, "scripts", "install.sh"), "#!/bin/sh\necho nope\n");
106
+
107
+ const read = await readSkillBundlesFromDir(dir);
108
+ assert.equal(read.bundles.length, 1);
109
+ assert.equal(read.bundles[0].hasUnimportedResources, true);
110
+ assert.equal(read.bundles[0].steps?.length, 1);
111
+ assert.ok(!read.bundles[0].body.includes("echo nope"));
112
+
113
+ const storage = new StorageManager(memoryDir);
114
+ await persistImportedSkills({ storage, bundles: read.bundles });
115
+ const imported = (await storage.readAllMemories()).filter((m) => m.frontmatter.category === "procedure");
116
+ assert.equal(imported.length, 1);
117
+ assert.equal(imported[0].frontmatter.structuredAttributes?.hasUnimportedResources, "true");
118
+ assert.equal(imported[0].frontmatter.status, "pending_review");
119
+ } finally {
120
+ await rm(dir, { recursive: true, force: true });
121
+ await rm(memoryDir, { recursive: true, force: true });
122
+ }
123
+ });
124
+
125
+ test("a step-less body is stored verbatim", async () => {
126
+ const dir = await tempDir("remnic-skill-prose-");
127
+ const memoryDir = await tempDir("remnic-skill-memory-");
128
+ try {
129
+ const bundleDir = path.join(dir, "prose-skill");
130
+ await mkdir(bundleDir, { recursive: true });
131
+ await writeFile(path.join(bundleDir, SKILL_FILE_NAME), "---\nname: prose-skill\n---\n\nJust prose guidance.\n");
132
+
133
+ const read = await readSkillBundlesFromDir(dir);
134
+ assert.equal(read.bundles[0].steps, null);
135
+
136
+ const storage = new StorageManager(memoryDir);
137
+ await persistImportedSkills({ storage, bundles: read.bundles });
138
+ const imported = (await storage.readAllMemories()).filter((m) => m.frontmatter.category === "procedure");
139
+ assert.match(imported[0].content, /Just prose guidance\./);
140
+ } finally {
141
+ await rm(dir, { recursive: true, force: true });
142
+ await rm(memoryDir, { recursive: true, force: true });
143
+ }
144
+ });
145
+
146
+ test("symlinked bundles and escaping entries are skipped with a reason", async () => {
147
+ const dir = await tempDir("remnic-skill-symlink-");
148
+ const outside = await tempDir("remnic-skill-outside-");
149
+ try {
150
+ const real = path.join(dir, "real-skill");
151
+ await mkdir(real, { recursive: true });
152
+ await writeFile(path.join(real, SKILL_FILE_NAME), "---\nname: real-skill\n---\n\nReal body\n");
153
+
154
+ // A symlinked bundle directory pointing outside the import root.
155
+ await mkdir(path.join(outside, "evil"), { recursive: true });
156
+ await writeFile(path.join(outside, "evil", SKILL_FILE_NAME), "---\nname: evil\n---\n\nEscaped body\n");
157
+ await symlink(path.join(outside, "evil"), path.join(dir, "linked-skill"), "dir");
158
+
159
+ // A bundle whose SKILL.md is itself a symlink.
160
+ const linkedFile = path.join(dir, "linked-file-skill");
161
+ await mkdir(linkedFile, { recursive: true });
162
+ await symlink(path.join(outside, "evil", SKILL_FILE_NAME), path.join(linkedFile, SKILL_FILE_NAME), "file");
163
+
164
+ // A directory with no SKILL.md at all.
165
+ await mkdir(path.join(dir, "empty-skill"), { recursive: true });
166
+
167
+ const read = await readSkillBundlesFromDir(dir);
168
+ assert.deepEqual(read.bundles.map((b) => b.slug), ["real-skill"]);
169
+ const reasons = new Map(read.skipped.map((s) => [s.entry, s.reason]));
170
+ assert.equal(reasons.get("linked-skill"), "symlink");
171
+ assert.match(reasons.get("linked-file-skill") ?? "", /not a regular file/);
172
+ assert.match(reasons.get("empty-skill") ?? "", /no SKILL\.md/);
173
+ } finally {
174
+ await rm(dir, { recursive: true, force: true });
175
+ await rm(outside, { recursive: true, force: true });
176
+ }
177
+ });
178
+
179
+ test("a symlinked import root is refused outright", async () => {
180
+ const dir = await tempDir("remnic-skill-root-");
181
+ const linkParent = await tempDir("remnic-skill-link-");
182
+ try {
183
+ const link = path.join(linkParent, "linked-root");
184
+ await symlink(dir, link, "dir");
185
+ await assert.rejects(() => readSkillBundlesFromDir(link), /symlinked directory/);
186
+ } finally {
187
+ await rm(dir, { recursive: true, force: true });
188
+ await rm(linkParent, { recursive: true, force: true });
189
+ }
190
+ });
@@ -0,0 +1,220 @@
1
+ /**
2
+ * skill-io.ts — filesystem export / import for procedural skill bundles
3
+ * (issue #2369).
4
+ *
5
+ * Export writes `<dir>/<slug>/SKILL.md` and never deletes anything it did not
6
+ * just render. Import walks every `<dir>/<slug>/SKILL.md`, rejects symlinks and any entry that
7
+ * resolves outside the requested root, and persists `pending_review` procedure
8
+ * memories through the sealed-envelope write path.
9
+ *
10
+ * Import is INERT: bundle resources (`scripts/`, binaries, extra files) are
11
+ * never read, copied, or executed. Their presence is recorded as
12
+ * `hasUnimportedResources` so a reviewer can act on it.
13
+ */
14
+
15
+ import { lstat, mkdir, readdir, readFile, realpath, writeFile } from "node:fs/promises";
16
+ import path from "node:path";
17
+
18
+ import { log } from "../logger.js";
19
+ import type { StorageManager } from "../storage.js";
20
+ import { pathIsInside } from "../utils/path-containment.js";
21
+ import { composeMemoryEnvelope } from "../write-envelope.js";
22
+ import { buildProcedurePersistBody } from "./procedure-types.js";
23
+ import {
24
+ parseSkillBundle,
25
+ renderSkillBundle,
26
+ sanitizeSkillSlug,
27
+ SKILL_FILE_NAME,
28
+ SKILL_IMPORT_SOURCE,
29
+ type ParsedSkillBundle,
30
+ type SkillBundle,
31
+ } from "./skill-projection.js";
32
+
33
+ export interface ExportSkillsResult {
34
+ outDir: string;
35
+ /** Slugs written this run, in projection order. */
36
+ slugs: string[];
37
+ }
38
+
39
+ /** Write bundles as `<outDir>/<slug>/SKILL.md`. Creates directories as needed. */
40
+ export async function exportSkillBundles(options: {
41
+ bundles: readonly SkillBundle[];
42
+ outDir: string;
43
+ }): Promise<ExportSkillsResult> {
44
+ const outDir = path.resolve(options.outDir);
45
+ await mkdir(outDir, { recursive: true });
46
+ const slugs: string[] = [];
47
+ for (const bundle of options.bundles) {
48
+ const dir = path.join(outDir, bundle.slug);
49
+ if (!pathIsInside(outDir, dir)) {
50
+ throw new Error(`skill export: refusing to write outside ${outDir} (slug ${bundle.slug})`);
51
+ }
52
+ try {
53
+ const existing = await lstat(dir);
54
+ if (existing.isSymbolicLink()) {
55
+ throw new Error(`skill export: refusing to write through symlink ${dir}`);
56
+ }
57
+ } catch (err) {
58
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
59
+ }
60
+ await mkdir(dir, { recursive: true });
61
+ const skillPath = path.join(dir, SKILL_FILE_NAME);
62
+ try {
63
+ const existingSkill = await lstat(skillPath);
64
+ if (existingSkill.isSymbolicLink()) {
65
+ throw new Error(`skill export: refusing to write through symlink ${skillPath}`);
66
+ }
67
+ } catch (err) {
68
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
69
+ }
70
+ await writeFile(skillPath, renderSkillBundle(bundle), "utf-8");
71
+ slugs.push(bundle.slug);
72
+ }
73
+ return { outDir, slugs };
74
+ }
75
+
76
+ /** One bundle read off disk, plus the resource flag the reviewer needs. */
77
+ export interface ReadSkillBundle extends ParsedSkillBundle {
78
+ /** True when the bundle directory holds anything besides SKILL.md. */
79
+ hasUnimportedResources: boolean;
80
+ }
81
+
82
+ export interface ReadSkillBundlesResult {
83
+ bundles: ReadSkillBundle[];
84
+ /** Entries that were not importable, with the reason (never silent). */
85
+ skipped: Array<{ entry: string; reason: string }>;
86
+ }
87
+
88
+ /**
89
+ * Read every `<dir>/*​/SKILL.md` bundle. Symlinked roots, symlinked bundle
90
+ * directories, symlinked SKILL.md files, and anything resolving outside `dir`
91
+ * are skipped with a reason (Review Prevention Checklist §3, §10).
92
+ */
93
+ export async function readSkillBundlesFromDir(dir: string): Promise<ReadSkillBundlesResult> {
94
+ const root = path.resolve(dir);
95
+ const rootStat = await lstat(root);
96
+ if (rootStat.isSymbolicLink()) {
97
+ throw new Error(`skill import: refusing to walk symlinked directory ${root}`);
98
+ }
99
+ if (!rootStat.isDirectory()) {
100
+ throw new Error(`skill import: ${root} is not a directory`);
101
+ }
102
+ const rootReal = await realpath(root);
103
+
104
+ const bundles: ReadSkillBundle[] = [];
105
+ const skipped: Array<{ entry: string; reason: string }> = [];
106
+ const entries = await readdir(root, { withFileTypes: true });
107
+ // Stable order — readdir order is not guaranteed (§6).
108
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
109
+
110
+ for (const entry of entries) {
111
+ try {
112
+ if (entry.isSymbolicLink()) {
113
+ skipped.push({ entry: entry.name, reason: "symlink" });
114
+ continue;
115
+ }
116
+ if (!entry.isDirectory()) continue;
117
+ const bundleDir = path.join(root, entry.name);
118
+ const bundleReal = await realpath(bundleDir);
119
+ if (!pathIsInside(rootReal, bundleReal)) {
120
+ skipped.push({ entry: entry.name, reason: "resolves outside the import root" });
121
+ continue;
122
+ }
123
+ const skillPath = path.join(bundleDir, SKILL_FILE_NAME);
124
+ let skillStat;
125
+ try {
126
+ skillStat = await lstat(skillPath);
127
+ } catch {
128
+ skipped.push({ entry: entry.name, reason: `no ${SKILL_FILE_NAME}` });
129
+ continue;
130
+ }
131
+ if (skillStat.isSymbolicLink() || !skillStat.isFile()) {
132
+ skipped.push({ entry: entry.name, reason: `${SKILL_FILE_NAME} is not a regular file` });
133
+ continue;
134
+ }
135
+ const parsed = parseSkillBundle(await readFile(skillPath, "utf-8"), sanitizeSkillSlug(entry.name));
136
+ if (!parsed) {
137
+ skipped.push({ entry: entry.name, reason: "empty body" });
138
+ continue;
139
+ }
140
+ const siblings = await readdir(bundleDir);
141
+ bundles.push({
142
+ ...parsed,
143
+ hasUnimportedResources: siblings.some((name) => name !== SKILL_FILE_NAME),
144
+ });
145
+ } catch (err) {
146
+ skipped.push({
147
+ entry: entry.name,
148
+ reason: `unreadable: ${err instanceof Error ? err.message : String(err)}`,
149
+ });
150
+ }
151
+ }
152
+
153
+ return { bundles, skipped };
154
+ }
155
+
156
+ export interface ImportedSkillRecord {
157
+ slug: string;
158
+ memoryId: string;
159
+ steps: number;
160
+ hasUnimportedResources: boolean;
161
+ }
162
+
163
+ export interface ImportSkillsResult {
164
+ imported: ImportedSkillRecord[];
165
+ /** Bundles that were read but not persisted (e.g. tombstone-blocked). */
166
+ rejected: Array<{ slug: string; reason: string }>;
167
+ }
168
+
169
+ /**
170
+ * Persist read bundles as `pending_review` procedure memories.
171
+ *
172
+ * `pending_review` is unconditional: imported procedures never auto-promote,
173
+ * regardless of `procedural.autoPromoteOccurrences`. Recall already excludes
174
+ * non-active procedures, so review is the promotion checkpoint.
175
+ */
176
+ export async function persistImportedSkills(options: {
177
+ storage: StorageManager;
178
+ bundles: readonly ReadSkillBundle[];
179
+ }): Promise<ImportSkillsResult> {
180
+ const imported: ImportedSkillRecord[] = [];
181
+ const rejected: Array<{ slug: string; reason: string }> = [];
182
+
183
+ for (const bundle of options.bundles) {
184
+ const title = (bundle.description ?? bundle.name ?? bundle.slug).trim() || bundle.slug;
185
+ const content = bundle.steps ? buildProcedurePersistBody(title, bundle.steps) : bundle.body;
186
+ const envelope = composeMemoryEnvelope(
187
+ {
188
+ content,
189
+ category: "procedure",
190
+ tags: ["skill-import", "procedure"],
191
+ structuredAttributes: {
192
+ skill_slug: bundle.slug,
193
+ // Basename only — never an operator's absolute path.
194
+ skill_source: `${bundle.slug}/${SKILL_FILE_NAME}`,
195
+ ...(bundle.provenance.memoryId ? { skill_origin_memory_id: bundle.provenance.memoryId } : {}),
196
+ ...(bundle.hasUnimportedResources ? { hasUnimportedResources: "true" } : {}),
197
+ },
198
+ },
199
+ { source: SKILL_IMPORT_SOURCE },
200
+ // Machine-generated / externally authored input — salvage, warn on drops.
201
+ { salvage: true },
202
+ );
203
+ if (envelope.salvageNotes.length > 0) {
204
+ log.warn(`skill-import write salvaged invalid fields: ${envelope.salvageNotes.join("; ")}`);
205
+ }
206
+ const written = await options.storage.writeSealedMemory(envelope, { status: "pending_review" });
207
+ if (written.tombstoneBlocked) {
208
+ rejected.push({ slug: bundle.slug, reason: "blocked by a tombstone; left for review" });
209
+ continue;
210
+ }
211
+ imported.push({
212
+ slug: bundle.slug,
213
+ memoryId: written.id,
214
+ steps: bundle.steps?.length ?? 0,
215
+ hasUnimportedResources: bundle.hasUnimportedResources,
216
+ });
217
+ }
218
+
219
+ return { imported, rejected };
220
+ }