hippo-memory 1.60.0 → 1.62.0

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 (593) hide show
  1. package/README.md +29 -54
  2. package/dist/ablation.js +9 -27
  3. package/dist/agent-memories/apply.d.ts +1 -1
  4. package/dist/agent-memories/apply.js +4 -1
  5. package/dist/agent-memories/claude-code.d.ts +3 -1
  6. package/dist/agent-memories/claude-code.js +54 -10
  7. package/dist/agent-memories/codex.js +1 -1
  8. package/dist/agent-memories/gemini.js +1 -1
  9. package/dist/agent-memories/legacy.js +1 -1
  10. package/dist/agent-memories/source.js +1 -1
  11. package/dist/agent-memories/sync.d.ts +3 -3
  12. package/dist/agent-memories/sync.js +22 -9
  13. package/dist/agent-memories/types.d.ts +0 -2
  14. package/dist/ambient-store.d.ts +14 -0
  15. package/dist/ambient-store.js +90 -0
  16. package/dist/ambient.d.ts +23 -0
  17. package/dist/ambient.js +72 -50
  18. package/dist/api/assemble.d.ts +93 -0
  19. package/dist/api/assemble.js +153 -0
  20. package/dist/api/audit.d.ts +17 -0
  21. package/dist/api/audit.js +23 -0
  22. package/dist/api/auth.d.ts +79 -0
  23. package/dist/api/auth.js +178 -0
  24. package/dist/api/context-select.d.ts +49 -0
  25. package/dist/api/context-select.js +344 -0
  26. package/dist/api/context-types.d.ts +105 -0
  27. package/dist/api/context-types.js +3 -0
  28. package/dist/api/context.d.ts +31 -0
  29. package/dist/api/context.js +378 -0
  30. package/dist/api/dormant.d.ts +30 -0
  31. package/dist/api/dormant.js +140 -0
  32. package/dist/api/drill-down.d.ts +84 -0
  33. package/dist/api/drill-down.js +126 -0
  34. package/dist/api/forget.d.ts +57 -0
  35. package/dist/api/forget.js +87 -0
  36. package/dist/api/goals.d.ts +18 -0
  37. package/dist/api/goals.js +33 -0
  38. package/dist/api/learn.d.ts +31 -0
  39. package/dist/api/learn.js +88 -0
  40. package/dist/api/outcome.d.ts +61 -0
  41. package/dist/api/outcome.js +66 -0
  42. package/dist/api/promote.d.ts +54 -0
  43. package/dist/api/promote.js +192 -0
  44. package/dist/api/quarantine.d.ts +24 -0
  45. package/dist/api/quarantine.js +121 -0
  46. package/dist/api/recall-types.d.ts +390 -0
  47. package/dist/api/recall-types.js +3 -0
  48. package/dist/api/recall.d.ts +36 -0
  49. package/dist/api/recall.js +499 -0
  50. package/dist/api/remember.d.ts +35 -0
  51. package/dist/api/remember.js +43 -0
  52. package/dist/api/sleep.d.ts +136 -0
  53. package/dist/api/sleep.js +256 -0
  54. package/dist/api/tokens.d.ts +26 -0
  55. package/dist/api/tokens.js +60 -0
  56. package/dist/api/types.d.ts +56 -0
  57. package/dist/api/types.js +38 -0
  58. package/dist/api.d.ts +20 -1262
  59. package/dist/api.js +25 -2727
  60. package/dist/audit.d.ts +3 -0
  61. package/dist/audit.js +6 -3
  62. package/dist/auth.d.ts +45 -4
  63. package/dist/auth.js +125 -48
  64. package/dist/autolearn.js +2 -2
  65. package/dist/capture/command.d.ts +33 -0
  66. package/dist/capture/command.js +264 -0
  67. package/dist/capture/compact.d.ts +44 -0
  68. package/dist/capture/compact.js +354 -0
  69. package/dist/capture/extract.d.ts +21 -0
  70. package/dist/capture/extract.js +464 -0
  71. package/dist/capture/transcript.d.ts +40 -0
  72. package/dist/capture/transcript.js +193 -0
  73. package/dist/capture-error.js +2 -1
  74. package/dist/churn-git.js +4 -2
  75. package/dist/cli/audit.d.ts +3 -0
  76. package/dist/cli/audit.js +159 -0
  77. package/dist/cli/auth.d.ts +2 -0
  78. package/dist/cli/auth.js +171 -0
  79. package/dist/cli/briefs.d.ts +4 -0
  80. package/dist/cli/briefs.js +453 -0
  81. package/dist/cli/card.d.ts +3 -0
  82. package/dist/cli/card.js +333 -0
  83. package/dist/cli/context.d.ts +15 -0
  84. package/dist/cli/context.js +376 -0
  85. package/dist/cli/continuity.d.ts +6 -0
  86. package/dist/cli/continuity.js +457 -0
  87. package/dist/cli/curate.d.ts +19 -0
  88. package/dist/cli/curate.js +556 -0
  89. package/dist/cli/dag.d.ts +5 -0
  90. package/dist/cli/dag.js +177 -0
  91. package/dist/cli/decisions.d.ts +4 -0
  92. package/dist/cli/decisions.js +528 -0
  93. package/dist/cli/eval.d.ts +5 -0
  94. package/dist/cli/eval.js +213 -0
  95. package/dist/cli/explain.d.ts +4 -0
  96. package/dist/cli/explain.js +156 -0
  97. package/dist/cli/goals.d.ts +2 -0
  98. package/dist/cli/goals.js +196 -0
  99. package/dist/cli/hook-blocks.d.ts +18 -0
  100. package/dist/cli/hook-blocks.js +233 -0
  101. package/dist/cli/init.d.ts +2 -0
  102. package/dist/cli/init.js +305 -0
  103. package/dist/cli/maintenance.d.ts +4 -0
  104. package/dist/cli/maintenance.js +190 -0
  105. package/dist/cli/playbooks.d.ts +4 -0
  106. package/dist/cli/playbooks.js +573 -0
  107. package/dist/cli/projects.js +9 -6
  108. package/dist/cli/recall.d.ts +7 -0
  109. package/dist/cli/recall.js +582 -0
  110. package/dist/cli/remember.d.ts +5 -0
  111. package/dist/cli/remember.js +485 -0
  112. package/dist/cli/serve.d.ts +5 -0
  113. package/dist/cli/serve.js +40 -0
  114. package/dist/cli/session-hooks.d.ts +29 -0
  115. package/dist/cli/session-hooks.js +647 -0
  116. package/dist/cli/setup.d.ts +4 -0
  117. package/dist/cli/setup.js +397 -0
  118. package/dist/cli/shared.d.ts +15 -19
  119. package/dist/cli/shared.js +50 -341
  120. package/dist/cli/slack.d.ts +2 -0
  121. package/dist/cli/slack.js +171 -0
  122. package/dist/cli/status.d.ts +18 -0
  123. package/dist/cli/status.js +409 -0
  124. package/dist/cli/transfer.d.ts +10 -0
  125. package/dist/cli/transfer.js +424 -0
  126. package/dist/cli/usage.d.ts +85 -0
  127. package/dist/cli/usage.js +741 -0
  128. package/dist/cli.d.ts +71 -120
  129. package/dist/cli.js +170 -8469
  130. package/dist/client.js +15 -8
  131. package/dist/compaction-record.d.ts +0 -2
  132. package/dist/compaction-record.js +7 -5
  133. package/dist/connectors/github/backfill.js +94 -87
  134. package/dist/connectors/github/cli-impl.js +4 -3
  135. package/dist/connectors/github/dlq.js +67 -54
  136. package/dist/connectors/github/ingest.js +34 -35
  137. package/dist/connectors/github/tenant-routing.js +3 -2
  138. package/dist/connectors/github/webhook.js +135 -216
  139. package/dist/connectors/slack/dlq.js +49 -61
  140. package/dist/connectors/slack/ingest.js +55 -48
  141. package/dist/connectors/slack/tenant-routing.js +4 -3
  142. package/dist/connectors/slack/webhook.js +72 -74
  143. package/dist/consolidate/conflicts.d.ts +10 -0
  144. package/dist/consolidate/conflicts.js +178 -0
  145. package/dist/consolidate/decay.d.ts +12 -0
  146. package/dist/consolidate/decay.js +145 -0
  147. package/dist/consolidate/llm-passes.d.ts +3 -0
  148. package/dist/consolidate/llm-passes.js +141 -0
  149. package/dist/consolidate/merge.d.ts +7 -0
  150. package/dist/consolidate/merge.js +251 -0
  151. package/dist/consolidate/physics-pass.d.ts +3 -0
  152. package/dist/consolidate/physics-pass.js +60 -0
  153. package/dist/consolidate/run.d.ts +69 -0
  154. package/dist/consolidate/run.js +76 -0
  155. package/dist/consolidate/sleep.d.ts +18 -0
  156. package/dist/consolidate/sleep.js +209 -0
  157. package/dist/consolidate/traces.d.ts +4 -0
  158. package/dist/consolidate/traces.js +178 -0
  159. package/dist/context-auto.js +7 -11
  160. package/dist/context-render.d.ts +1 -1
  161. package/dist/context-render.js +1 -1
  162. package/dist/customer-notes.d.ts +3 -0
  163. package/dist/customer-notes.js +83 -72
  164. package/dist/dag.js +225 -188
  165. package/dist/dashboard-actions.d.ts +20 -0
  166. package/dist/dashboard-actions.js +88 -0
  167. package/dist/dashboard-params.d.ts +45 -0
  168. package/dist/dashboard-params.js +127 -0
  169. package/dist/dashboard-queries.d.ts +15 -0
  170. package/dist/dashboard-queries.js +355 -0
  171. package/dist/dashboard-snapshot.d.ts +138 -0
  172. package/dist/dashboard-snapshot.js +308 -0
  173. package/dist/dashboard-types.d.ts +163 -0
  174. package/dist/dashboard-types.js +3 -0
  175. package/dist/dashboard.d.ts +6 -7
  176. package/dist/dashboard.js +228 -202
  177. package/dist/db/busy.d.ts +5 -0
  178. package/dist/db/busy.js +24 -0
  179. package/dist/db/continuity.d.ts +5 -0
  180. package/dist/db/continuity.js +145 -0
  181. package/dist/db/meta.d.ts +8 -0
  182. package/dist/db/meta.js +35 -0
  183. package/dist/db/migrate.d.ts +9 -0
  184. package/dist/db/migrate.js +138 -0
  185. package/dist/db/migrations/index.d.ts +5 -0
  186. package/dist/db/migrations/index.js +109 -0
  187. package/dist/db/migrations/types.d.ts +14 -0
  188. package/dist/db/migrations/types.js +2 -0
  189. package/dist/db/migrations/v01.d.ts +3 -0
  190. package/dist/db/migrations/v01.js +38 -0
  191. package/dist/db/migrations/v02.d.ts +3 -0
  192. package/dist/db/migrations/v02.js +21 -0
  193. package/dist/db/migrations/v03.d.ts +3 -0
  194. package/dist/db/migrations/v03.js +22 -0
  195. package/dist/db/migrations/v04.d.ts +3 -0
  196. package/dist/db/migrations/v04.js +28 -0
  197. package/dist/db/migrations/v05.d.ts +3 -0
  198. package/dist/db/migrations/v05.js +21 -0
  199. package/dist/db/migrations/v06.d.ts +3 -0
  200. package/dist/db/migrations/v06.js +25 -0
  201. package/dist/db/migrations/v07.d.ts +3 -0
  202. package/dist/db/migrations/v07.js +13 -0
  203. package/dist/db/migrations/v08.d.ts +3 -0
  204. package/dist/db/migrations/v08.js +8 -0
  205. package/dist/db/migrations/v09.d.ts +3 -0
  206. package/dist/db/migrations/v09.js +13 -0
  207. package/dist/db/migrations/v10.d.ts +3 -0
  208. package/dist/db/migrations/v10.js +17 -0
  209. package/dist/db/migrations/v11.d.ts +3 -0
  210. package/dist/db/migrations/v11.js +15 -0
  211. package/dist/db/migrations/v12.d.ts +3 -0
  212. package/dist/db/migrations/v12.js +11 -0
  213. package/dist/db/migrations/v13.d.ts +3 -0
  214. package/dist/db/migrations/v13.js +15 -0
  215. package/dist/db/migrations/v14.d.ts +3 -0
  216. package/dist/db/migrations/v14.js +66 -0
  217. package/dist/db/migrations/v15.d.ts +3 -0
  218. package/dist/db/migrations/v15.js +42 -0
  219. package/dist/db/migrations/v16.d.ts +3 -0
  220. package/dist/db/migrations/v16.js +61 -0
  221. package/dist/db/migrations/v17.d.ts +3 -0
  222. package/dist/db/migrations/v17.js +46 -0
  223. package/dist/db/migrations/v18.d.ts +3 -0
  224. package/dist/db/migrations/v18.js +60 -0
  225. package/dist/db/migrations/v19.d.ts +3 -0
  226. package/dist/db/migrations/v19.js +28 -0
  227. package/dist/db/migrations/v20.d.ts +3 -0
  228. package/dist/db/migrations/v20.js +42 -0
  229. package/dist/db/migrations/v21.d.ts +3 -0
  230. package/dist/db/migrations/v21.js +16 -0
  231. package/dist/db/migrations/v22.d.ts +3 -0
  232. package/dist/db/migrations/v22.js +82 -0
  233. package/dist/db/migrations/v23.d.ts +3 -0
  234. package/dist/db/migrations/v23.js +48 -0
  235. package/dist/db/migrations/v24.d.ts +3 -0
  236. package/dist/db/migrations/v24.js +72 -0
  237. package/dist/db/migrations/v25.d.ts +3 -0
  238. package/dist/db/migrations/v25.js +46 -0
  239. package/dist/db/migrations/v26.d.ts +3 -0
  240. package/dist/db/migrations/v26.js +23 -0
  241. package/dist/db/migrations/v27.d.ts +3 -0
  242. package/dist/db/migrations/v27.js +57 -0
  243. package/dist/db/migrations/v28.d.ts +3 -0
  244. package/dist/db/migrations/v28.js +38 -0
  245. package/dist/db/migrations/v29.d.ts +3 -0
  246. package/dist/db/migrations/v29.js +78 -0
  247. package/dist/db/migrations/v30.d.ts +3 -0
  248. package/dist/db/migrations/v30.js +92 -0
  249. package/dist/db/migrations/v31.d.ts +3 -0
  250. package/dist/db/migrations/v31.js +74 -0
  251. package/dist/db/migrations/v32.d.ts +3 -0
  252. package/dist/db/migrations/v32.js +102 -0
  253. package/dist/db/migrations/v33.d.ts +3 -0
  254. package/dist/db/migrations/v33.js +104 -0
  255. package/dist/db/migrations/v34.d.ts +3 -0
  256. package/dist/db/migrations/v34.js +93 -0
  257. package/dist/db/migrations/v35.d.ts +3 -0
  258. package/dist/db/migrations/v35.js +98 -0
  259. package/dist/db/migrations/v36.d.ts +3 -0
  260. package/dist/db/migrations/v36.js +98 -0
  261. package/dist/db/migrations/v37.d.ts +3 -0
  262. package/dist/db/migrations/v37.js +219 -0
  263. package/dist/db/migrations/v38.d.ts +3 -0
  264. package/dist/db/migrations/v38.js +277 -0
  265. package/dist/db/migrations/v39.d.ts +3 -0
  266. package/dist/db/migrations/v39.js +59 -0
  267. package/dist/db/migrations/v40.d.ts +3 -0
  268. package/dist/db/migrations/v40.js +74 -0
  269. package/dist/db/migrations/v41.d.ts +3 -0
  270. package/dist/db/migrations/v41.js +43 -0
  271. package/dist/db/migrations/v42.d.ts +3 -0
  272. package/dist/db/migrations/v42.js +41 -0
  273. package/dist/db/migrations/v43.d.ts +3 -0
  274. package/dist/db/migrations/v43.js +67 -0
  275. package/dist/db/migrations/v44.d.ts +3 -0
  276. package/dist/db/migrations/v44.js +28 -0
  277. package/dist/db/migrations/v45.d.ts +3 -0
  278. package/dist/db/migrations/v45.js +30 -0
  279. package/dist/db/migrations/v46.d.ts +3 -0
  280. package/dist/db/migrations/v46.js +25 -0
  281. package/dist/db/migrations/v47.d.ts +3 -0
  282. package/dist/db/migrations/v47.js +17 -0
  283. package/dist/db/migrations/v48.d.ts +3 -0
  284. package/dist/db/migrations/v48.js +10 -0
  285. package/dist/db/migrations/v49.d.ts +3 -0
  286. package/dist/db/migrations/v49.js +31 -0
  287. package/dist/db/migrations/v50.d.ts +3 -0
  288. package/dist/db/migrations/v50.js +67 -0
  289. package/dist/db/migrations/v51.d.ts +3 -0
  290. package/dist/db/migrations/v51.js +14 -0
  291. package/dist/db/migrations/v52.d.ts +3 -0
  292. package/dist/db/migrations/v52.js +7 -0
  293. package/dist/db/open.d.ts +23 -0
  294. package/dist/db/open.js +146 -0
  295. package/dist/db/sqlite.d.ts +21 -0
  296. package/dist/db/sqlite.js +8 -0
  297. package/dist/db/tables.d.ts +7 -0
  298. package/dist/db/tables.js +38 -0
  299. package/dist/db.d.ts +6 -46
  300. package/dist/db.js +5 -3049
  301. package/dist/decisions.d.ts +4 -1
  302. package/dist/decisions.js +102 -83
  303. package/dist/dedupe.js +3 -2
  304. package/dist/delivery-recorder.js +4 -1
  305. package/dist/doctor.js +14 -10
  306. package/dist/embedding-provider.d.ts +1 -1
  307. package/dist/embedding-provider.js +4 -3
  308. package/dist/embeddings.d.ts +9 -52
  309. package/dist/embeddings.js +43 -297
  310. package/dist/env.d.ts +75 -0
  311. package/dist/env.js +119 -0
  312. package/dist/eval-suite.js +1 -1
  313. package/dist/eval.js +2 -2
  314. package/dist/extract.js +1 -1
  315. package/dist/gated-write.js +3 -1
  316. package/dist/goals.d.ts +3 -1
  317. package/dist/goals.js +117 -86
  318. package/dist/graph/read.d.ts +73 -0
  319. package/dist/graph/read.js +325 -0
  320. package/dist/graph/rows.d.ts +45 -0
  321. package/dist/graph/rows.js +51 -0
  322. package/dist/graph/types.d.ts +83 -0
  323. package/dist/graph/types.js +11 -0
  324. package/dist/graph/write.d.ts +93 -0
  325. package/dist/{graph.js → graph/write.js} +6 -384
  326. package/dist/graph-extract.js +2 -1
  327. package/dist/graph-recall.d.ts +1 -1
  328. package/dist/graph-recall.js +2 -2
  329. package/dist/graph-stream.js +1 -1
  330. package/dist/graph-view.d.ts +1 -1
  331. package/dist/graph-view.js +1 -1
  332. package/dist/half-life-migration.d.ts +1 -1
  333. package/dist/half-life-migration.js +2 -1
  334. package/dist/hooks/codex-session.d.ts +8 -0
  335. package/dist/hooks/codex-session.js +76 -0
  336. package/dist/hooks/codex-wrapper.d.ts +55 -0
  337. package/dist/hooks/codex-wrapper.js +288 -0
  338. package/dist/hooks/json-hooks.d.ts +63 -0
  339. package/dist/hooks/json-hooks.js +356 -0
  340. package/dist/hooks/opencode.d.ts +50 -0
  341. package/dist/hooks/opencode.js +202 -0
  342. package/dist/hooks/shared.d.ts +54 -0
  343. package/dist/hooks/shared.js +77 -0
  344. package/dist/http-retry.d.ts +2 -0
  345. package/dist/http-retry.js +4 -3
  346. package/dist/http-util.d.ts +3 -0
  347. package/dist/http-util.js +10 -0
  348. package/dist/{importers.d.ts → importers/core.d.ts} +13 -17
  349. package/dist/importers/core.js +141 -0
  350. package/dist/importers/markdown-parse.d.ts +41 -0
  351. package/dist/importers/markdown-parse.js +132 -0
  352. package/dist/importers/markdown.d.ts +3 -0
  353. package/dist/importers/markdown.js +92 -0
  354. package/dist/importers/sources.d.ts +7 -0
  355. package/dist/importers/sources.js +229 -0
  356. package/dist/importers/vault.d.ts +11 -0
  357. package/dist/importers/vault.js +352 -0
  358. package/dist/incidents.d.ts +3 -0
  359. package/dist/incidents.js +52 -43
  360. package/dist/index.d.ts +25 -6
  361. package/dist/index.js +23 -6
  362. package/dist/invalidation.js +2 -1
  363. package/dist/judgment.js +2 -1
  364. package/dist/keyset.d.ts +13 -0
  365. package/dist/keyset.js +8 -0
  366. package/dist/local-embedding.d.ts +13 -0
  367. package/dist/local-embedding.js +165 -0
  368. package/dist/log.d.ts +7 -0
  369. package/dist/log.js +19 -1
  370. package/dist/mcp/admin-tools.d.ts +8 -0
  371. package/dist/mcp/admin-tools.js +116 -0
  372. package/dist/mcp/format.d.ts +27 -0
  373. package/dist/mcp/format.js +135 -0
  374. package/dist/mcp/memory-tools.d.ts +5 -0
  375. package/dist/mcp/memory-tools.js +83 -0
  376. package/dist/mcp/protocol.d.ts +83 -0
  377. package/dist/mcp/protocol.js +55 -0
  378. package/dist/mcp/recall-tools.d.ts +6 -0
  379. package/dist/mcp/recall-tools.js +320 -0
  380. package/dist/mcp/request.d.ts +10 -0
  381. package/dist/mcp/request.js +163 -0
  382. package/dist/mcp/server.d.ts +4 -75
  383. package/dist/mcp/server.js +7 -1190
  384. package/dist/mcp/session-state.d.ts +11 -0
  385. package/dist/mcp/session-state.js +28 -0
  386. package/dist/mcp/stdio.d.ts +8 -0
  387. package/dist/mcp/stdio.js +79 -0
  388. package/dist/mcp/tools.d.ts +11 -0
  389. package/dist/mcp/tools.js +247 -0
  390. package/dist/memory.d.ts +3 -0
  391. package/dist/memory.js +27 -1
  392. package/dist/multihop.d.ts +1 -1
  393. package/dist/multihop.js +2 -1
  394. package/dist/owner-validation.js +2 -1
  395. package/dist/physics-state.js +10 -7
  396. package/dist/policies.d.ts +3 -0
  397. package/dist/policies.js +93 -74
  398. package/dist/postinstall.js +3 -2
  399. package/dist/predictions/planning-fallacy.d.ts +100 -0
  400. package/dist/predictions/planning-fallacy.js +190 -0
  401. package/dist/{predictions.d.ts → predictions/store.d.ts} +7 -102
  402. package/dist/predictions/store.js +434 -0
  403. package/dist/processes.d.ts +3 -0
  404. package/dist/processes.js +94 -76
  405. package/dist/project-briefs.d.ts +3 -0
  406. package/dist/project-briefs.js +142 -113
  407. package/dist/project-identity.js +3 -2
  408. package/dist/project-merge.d.ts +12 -4
  409. package/dist/project-merge.js +132 -45
  410. package/dist/quarantine.d.ts +2 -1
  411. package/dist/quarantine.js +8 -5
  412. package/dist/raw-archive.js +1 -1
  413. package/dist/recall-history.js +3 -2
  414. package/dist/recall-pipeline.d.ts +2 -2
  415. package/dist/recall-pipeline.js +16 -8
  416. package/dist/recall-scope.js +1 -1
  417. package/dist/recall-trace.d.ts +1 -1
  418. package/dist/refine-llm.js +2 -1
  419. package/dist/reject-flow.js +6 -1
  420. package/dist/rerankers/clef.js +6 -5
  421. package/dist/rerankers/jev.d.ts +1 -1
  422. package/dist/rerankers/jev.js +4 -4
  423. package/dist/rerankers/llm.d.ts +4 -2
  424. package/dist/rerankers/llm.js +58 -38
  425. package/dist/rerankers/types.d.ts +1 -1
  426. package/dist/salience.js +1 -1
  427. package/dist/scheduler.d.ts +1 -0
  428. package/dist/scheduler.js +26 -4
  429. package/dist/scope.js +4 -3
  430. package/dist/search/as-of.d.ts +10 -0
  431. package/dist/search/as-of.js +22 -0
  432. package/dist/search/bm25-search.d.ts +14 -0
  433. package/dist/search/bm25-search.js +43 -0
  434. package/dist/search/bm25.d.ts +15 -0
  435. package/dist/search/bm25.js +54 -0
  436. package/dist/search/boosts.d.ts +54 -0
  437. package/dist/search/boosts.js +94 -0
  438. package/dist/search/breakdown.d.ts +7 -0
  439. package/dist/search/breakdown.js +20 -0
  440. package/dist/search/explain.d.ts +25 -0
  441. package/dist/search/explain.js +31 -0
  442. package/dist/search/finalize.d.ts +8 -0
  443. package/dist/search/finalize.js +52 -0
  444. package/dist/search/fusion.d.ts +27 -0
  445. package/dist/search/fusion.js +42 -0
  446. package/dist/search/hybrid-score.d.ts +20 -0
  447. package/dist/search/hybrid-score.js +73 -0
  448. package/dist/search/hybrid.d.ts +46 -0
  449. package/dist/search/hybrid.js +64 -0
  450. package/dist/search/physics-search.d.ts +29 -0
  451. package/dist/search/physics-search.js +162 -0
  452. package/dist/search/rerank.d.ts +10 -0
  453. package/dist/search/rerank.js +72 -0
  454. package/dist/search/temporal.d.ts +15 -0
  455. package/dist/search/temporal.js +45 -0
  456. package/dist/search/types.d.ts +91 -0
  457. package/dist/search/types.js +2 -0
  458. package/dist/search/vector.d.ts +30 -0
  459. package/dist/search/vector.js +71 -0
  460. package/dist/secret-detect.d.ts +2 -0
  461. package/dist/secret-detect.js +2 -1
  462. package/dist/server/auth.d.ts +52 -0
  463. package/dist/server/auth.js +221 -0
  464. package/dist/server/client-ip.d.ts +25 -0
  465. package/dist/server/client-ip.js +92 -0
  466. package/dist/server/cursor.d.ts +23 -0
  467. package/dist/server/cursor.js +58 -0
  468. package/dist/server/lifecycle.d.ts +7 -0
  469. package/dist/server/lifecycle.js +29 -0
  470. package/dist/server/mcp-http.d.ts +5 -0
  471. package/dist/server/mcp-http.js +199 -0
  472. package/dist/server/request.d.ts +33 -0
  473. package/dist/server/request.js +103 -0
  474. package/dist/server/routes/admin.d.ts +9 -0
  475. package/dist/server/routes/admin.js +158 -0
  476. package/dist/server/routes/customer-notes.d.ts +7 -0
  477. package/dist/server/routes/customer-notes.js +112 -0
  478. package/dist/server/routes/decisions.d.ts +7 -0
  479. package/dist/server/routes/decisions.js +133 -0
  480. package/dist/server/routes/incidents.d.ts +7 -0
  481. package/dist/server/routes/incidents.js +126 -0
  482. package/dist/server/routes/memories.d.ts +10 -0
  483. package/dist/server/routes/memories.js +179 -0
  484. package/dist/server/routes/policies.d.ts +8 -0
  485. package/dist/server/routes/policies.js +151 -0
  486. package/dist/server/routes/predictions.d.ts +7 -0
  487. package/dist/server/routes/predictions.js +164 -0
  488. package/dist/server/routes/processes.d.ts +7 -0
  489. package/dist/server/routes/processes.js +161 -0
  490. package/dist/server/routes/project-briefs.d.ts +8 -0
  491. package/dist/server/routes/project-briefs.js +136 -0
  492. package/dist/server/routes/recall.d.ts +8 -0
  493. package/dist/server/routes/recall.js +340 -0
  494. package/dist/server/routes/skills.d.ts +8 -0
  495. package/dist/server/routes/skills.js +150 -0
  496. package/dist/server/types.d.ts +56 -0
  497. package/dist/server/types.js +2 -0
  498. package/dist/server/validation.d.ts +14 -0
  499. package/dist/server/validation.js +91 -0
  500. package/dist/server.d.ts +7 -61
  501. package/dist/server.js +69 -2362
  502. package/dist/session-digest.d.ts +1 -1
  503. package/dist/session-digest.js +9 -2
  504. package/dist/shared.d.ts +12 -1
  505. package/dist/shared.js +33 -27
  506. package/dist/skills.d.ts +3 -0
  507. package/dist/skills.js +88 -70
  508. package/dist/stdin.js +2 -2
  509. package/dist/store/audit-event.d.ts +19 -0
  510. package/dist/store/audit-event.js +33 -0
  511. package/dist/store/candidates.d.ts +42 -0
  512. package/dist/store/candidates.js +152 -0
  513. package/dist/store/conflicts.d.ts +44 -0
  514. package/dist/store/conflicts.js +444 -0
  515. package/dist/store/delete-and-batch.d.ts +63 -0
  516. package/dist/store/delete-and-batch.js +310 -0
  517. package/dist/store/entry-reads.d.ts +92 -0
  518. package/dist/store/entry-reads.js +255 -0
  519. package/dist/store/entry-row.d.ts +67 -0
  520. package/dist/store/entry-row.js +208 -0
  521. package/dist/store/entry-writes.d.ts +46 -0
  522. package/dist/store/entry-writes.js +148 -0
  523. package/dist/store/handoffs.d.ts +26 -0
  524. package/dist/store/handoffs.js +184 -0
  525. package/dist/store/index-and-stats.d.ts +52 -0
  526. package/dist/store/index-and-stats.js +214 -0
  527. package/dist/store/markdown.d.ts +10 -0
  528. package/dist/store/markdown.js +108 -0
  529. package/dist/store/mirrors.d.ts +49 -0
  530. package/dist/store/mirrors.js +312 -0
  531. package/dist/store/open.d.ts +15 -0
  532. package/dist/store/open.js +223 -0
  533. package/dist/store/rows.d.ts +178 -0
  534. package/dist/store/rows.js +158 -0
  535. package/dist/store/search-rows.d.ts +86 -0
  536. package/dist/store/search-rows.js +260 -0
  537. package/dist/store/sessions.d.ts +83 -0
  538. package/dist/store/sessions.js +272 -0
  539. package/dist/store/summaries.d.ts +94 -0
  540. package/dist/store/summaries.js +377 -0
  541. package/dist/store/tenant-lookup.d.ts +12 -0
  542. package/dist/store/tenant-lookup.js +16 -0
  543. package/dist/store-cards.js +2 -1
  544. package/dist/summary-dirty.d.ts +4 -0
  545. package/dist/summary-dirty.js +32 -0
  546. package/dist/support-bundle.js +4 -3
  547. package/dist/tenant.js +2 -2
  548. package/dist/tokenize.d.ts +2 -0
  549. package/dist/tokenize.js +16 -0
  550. package/dist/transcript-tail.d.ts +7 -0
  551. package/dist/transcript-tail.js +48 -0
  552. package/dist/vector-store.d.ts +27 -0
  553. package/dist/vector-store.js +210 -0
  554. package/dist/version.d.ts +2 -2
  555. package/dist/version.js +2 -2
  556. package/dist/working-memory.js +1 -1
  557. package/dist/yaml.js +36 -11
  558. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  559. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  560. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  561. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  562. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  563. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  564. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  565. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  566. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  567. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  568. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  569. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  570. package/dist-ui/assets/index-DPN7cP19.js +33 -0
  571. package/dist-ui/assets/index-dFloKRVr.css +1 -0
  572. package/dist-ui/index.html +3 -25
  573. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  574. package/extensions/openclaw-plugin/package.json +1 -1
  575. package/openclaw.plugin.json +1 -1
  576. package/package.json +1 -1
  577. package/dist/capture.d.ts +0 -155
  578. package/dist/capture.js +0 -1295
  579. package/dist/consolidate.d.ts +0 -58
  580. package/dist/consolidate.js +0 -1124
  581. package/dist/graph.d.ts +0 -245
  582. package/dist/hooks.d.ts +0 -208
  583. package/dist/hooks.js +0 -1076
  584. package/dist/importers.js +0 -900
  585. package/dist/predictions.js +0 -620
  586. package/dist/search.d.ts +0 -320
  587. package/dist/search.js +0 -970
  588. package/dist/store.d.ts +0 -776
  589. package/dist/store.js +0 -3473
  590. package/dist-ui/assets/d3-BiWEKnn4.js +0 -1
  591. package/dist-ui/assets/index-BhT8RvO6.js +0 -61
  592. package/dist-ui/assets/index-RoXXJ5dq.css +0 -1
  593. package/dist-ui/assets/three-BDgTxR1l.js +0 -4112
package/dist/importers.js DELETED
@@ -1,900 +0,0 @@
1
- /**
2
- * Memory importers for Hippo.
3
- * Imports memories from ChatGPT, Claude, Cursor, generic files, and structured markdown.
4
- */
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- import { createHash } from 'node:crypto';
8
- import { createMemory, Layer } from './memory.js';
9
- import { initStore, loadAllEntries, writeEntry } from './store.js';
10
- import { duplicateKey, storedTextKeys } from './same-text.js';
11
- import { getGlobalRoot, initGlobal } from './shared.js';
12
- import { remember, archiveRaw, isPrivateScope } from './api.js';
13
- import { openHippoDb, closeHippoDb } from './db.js';
14
- import { RejectedValueError, checkRejectionGuard } from './rejection.js';
15
- import { loadConfig } from './config.js';
16
- import { vetSecrets } from './secret-detect.js';
17
- import { log } from './log.js';
18
- // ---------------------------------------------------------------------------
19
- // Shared core: dedup + write
20
- // ---------------------------------------------------------------------------
21
- /**
22
- * Given an array of raw text chunks, deduplicate against existing memories,
23
- * create MemoryEntry objects, write them (unless dry-run), and return a result.
24
- */
25
- export function importEntries(chunks, source, tags, options) {
26
- const targetRoot = options.global ? getGlobalRoot() : options.hippoRoot;
27
- // Ensure store is ready
28
- if (options.global) {
29
- initGlobal();
30
- }
31
- const keys = storedTextKeys(loadAllEntries(targetRoot, options.global ? undefined : options.tenantId));
32
- const allTags = [...new Set([...tags, ...(options.extraTags ?? [])])];
33
- const baseHalfLifeDays = loadConfig(targetRoot).defaultHalfLifeDays;
34
- let total = 0;
35
- let imported = 0;
36
- let skipped = 0;
37
- let rejected = 0;
38
- let redacted = 0;
39
- const entries = [];
40
- // AT1 P2 fix: a dry-run preview never called writeEntry, so it never
41
- // checked tombstones either — every non-duplicate chunk counted as
42
- // `imported` even when a real run would refuse it, making the preview's
43
- // `rejected` count silently 0. Probe (read-only) via the same guard
44
- // writeEntry uses internally, without ever writing.
45
- const dryRunDb = options.dryRun ? openHippoDb(targetRoot) : null;
46
- try {
47
- for (const raw of chunks) {
48
- const original = raw.trim();
49
- const trimmed = vetSecrets(original, allTags, true).content;
50
- const wasRedacted = trimmed !== original;
51
- if (trimmed.length > 1000) {
52
- log.warn(`imported memory truncated from ${trimmed.length} to 1000 chars`);
53
- }
54
- const chunk = trimmed.slice(0, 1000);
55
- // Skip empty or too-short chunks
56
- if (!chunk || chunk.length < 10) {
57
- skipped++;
58
- continue;
59
- }
60
- total++;
61
- // Dedup check: skip only when the same text is already stored
62
- if (keys.has(duplicateKey(chunk))) {
63
- skipped++;
64
- continue;
65
- }
66
- // A3: kind defaults to 'distilled'. ChatGPT/Claude/Cursor exports are curated
67
- // user pastes, not raw transcripts from a system of record, so distilled is
68
- // correct here. E1.3 (Slack ingestion) shipped 2026-04-29 in src/connectors/slack/
69
- // and sets kind: 'raw' + routes deletions through archiveRawMemory() — these
70
- // importers stay 'distilled' per the original reasoning. See MEMORY_ENVELOPE.md.
71
- // L9: the dedup read above is scoped by options.tenantId — the WRITE
72
- // must match, or scoped-dedup-passes-then-default-tenant-write breaks
73
- // the per-tenant contract. Mirror the dedup-read guard: global=true
74
- // → host-wide write to global store (tenantId irrelevant, createMemory
75
- // defaults to 'default'). global=false → write to the same tenant as
76
- // the dedup read.
77
- const entry = createMemory(chunk, {
78
- layer: Layer.Episodic,
79
- tags: allTags,
80
- source,
81
- confidence: 'observed',
82
- tenantId: options.global ? undefined : options.tenantId,
83
- baseHalfLifeDays,
84
- });
85
- if (options.dryRun) {
86
- if (dryRunDb) {
87
- try {
88
- checkRejectionGuard(dryRunDb, entry.tenantId ?? 'default', entry.id, entry.content);
89
- }
90
- catch (err) {
91
- if (err instanceof RejectedValueError) {
92
- rejected++;
93
- continue;
94
- }
95
- throw err;
96
- }
97
- }
98
- }
99
- else {
100
- // AT1 (plan §3 containment): a rejection refuses one CHUNK, not the
101
- // whole import batch. Caught per-item so siblings still land.
102
- try {
103
- writeEntry(targetRoot, entry);
104
- }
105
- catch (err) {
106
- if (err instanceof RejectedValueError) {
107
- rejected++;
108
- continue;
109
- }
110
- throw err;
111
- }
112
- // Add to existing so subsequent chunks dedup against freshly imported ones
113
- keys.add(duplicateKey(chunk));
114
- }
115
- entries.push(entry);
116
- imported++;
117
- if (wasRedacted)
118
- redacted++;
119
- }
120
- return { total, imported, skipped, rejected, redacted, entries };
121
- }
122
- finally {
123
- if (dryRunDb)
124
- closeHippoDb(dryRunDb);
125
- }
126
- }
127
- function isJsonString(x) {
128
- return typeof x === 'string';
129
- }
130
- function isJsonPlainObject(x) {
131
- return x !== null && !Array.isArray(x) && typeof x === 'object';
132
- }
133
- /** Coerce one imported record (string, `{content|text: ...}` object, or
134
- * anything else) into the plain-text memory chunk it represents. */
135
- function extractMemoryText(candidate) {
136
- if (isJsonString(candidate))
137
- return candidate;
138
- if (isJsonPlainObject(candidate)) {
139
- return String(candidate['content'] ?? candidate['text'] ?? '');
140
- }
141
- return '';
142
- }
143
- /**
144
- * Parse ChatGPT memory export file.
145
- * Supports:
146
- * - JSON array of strings: ["memory 1", "memory 2"]
147
- * - JSON array of objects: [{"content": "...", "created": "..."}]
148
- * - ChatGPT export format: {"memories": [{"content": "...", "created_at": "..."}]}
149
- * - Plain text: one memory per line
150
- */
151
- function parseChatGPTFile(filePath) {
152
- const raw = fs.readFileSync(filePath, 'utf8').trim();
153
- // Try JSON first
154
- if (raw.startsWith('[') || raw.startsWith('{')) {
155
- try {
156
- const parsed = JSON.parse(raw);
157
- // {"memories": [...]} - ChatGPT export format
158
- if (isJsonPlainObject(parsed) && Array.isArray(parsed.memories)) {
159
- return parsed.memories.map(extractMemoryText).filter(Boolean);
160
- }
161
- // Array format
162
- if (Array.isArray(parsed)) {
163
- return parsed.map(extractMemoryText).filter(Boolean);
164
- }
165
- }
166
- catch {
167
- // Fall through to plain text
168
- }
169
- }
170
- // Plain text: one memory per line
171
- return raw.split('\n').map((l) => l.trim()).filter(Boolean);
172
- }
173
- export function importChatGPT(filePath, options) {
174
- const chunks = parseChatGPTFile(filePath);
175
- return importEntries(chunks, 'import:chatgpt', ['imported', 'chatgpt'], options);
176
- }
177
- // ---------------------------------------------------------------------------
178
- // Claude importer
179
- // ---------------------------------------------------------------------------
180
- const HIPPO_START = '<!-- hippo:start -->';
181
- const HIPPO_END = '<!-- hippo:end -->';
182
- /**
183
- * Strip the hippo hook block from markdown content.
184
- */
185
- function stripHippoBlock(content) {
186
- const startIdx = content.indexOf(HIPPO_START);
187
- const endIdx = content.indexOf(HIPPO_END);
188
- if (startIdx === -1 || endIdx === -1)
189
- return content;
190
- return content.slice(0, startIdx) + content.slice(endIdx + HIPPO_END.length);
191
- }
192
- /**
193
- * Split markdown into meaningful chunks (headings + bullet points).
194
- */
195
- function splitMarkdown(content) {
196
- const chunks = [];
197
- const lines = content.split('\n');
198
- let current = '';
199
- for (const line of lines) {
200
- const trimmed = line.trim();
201
- // Heading: start a new chunk
202
- if (/^#{1,6}\s+/.test(trimmed)) {
203
- if (current.trim())
204
- chunks.push(current.trim());
205
- current = trimmed;
206
- continue;
207
- }
208
- // Bullet point: each bullet is its own chunk (flush previous if not a bullet context)
209
- if (/^[-*+]\s+/.test(trimmed)) {
210
- if (current.trim() && !/^[-*+]\s+/.test(current.split('\n')[0])) {
211
- chunks.push(current.trim());
212
- current = '';
213
- }
214
- if (current.trim()) {
215
- chunks.push(current.trim());
216
- current = '';
217
- }
218
- current = trimmed.replace(/^[-*+]\s+/, '').trim();
219
- continue;
220
- }
221
- // Numbered list item
222
- if (/^\d+\.\s+/.test(trimmed)) {
223
- if (current.trim()) {
224
- chunks.push(current.trim());
225
- current = '';
226
- }
227
- current = trimmed.replace(/^\d+\.\s+/, '').trim();
228
- continue;
229
- }
230
- // Empty line
231
- if (!trimmed) {
232
- if (current.trim()) {
233
- chunks.push(current.trim());
234
- current = '';
235
- }
236
- continue;
237
- }
238
- // Regular line: append to current
239
- current = current ? current + ' ' + trimmed : trimmed;
240
- }
241
- if (current.trim())
242
- chunks.push(current.trim());
243
- return chunks.filter(Boolean);
244
- }
245
- /**
246
- * Parse CLAUDE.md or Claude memory.json.
247
- */
248
- function parseClaudeFile(filePath) {
249
- const raw = fs.readFileSync(filePath, 'utf8');
250
- // JSON memory file
251
- if (filePath.endsWith('.json')) {
252
- try {
253
- const parsed = JSON.parse(raw.trim());
254
- if (Array.isArray(parsed)) {
255
- return parsed.map(extractMemoryText).filter(Boolean);
256
- }
257
- }
258
- catch {
259
- // Fall through to markdown
260
- }
261
- }
262
- // Markdown file: strip hippo block and split
263
- const cleaned = stripHippoBlock(raw);
264
- return splitMarkdown(cleaned);
265
- }
266
- export function importClaude(filePath, options) {
267
- const chunks = parseClaudeFile(filePath);
268
- return importEntries(chunks, 'import:claude', ['imported', 'claude'], options);
269
- }
270
- // ---------------------------------------------------------------------------
271
- // Cursor importer
272
- // ---------------------------------------------------------------------------
273
- /**
274
- * Split cursor rules file into chunks.
275
- * Priority: numbered items, then bullet points, then double newlines.
276
- */
277
- function parseCursorFile(content) {
278
- const chunks = [];
279
- const lines = content.split('\n');
280
- let current = '';
281
- for (const line of lines) {
282
- const trimmed = line.trim();
283
- // Skip comment-only lines that are empty after stripping #
284
- if (!trimmed || trimmed === '#') {
285
- if (current.trim()) {
286
- chunks.push(current.trim());
287
- current = '';
288
- }
289
- continue;
290
- }
291
- // Numbered item: 1. 2. 3.
292
- if (/^\d+\.\s+/.test(trimmed)) {
293
- if (current.trim()) {
294
- chunks.push(current.trim());
295
- current = '';
296
- }
297
- current = trimmed.replace(/^\d+\.\s+/, '').trim();
298
- continue;
299
- }
300
- // Bullet: - or *
301
- if (/^[-*]\s+/.test(trimmed)) {
302
- if (current.trim()) {
303
- chunks.push(current.trim());
304
- current = '';
305
- }
306
- current = trimmed.replace(/^[-*]\s+/, '').trim();
307
- continue;
308
- }
309
- // Regular line
310
- current = current ? current + ' ' + trimmed : trimmed;
311
- }
312
- if (current.trim())
313
- chunks.push(current.trim());
314
- // Also split on double newlines within chunks if they somehow ended up there
315
- return chunks.flatMap((c) => {
316
- const parts = c.split(/\n{2,}/);
317
- return parts.map((p) => p.trim()).filter(Boolean);
318
- });
319
- }
320
- const CURSOR_RULE_FILE = /\.mdc?$/i;
321
- /** Import `.cursorrules`, one rule file, or a `.cursor/rules` tree of `.mdc` and `.md` rules. */
322
- export function importCursor(sourcePath, options) {
323
- const files = fs.statSync(sourcePath).isDirectory()
324
- ? collectMarkdownFiles(sourcePath, options.hippoRoot, CURSOR_RULE_FILE).map((rel) => path.join(sourcePath, rel))
325
- : [sourcePath];
326
- const chunks = files.flatMap((file) => {
327
- const raw = fs.readFileSync(file, 'utf8');
328
- return parseCursorFile(CURSOR_RULE_FILE.test(file) ? parseFrontmatter(raw).body : raw);
329
- });
330
- return importEntries(chunks, 'import:cursor', ['imported', 'cursor'], options);
331
- }
332
- // ---------------------------------------------------------------------------
333
- // Generic file importer
334
- // ---------------------------------------------------------------------------
335
- /**
336
- * Split a generic file into chunks.
337
- * Markdown: split on headings and bullet points.
338
- * Plain text: split on double newlines or one-per-line.
339
- */
340
- function parseGenericFile(filePath) {
341
- const raw = fs.readFileSync(filePath, 'utf8');
342
- const isMarkdown = filePath.endsWith('.md') || filePath.endsWith('.mdx');
343
- if (isMarkdown) {
344
- return splitMarkdown(raw);
345
- }
346
- // Plain text: try double newlines first
347
- const byParagraph = raw.split(/\n{2,}/).map((p) => p.trim()).filter(Boolean);
348
- if (byParagraph.length > 1)
349
- return byParagraph;
350
- // Fall back to one-per-line
351
- return raw.split('\n').map((l) => l.trim()).filter(Boolean);
352
- }
353
- export function importGenericFile(filePath, options) {
354
- const chunks = parseGenericFile(filePath);
355
- return importEntries(chunks, 'import:file', ['imported'], options);
356
- }
357
- // ---------------------------------------------------------------------------
358
- // Structured markdown importer (MEMORY.md / AGENTS.md format)
359
- // ---------------------------------------------------------------------------
360
- /**
361
- * Slugify a heading for use as a tag.
362
- * "Data Pipeline & Cache" -> "data-pipeline-cache"
363
- */
364
- function slugify(heading) {
365
- return heading
366
- .toLowerCase()
367
- .replace(/[^a-z0-9\s-]/g, ' ')
368
- .trim()
369
- .replace(/\s+/g, '-')
370
- .replace(/-+/g, '-')
371
- .slice(0, 50);
372
- }
373
- /**
374
- * Parse structured markdown into {content, sectionSlug} pairs.
375
- * Each heading starts a new section. Bullet points / numbered items under
376
- * the heading become individual memories tagged with the section slug.
377
- */
378
- function parseStructuredMarkdown(raw) {
379
- const results = [];
380
- const lines = raw.split('\n');
381
- let currentSection = '';
382
- let currentSlug = '';
383
- let pendingText = '';
384
- function flush() {
385
- if (!pendingText.trim())
386
- return;
387
- results.push({ content: pendingText.trim(), sectionSlug: currentSlug });
388
- pendingText = '';
389
- }
390
- for (const line of lines) {
391
- const trimmed = line.trim();
392
- // Heading
393
- const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)/);
394
- if (headingMatch) {
395
- flush();
396
- currentSection = headingMatch[2].trim();
397
- currentSlug = slugify(currentSection);
398
- continue;
399
- }
400
- // Bullet or numbered item: flush previous, start new
401
- if (/^[-*+]\s+/.test(trimmed) || /^\d+\.\s+/.test(trimmed)) {
402
- flush();
403
- const itemText = trimmed.replace(/^[-*+]\s+/, '').replace(/^\d+\.\s+/, '').trim();
404
- pendingText = itemText;
405
- continue;
406
- }
407
- // Empty line: flush current pending
408
- if (!trimmed) {
409
- flush();
410
- continue;
411
- }
412
- // Continuation of current item
413
- pendingText = pendingText ? pendingText + ' ' + trimmed : trimmed;
414
- }
415
- flush();
416
- return results.filter((r) => r.content.length > 0);
417
- }
418
- export function importMarkdown(filePath, options) {
419
- const raw = fs.readFileSync(filePath, 'utf8');
420
- const parsed = parseStructuredMarkdown(raw);
421
- // Group by section slug so we can pass per-chunk tags
422
- // We call importEntries per unique slug to get the right tags per section
423
- const bySlug = new Map();
424
- for (const { content, sectionSlug } of parsed) {
425
- const list = bySlug.get(sectionSlug) ?? [];
426
- list.push(content);
427
- bySlug.set(sectionSlug, list);
428
- }
429
- let totalResult = { total: 0, imported: 0, skipped: 0, rejected: 0, entries: [] };
430
- for (const [slug, chunks] of bySlug.entries()) {
431
- const sectionTags = slug ? ['imported', slug] : ['imported'];
432
- const result = importEntries(chunks, 'import:markdown', sectionTags, options);
433
- totalResult = {
434
- total: totalResult.total + result.total,
435
- imported: totalResult.imported + result.imported,
436
- skipped: totalResult.skipped + result.skipped,
437
- // AT1 P2 fix: `rejected` is now optional on ImportResult (compat) — tolerate
438
- // undefined on either side of the accumulation.
439
- rejected: (totalResult.rejected ?? 0) + (result.rejected ?? 0),
440
- redacted: (totalResult.redacted ?? 0) + (result.redacted ?? 0),
441
- entries: [...totalResult.entries, ...result.entries],
442
- };
443
- }
444
- return totalResult;
445
- }
446
- // ---------------------------------------------------------------------------
447
- // K1 vault importer (markdown-vault FOLDER → kind='raw' memories)
448
- //
449
- // MIRRORS THE CONNECTOR PATTERN (src/connectors/slack|github), NOT the
450
- // single-file importers above. Each note becomes a single kind='raw' row with
451
- // provenance in TAGS (`source:vault` + `vault:<name>`), an artifactRef cursor
452
- // key, and a content-hash tag. Changes APPEND a new raw row after archiveRaw of
453
- // the old one; deletions archiveRaw the orphaned rows. We NEVER `supersede` a
454
- // raw row (supersede yields kind='distilled', losing raw-append-only protection
455
- // and escaping the kind='raw' deletion rescan) — all raw deletions route through
456
- // `archiveRaw` (the only trigger-legit raw delete).
457
- // ---------------------------------------------------------------------------
458
- /** Escape LIKE wildcards in operator-supplied text (mirror of
459
- * src/project-briefs.ts:477 / src/store.ts:782, kept local since neither is
460
- * exported). Used so a `%`/`_`/`\` in the vault name cannot over-match the
461
- * loader prefix and archive another vault's rows. */
462
- function escapeLike(term) {
463
- return term.replace(/[%_\\]/g, '\\$&');
464
- }
465
- function parseFrontmatter(raw) {
466
- // Must start with `---` on its own line. Accept CRLF or LF.
467
- const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
468
- if (!m)
469
- return { fm: {}, body: raw };
470
- const block = m[1];
471
- const body = raw.slice(m[0].length);
472
- const fm = {};
473
- const lines = block.split(/\r?\n/);
474
- for (let i = 0; i < lines.length; i++) {
475
- const kv = lines[i].match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/);
476
- if (!kv)
477
- continue;
478
- let val = kv[2].trim();
479
- if (val === '') {
480
- // YAML block-style list: `key:` followed by indented `- item` lines
481
- // (common in Obsidian/Dendron frontmatter). Collect them into a
482
- // comma-joined value so frontmatterList parses them (codex P2).
483
- const items = [];
484
- let j = i + 1;
485
- let item;
486
- while (j < lines.length && (item = lines[j].match(/^\s+-\s+(.+?)\s*$/)) !== null) {
487
- items.push(item[1].replace(/^['"]|['"]$/g, '').trim());
488
- j++;
489
- }
490
- if (items.length) {
491
- val = items.join(', ');
492
- i = j - 1;
493
- }
494
- }
495
- fm[kv[1]] = val;
496
- }
497
- return { fm, body };
498
- }
499
- /** Pull a frontmatter field that may be a YAML flow list (`[a, b]`), a
500
- * comma-separated scalar (`a, b`), or a single token, into a string[]. Quotes
501
- * and surrounding brackets are stripped; empty entries dropped. */
502
- function frontmatterList(value) {
503
- if (!value)
504
- return [];
505
- let v = value.trim();
506
- if (v.startsWith('[') && v.endsWith(']'))
507
- v = v.slice(1, -1);
508
- return v
509
- .split(',')
510
- .map((s) => s.trim().replace(/^['"]|['"]$/g, '').trim())
511
- .filter(Boolean);
512
- }
513
- /** Parse `[[wikilinks]]` from body text. `[[target]]` and `[[target|alias]]`
514
- * both yield `target` (alias dropped). Returns de-duplicated, order-preserving
515
- * target strings (trimmed). Embeds (`![[…]]`) are intentionally matched too —
516
- * the leading `!` is not part of the `[[…]]` capture, so an embed contributes
517
- * its target as a candidate, which is the desired no-crash baseline behaviour. */
518
- function parseWikilinks(body) {
519
- const out = [];
520
- const seen = new Set();
521
- const re = /\[\[([^\]]+?)\]\]/g;
522
- let match;
523
- while ((match = re.exec(body)) !== null) {
524
- const inner = match[1];
525
- const target = (inner.split('|')[0] ?? '').trim();
526
- if (!target)
527
- continue;
528
- if (seen.has(target))
529
- continue;
530
- seen.add(target);
531
- out.push(target);
532
- }
533
- return out;
534
- }
535
- /** Recursively collect files whose name matches `match` (`*.md` by default) under `root`,
536
- * as paths relative to `root` with forward-slash separators (stable artifactRef keys across OSes).
537
- * Symlinks are not followed. Skips dot-directories (the default `.hippo` store,
538
- * `.git`, `.obsidian`, `.trash`) AND the canonicalized Hippo store path during
539
- * the walk, so re-importing a vault that CONTAINS the store never ingests its own
540
- * markdown mirror files (codex R5 P1: `hippo import --vault .` after `hippo
541
- * init` in the vault would otherwise self-import its mirror rows and grow on
542
- * every run). The root-IS-the-store case is handled one level up in
543
- * importVault (a no-op early return), NOT here: returning [] for it would feed
544
- * the deletion-sync an empty scan that mass-archives every live row (codex R8). */
545
- function collectMarkdownFiles(root, hippoRoot, match = /\.md$/i) {
546
- const out = [];
547
- // Canonicalize (realpath) so a non-dot HIPPO_HOME store nested in the vault is
548
- // skipped even when hippoRoot is an aliased path (junction / Windows case
549
- // variant); path.resolve would miss it and self-import the store's mirror
550
- // files (codex R9 follow-up: same gap as the importVault guard, sibling site).
551
- const resolvedHippoRoot = realpathOrResolve(hippoRoot);
552
- const walk = (dir) => {
553
- const entries = fs.readdirSync(dir, { withFileTypes: true });
554
- for (const ent of entries) {
555
- if (ent.isSymbolicLink())
556
- continue;
557
- const abs = path.join(dir, ent.name);
558
- if (ent.isDirectory()) {
559
- // Skip dot-dirs (config/system, incl. the default `.hippo` store) and
560
- // the canonicalized store path (covers a HIPPO_HOME outside `.hippo`).
561
- if (ent.name.startsWith('.'))
562
- continue;
563
- if (realpathOrResolve(abs) === resolvedHippoRoot)
564
- continue;
565
- walk(abs);
566
- }
567
- else if (ent.isFile() && match.test(ent.name)) {
568
- out.push(path.relative(root, abs).split(path.sep).join('/'));
569
- }
570
- }
571
- };
572
- walk(root);
573
- out.sort();
574
- return out;
575
- }
576
- /** Canonicalize a path for the self-store comparison: dereference symlinks /
577
- * junctions and normalize case (Windows) via the OS realpath, so a junction or
578
- * a case-variant path to the store is still recognized as self-store (codex R9
579
- * P2: path.resolve does neither, so an aliased store path slipped past the
580
- * guard and triggered the mass-archive). Falls back to path.resolve when the
581
- * path does not exist yet - an uninitialized store or a typo'd vault path
582
- * cannot be a live self-store, and a non-existent vault folder fails later in
583
- * the walk (readdirSync) before deletion-sync can archive anything. */
584
- function realpathOrResolve(p) {
585
- try {
586
- return fs.realpathSync.native(p);
587
- }
588
- catch (err) {
589
- log.debug(`import: realpath fell back to resolve for ${p}: ${err instanceof Error ? err.message : String(err)}`);
590
- return path.resolve(p);
591
- }
592
- }
593
- /**
594
- * Import a markdown vault FOLDER as `kind='raw'` memories.
595
- *
596
- * NOT re-entrant: idempotency rests on the in-memory `existing` Map loaded once
597
- * at the top. Two concurrent importVault runs over the same vault could both see
598
- * a note as absent and double-insert (the connector pattern relies on a single
599
- * sequential writer; same caveat applies here).
600
- */
601
- export function importVault(folderPath, options) {
602
- const hippoRoot = options.hippoRoot;
603
- const tenantId = options.tenantId ?? 'default';
604
- // Vault NAME is the identity key for the destructive deletion-sync below, so it
605
- // must be explicit. Defaulting to the folder basename meant two unrelated vaults
606
- // sharing a basename (e.g. work/notes and personal/notes) collided on the same
607
- // `vault:<name>:*` prefix - importing the second loaded the first's rows and the
608
- // deletion-sync archived them (codex R10 P2). Require a deliberate name instead
609
- // of inferring a path-unstable one.
610
- const vaultName = options.name?.trim();
611
- if (!vaultName) {
612
- throw new Error('importVault requires an explicit vault name (options.name): it is the identity key for source-deletion sync and must not be inferred from the folder basename.');
613
- }
614
- if (vaultName.includes(':')) {
615
- // ':' is the artifactRef delimiter (vault:<name>:<relpath>); a name
616
- // containing it lets a different vault's prefix scan over-match and archive
617
- // its rows (codex P2). Reject rather than silently corrupt the keys.
618
- throw new Error(`vault name must not contain ':' (artifactRef delimiter): ${vaultName}`);
619
- }
620
- const scope = options.scope ?? null;
621
- // Privacy footgun guard (codex R13 P2): hippo's recall filter only default-denies
622
- // scopes shaped `<source>:private:*` (see isPrivateScope / PRIVATE_SCOPE_RE in
623
- // scope.ts). A bare `private` (or `private:<x>`) first segment is NOT recognized
624
- // as private, so notes a user believes are private would still be returned to
625
- // no-scope recall callers. Reject the alias and point at the source-prefixed form
626
- // rather than silently storing public-visible "private" notes. Use recall's own
627
- // isPrivateScope as the single source of truth: reject a scope that names a
628
- // `private` segment yet is NOT a valid `<source>:private:*` (catches `private`,
629
- // `private:x`, and `vault:private` with a missing trailing segment).
630
- if (scope !== null && scope.split(':').includes('private') && !isPrivateScope(scope)) {
631
- throw new Error(`vault scope '${scope}' is not recognized as private by recall (only '<source>:private:*' scopes are default-denied). Use a source-prefixed scope such as 'vault:private:${vaultName}'.`);
632
- }
633
- const extraTags = options.extraTags ?? [];
634
- const dryRun = options.dryRun ?? false;
635
- if (options.global) {
636
- // The raw-archive path is tenant-local; global mode would put raw vault rows
637
- // in the wrong store. Reject for SDK callers too (the CLI also rejects
638
- // --global) rather than silently writing local (codex P2).
639
- throw new Error('importVault does not support global mode (raw rows are tenant-local).');
640
- }
641
- // Self-store no-op guard (codex R8 P1). MUST run BEFORE the existing-rows load
642
- // and the deletion-sync pass below. If the vault folder IS the store (or lives
643
- // inside it), there are no real vault notes - only the store's own markdown
644
- // mirror files. Letting collectMarkdownFiles return [] for this case is NOT
645
- // safe: an empty scan is indistinguishable from "every note was deleted", so
646
- // deletion-sync would archive every live vault:<name>:* row, and raw-archive
647
- // content redaction makes that loss IRREVERSIBLE. The only safe reading of
648
- // "import the store into itself" is "do nothing". Canonicalize both paths
649
- // (realpath: dereference junctions/symlinks + normalize Windows case) so an
650
- // aliased path to the store is still caught (codex R9 P2).
651
- const resolvedStore = realpathOrResolve(hippoRoot);
652
- const resolvedFolder = realpathOrResolve(folderPath);
653
- if (resolvedFolder === resolvedStore || resolvedFolder.startsWith(resolvedStore + path.sep)) {
654
- return { total: 0, imported: 0, skipped: 0, rejected: 0, archived: 0, entries: [] };
655
- }
656
- const ctx = {
657
- hippoRoot,
658
- tenantId,
659
- // Process-local actor; the vault importer is a CLI/SDK ingestion path, not
660
- // a bearer-authed request. archiveRaw / remember thread this into audit.
661
- actor: { subject: 'connector:vault', role: 'admin' },
662
- };
663
- // Load ONCE: every existing raw row for this vault, tenant-scoped. The same
664
- // Map serves both per-file idempotency AND the deletion diff (no second
665
- // query). LIKE-escape the vault name so a `%`/`_` in it can't over-match.
666
- initStore(hippoRoot);
667
- // artifactRef -> ALL its live raw rows. >1 only after a concurrent double-insert
668
- // (the importer is not re-entrant; see the JSDoc). The buckets matter: a later
669
- // changed/deletion pass must archive EVERY matching row, not just the last one
670
- // scanned, or older raw vault content lingers live + searchable (codex P2).
671
- const existing = new Map();
672
- {
673
- const db = openHippoDb(hippoRoot);
674
- try {
675
- const likeParam = `vault:${escapeLike(vaultName)}:%`;
676
- // SAFETY: query selects exactly the columns of VaultRow, in the same
677
- // names, from the memories table this module owns.
678
- const rows = db
679
- .prepare(`SELECT id, artifact_ref, tags_json, scope FROM memories
680
- WHERE artifact_ref LIKE ? ESCAPE '\\' AND tenant_id = ? AND kind = 'raw'`)
681
- .all(likeParam, tenantId);
682
- // SQLite LIKE is case-insensitive for ASCII, so the query over-fetches
683
- // (vault 'A' also matches 'vault:a:%'). Filter to the EXACT-case prefix in
684
- // JS so deletion-sync never archives a different-cased vault's rows (codex P2).
685
- const exactPrefix = `vault:${vaultName}:`;
686
- for (const row of rows) {
687
- if (row.artifact_ref && row.artifact_ref.startsWith(exactPrefix)) {
688
- const bucket = existing.get(row.artifact_ref);
689
- if (bucket)
690
- bucket.push(row);
691
- else
692
- existing.set(row.artifact_ref, [row]);
693
- }
694
- }
695
- }
696
- finally {
697
- closeHippoDb(db);
698
- }
699
- }
700
- const relpaths = collectMarkdownFiles(folderPath, hippoRoot);
701
- const seen = new Set();
702
- let total = 0;
703
- let imported = 0;
704
- let skipped = 0;
705
- let rejected = 0;
706
- let archived = 0;
707
- let redacted = 0;
708
- const entries = [];
709
- const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
710
- // AT1 P2 fix: dry-run never called remember(), so it never probed
711
- // tombstones — every note that reached the write step counted as
712
- // `imported` even when a real run would refuse it. Probe (read-only) via
713
- // the same guard remember()/writeEntry uses, without ever writing.
714
- const dryRunDb = dryRun ? openHippoDb(hippoRoot) : null;
715
- try {
716
- for (const relpath of relpaths) {
717
- total++;
718
- const artifactRef = `vault:${vaultName}:${relpath}`;
719
- seen.add(artifactRef);
720
- // Content-hash is computed from the RAW file bytes (deterministic; no
721
- // Date/random in the content path) so idempotency survives frontmatter
722
- // edits identically to body edits.
723
- let rawFileContent;
724
- try {
725
- rawFileContent = fs.readFileSync(path.join(folderPath, relpath), 'utf8');
726
- }
727
- catch {
728
- // File vanished between enumeration and read (TOCTOU), or a transient
729
- // IO/permission error. Skip this one file rather than aborting the whole
730
- // import (incl. the deletion-sync pass); an idempotent re-run picks it up.
731
- skipped++;
732
- continue;
733
- }
734
- const hash = createHash('sha256').update(rawFileContent).digest('hex');
735
- const hashTag = `content-hash:${hash}`;
736
- // Load every live raw row for this ref (>1 only after a concurrent double-
737
- // insert). The idempotency decision happens below, AFTER the full tag set is
738
- // built, so it can compare the complete envelope rather than a subset.
739
- const priors = existing.get(artifactRef) ?? [];
740
- const { fm, body } = parseFrontmatter(rawFileContent);
741
- // Empty / frontmatter-only note: nothing storable (createMemory enforces a
742
- // min content length). The note's CONTENT was deleted at source, so this is a
743
- // content-deletion: archive any prior row(s) - the old body must not stay live
744
- // and searchable after the source no longer holds it (codex R12 P2) - then
745
- // skip the write. Archiving here is safe precisely because we then skip
746
- // remember() entirely: there is no archive-then-throw-on-empty-body hazard
747
- // (the original reason this branch did not archive). The note stays in `seen`
748
- // so deletion-sync does not double-process it.
749
- if (body.trim().length < 3) {
750
- for (const p of priors) {
751
- archived++; // count even in dryRun so the preview reflects the removal
752
- if (!dryRun)
753
- archiveRaw(ctx, p.id, `emptied:${artifactRef}`);
754
- }
755
- skipped++;
756
- continue;
757
- }
758
- // Build the FULL tag envelope this import would write, BEFORE the idempotency
759
- // decision. De-duplicate (createMemory stores tags verbatim, so a collision
760
- // between, e.g., a frontmatter tag and an extraTag would otherwise produce a
761
- // duplicate). Order-preserving.
762
- const frontmatterTags = [
763
- ...frontmatterList(fm['tags']),
764
- ...frontmatterList(fm['aliases']).map((a) => `alias:${a}`),
765
- ];
766
- const wikilinkTags = parseWikilinks(body).map((t) => `wikilink-candidate:${t}`);
767
- const tags = Array.from(new Set([
768
- 'source:vault',
769
- `vault:${vaultName}`,
770
- hashTag,
771
- ...frontmatterTags,
772
- ...wikilinkTags,
773
- ...extraTags,
774
- ]));
775
- // Unchanged iff EVERY live raw row carries the EXACT same tag set AND scope.
776
- // Comparing the COMPLETE set (not the content-hash + a subset of extra tags)
777
- // means every envelope change registers: content (via the content-hash tag),
778
- // frontmatter, wikilinks, an ADDED extra tag, or a REMOVED one - the earlier
779
- // piecemeal checks missed scope (R10 P2) then tag removal (R11 P2). Set
780
- // equality is order-independent and both sides are deduped. (`length > 0`
781
- // guard: a never-seen file must import, not skip; archiving ALL priors on a
782
- // mismatch also clears any concurrent-double-insert duplicates.)
783
- const wantTags = new Set(tags);
784
- const envelopeUnchanged = priors.length > 0 &&
785
- priors.every((p) => {
786
- if ((p.scope ?? null) !== scope)
787
- return false;
788
- const priorTags = parseJsonArrayLoose(p.tags_json);
789
- return priorTags.length === wantTags.size && priorTags.every((t) => wantTags.has(t));
790
- });
791
- if (envelopeUnchanged) {
792
- // Unchanged file + envelope → skip (idempotent re-import).
793
- skipped++;
794
- continue;
795
- }
796
- // Changed file → archive EVERY old raw row for this ref (normally one; >1
797
- // only after a concurrent double-insert), then append the new one. NEVER
798
- // supersede (would yield kind='distilled'). archiveRaw commits + closes its
799
- // handle before remember() runs, so there is no double-live row; a crash
800
- // between them self-heals (file re-imported as fresh raw next run).
801
- for (const p of priors) {
802
- archived++; // count the would-be archive even in dryRun (true preview)
803
- if (!dryRun)
804
- archiveRaw(ctx, p.id, `changed:${artifactRef}`);
805
- }
806
- // remember() owns the actual write. We build an `echo` of the SAME content +
807
- // tags via createMemory purely for the ImportResult, then reconcile its id to
808
- // remember()'s real row id so entries[] reflects the row that landed.
809
- const content = vetSecrets(body, tags, true).content;
810
- const echo = createMemory(content, {
811
- kind: 'raw',
812
- tags,
813
- scope,
814
- owner: 'agent:vault-import',
815
- artifact_ref: artifactRef,
816
- tenantId,
817
- baseHalfLifeDays,
818
- });
819
- // dryRun preview: count what WOULD import, but make no writes (codex P2).
820
- if (!dryRun) {
821
- // AT1 (plan §3 containment): a rejected note must not abort the rest
822
- // of the vault scan (deletion-sync pass included). The priors above
823
- // are already archived by this point — same self-heal story as any
824
- // other crash between archiveRaw and remember() (comment above): a
825
- // re-run with the file still rejected hits the same refusal again,
826
- // loud each time via the rejected count.
827
- try {
828
- const result = remember(ctx, {
829
- content,
830
- kind: 'raw',
831
- artifactRef,
832
- owner: 'agent:vault-import',
833
- scope: scope ?? undefined,
834
- tags,
835
- });
836
- echo.id = result.id;
837
- }
838
- catch (err) {
839
- if (err instanceof RejectedValueError) {
840
- rejected++;
841
- continue;
842
- }
843
- throw err;
844
- }
845
- }
846
- else if (dryRunDb) {
847
- // AT1 P2 fix: probe the tombstone without writing so the preview's
848
- // `rejected` count matches what a real run would refuse.
849
- try {
850
- checkRejectionGuard(dryRunDb, tenantId, echo.id, echo.content);
851
- }
852
- catch (err) {
853
- if (err instanceof RejectedValueError) {
854
- rejected++;
855
- continue;
856
- }
857
- throw err;
858
- }
859
- }
860
- entries.push(echo);
861
- imported++;
862
- if (content !== body)
863
- redacted++;
864
- }
865
- }
866
- finally {
867
- if (dryRunDb)
868
- closeHippoDb(dryRunDb);
869
- }
870
- // Deletion-sync: any artifactRef present in the Map but NOT seen this run is a
871
- // note that vanished from the source folder → archive its raw row. Per-file
872
- // archiveRaw (own handle); no outer SAVEPOINT (no cross-file idempotency row
873
- // to commit atomically, unlike github's multi-row case).
874
- for (const [artifactRef, rows] of existing) {
875
- if (seen.has(artifactRef))
876
- continue;
877
- for (const row of rows) {
878
- archived++; // count even in dryRun so the preview reflects destructive deletes
879
- if (!dryRun)
880
- archiveRaw(ctx, row.id, `source_deleted:${artifactRef}`);
881
- }
882
- }
883
- return { total, imported, skipped, rejected, archived, redacted, entries };
884
- }
885
- /** Local tolerant JSON-array parse for the loader's `tags_json` column. The
886
- * store's own `parseJsonArray` is not exported; this matches its contract
887
- * (returns [] on null/garbage). */
888
- function parseJsonArrayLoose(value) {
889
- if (!value)
890
- return [];
891
- try {
892
- const parsed = JSON.parse(value);
893
- return Array.isArray(parsed) ? parsed.filter(isJsonString) : [];
894
- }
895
- catch (err) {
896
- log.debug(`import: unreadable tags_json read as no tags: ${err instanceof Error ? err.message : String(err)}`);
897
- return [];
898
- }
899
- }
900
- //# sourceMappingURL=importers.js.map